HostingAsset-Hierarchie spec in enum HsHostingAssetType and generates PlantUML #72

Merged
hsh-michaelhoennig merged 9 commits from add-mermaid-graph-generator-for-hosting-asset-type-structure into master 2024-07-09 14:32:14 +02:00
31 changed files with 1076 additions and 233 deletions

View File

@ -0,0 +1,199 @@
## HostingAsset Type Structure
### Domain
```plantuml
@startuml
left to right direction
package Booking #feb28c {
entity BI_PRIVATE_CLOUD
entity BI_CLOUD_SERVER
entity BI_MANAGED_SERVER
entity BI_MANAGED_WEBSPACE
entity BI_DOMAIN_DNS_SETUP
entity BI_DOMAIN_EMAIL_SUBMISSION_SETUP
}
package Hosting #feb28c{
package Domain #99bcdb {
entity HA_DOMAIN_SETUP
entity HA_DOMAIN_DNS_SETUP
entity HA_DOMAIN_HTTP_SETUP
entity HA_DOMAIN_EMAIL_SUBMISSION_SETUP
entity HA_DOMAIN_EMAIL_MAILBOX_SETUP
entity HA_EMAIL_ADDRESS
}
package Server #99bcdb {
entity HA_CLOUD_SERVER
entity HA_MANAGED_SERVER
entity HA_IP_NUMBER
}
package Webspace #99bcdb {
entity HA_MANAGED_WEBSPACE
entity HA_UNIX_USER
entity HA_EMAIL_ALIAS
}
}
BI_CLOUD_SERVER *--> BI_PRIVATE_CLOUD
BI_MANAGED_SERVER *--> BI_PRIVATE_CLOUD
BI_MANAGED_WEBSPACE *--> BI_MANAGED_SERVER
HA_CLOUD_SERVER ==* BI_CLOUD_SERVER
HA_MANAGED_SERVER ==* BI_MANAGED_SERVER
HA_MANAGED_WEBSPACE ==* BI_MANAGED_WEBSPACE
HA_MANAGED_WEBSPACE o..> HA_MANAGED_SERVER
HA_UNIX_USER *==> HA_MANAGED_WEBSPACE
HA_DOMAIN_SETUP o..> HA_DOMAIN_SETUP
HA_DOMAIN_DNS_SETUP *==> HA_DOMAIN_SETUP
HA_DOMAIN_HTTP_SETUP *==> HA_DOMAIN_SETUP
HA_DOMAIN_HTTP_SETUP o..> HA_UNIX_USER
HA_DOMAIN_EMAIL_SUBMISSION_SETUP *==> HA_DOMAIN_SETUP
HA_DOMAIN_EMAIL_SUBMISSION_SETUP o..> HA_MANAGED_WEBSPACE
HA_DOMAIN_EMAIL_MAILBOX_SETUP *==> HA_DOMAIN_SETUP
HA_DOMAIN_EMAIL_MAILBOX_SETUP o..> HA_MANAGED_WEBSPACE
HA_EMAIL_ALIAS *==> HA_MANAGED_WEBSPACE
HA_EMAIL_ADDRESS *==> HA_DOMAIN_EMAIL_MAILBOX_SETUP
HA_IP_NUMBER o..> HA_CLOUD_SERVER
HA_IP_NUMBER o..> HA_MANAGED_SERVER
HA_IP_NUMBER o..> HA_MANAGED_WEBSPACE
package Legend #white {
SUB_ENTITY1 *--> REQUIRED_PARENT_ENTITY
SUB_ENTITY2 *..> OPTIONAL_PARENT_ENTITY
ASSIGNED_ENTITY1 o--> REQUIRED_ASSIGNED_TO_ENTITY1
ASSIGNED_ENTITY2 o..> OPTIONAL_ASSIGNED_TO_ENTITY2
}
Booking -down[hidden]->Legend
```
### MariaDB
```plantuml
@startuml
left to right direction
package Booking #feb28c {
entity BI_PRIVATE_CLOUD
entity BI_CLOUD_SERVER
entity BI_MANAGED_SERVER
entity BI_MANAGED_WEBSPACE
entity BI_DOMAIN_DNS_SETUP
entity BI_DOMAIN_EMAIL_SUBMISSION_SETUP
}
package Hosting #feb28c{
package MariaDB #99bcdb {
entity HA_MARIADB_INSTANCE
entity HA_MARIADB_USER
entity HA_MARIADB_DATABASE
}
package Server #99bcdb {
entity HA_CLOUD_SERVER
entity HA_MANAGED_SERVER
entity HA_IP_NUMBER
}
package Webspace #99bcdb {
entity HA_MANAGED_WEBSPACE
entity HA_UNIX_USER
entity HA_EMAIL_ALIAS
}
}
BI_CLOUD_SERVER *--> BI_PRIVATE_CLOUD
BI_MANAGED_SERVER *--> BI_PRIVATE_CLOUD
BI_MANAGED_WEBSPACE *--> BI_MANAGED_SERVER
HA_CLOUD_SERVER ==* BI_CLOUD_SERVER
HA_MANAGED_SERVER ==* BI_MANAGED_SERVER
HA_MANAGED_WEBSPACE ==* BI_MANAGED_WEBSPACE
HA_MANAGED_WEBSPACE o..> HA_MANAGED_SERVER
HA_UNIX_USER *==> HA_MANAGED_WEBSPACE
HA_EMAIL_ALIAS *==> HA_MANAGED_WEBSPACE
HA_MARIADB_INSTANCE *==> HA_MANAGED_SERVER
HA_MARIADB_USER *==> HA_MARIADB_INSTANCE
HA_MARIADB_USER o..> HA_MANAGED_WEBSPACE
HA_MARIADB_DATABASE *==> HA_MANAGED_WEBSPACE
HA_MARIADB_DATABASE o..> HA_MARIADB_INSTANCE
HA_IP_NUMBER o..> HA_CLOUD_SERVER
HA_IP_NUMBER o..> HA_MANAGED_SERVER
HA_IP_NUMBER o..> HA_MANAGED_WEBSPACE
package Legend #white {
SUB_ENTITY1 *--> REQUIRED_PARENT_ENTITY
SUB_ENTITY2 *..> OPTIONAL_PARENT_ENTITY
ASSIGNED_ENTITY1 o--> REQUIRED_ASSIGNED_TO_ENTITY1
ASSIGNED_ENTITY2 o..> OPTIONAL_ASSIGNED_TO_ENTITY2
}
Booking -down[hidden]->Legend
```
### PostgreSQL
```plantuml
@startuml
left to right direction
package Booking #feb28c {
entity BI_PRIVATE_CLOUD
entity BI_CLOUD_SERVER
entity BI_MANAGED_SERVER
entity BI_MANAGED_WEBSPACE
entity BI_DOMAIN_DNS_SETUP
entity BI_DOMAIN_EMAIL_SUBMISSION_SETUP
}
package Hosting #feb28c{
package PostgreSQL #99bcdb {
entity HA_PGSQL_INSTANCE
entity HA_PGSQL_USER
entity HA_PGSQL_DATABASE
}
package Server #99bcdb {
entity HA_CLOUD_SERVER
entity HA_MANAGED_SERVER
entity HA_IP_NUMBER
}
package Webspace #99bcdb {
entity HA_MANAGED_WEBSPACE
entity HA_UNIX_USER
entity HA_EMAIL_ALIAS
}
}
BI_CLOUD_SERVER *--> BI_PRIVATE_CLOUD
BI_MANAGED_SERVER *--> BI_PRIVATE_CLOUD
BI_MANAGED_WEBSPACE *--> BI_MANAGED_SERVER
HA_CLOUD_SERVER ==* BI_CLOUD_SERVER
HA_MANAGED_SERVER ==* BI_MANAGED_SERVER
HA_MANAGED_WEBSPACE ==* BI_MANAGED_WEBSPACE
HA_MANAGED_WEBSPACE o..> HA_MANAGED_SERVER
HA_UNIX_USER *==> HA_MANAGED_WEBSPACE
HA_EMAIL_ALIAS *==> HA_MANAGED_WEBSPACE
HA_PGSQL_INSTANCE *==> HA_MANAGED_SERVER
HA_PGSQL_USER *==> HA_PGSQL_INSTANCE
HA_PGSQL_USER o..> HA_MANAGED_WEBSPACE
HA_PGSQL_DATABASE *==> HA_MANAGED_WEBSPACE
HA_PGSQL_DATABASE o..> HA_PGSQL_INSTANCE
HA_IP_NUMBER o..> HA_CLOUD_SERVER
HA_IP_NUMBER o..> HA_MANAGED_SERVER
HA_IP_NUMBER o..> HA_MANAGED_WEBSPACE
package Legend #white {
SUB_ENTITY1 *--> REQUIRED_PARENT_ENTITY
SUB_ENTITY2 *..> OPTIONAL_PARENT_ENTITY
ASSIGNED_ENTITY1 o--> REQUIRED_ASSIGNED_TO_ENTITY1
ASSIGNED_ENTITY2 o..> OPTIONAL_ASSIGNED_TO_ENTITY2
}
Booking -down[hidden]->Legend
```
This code generated was by HsHostingAssetType.main, do not amend manually.

View File

@ -1,8 +1,37 @@
package net.hostsharing.hsadminng.hs.booking.item; package net.hostsharing.hsadminng.hs.booking.item;
public enum HsBookingItemType { import java.util.List;
import static java.util.Optional.ofNullable;
public enum HsBookingItemType implements Node {
PRIVATE_CLOUD, PRIVATE_CLOUD,
CLOUD_SERVER, CLOUD_SERVER(PRIVATE_CLOUD),
MANAGED_SERVER, MANAGED_SERVER(PRIVATE_CLOUD),
MANAGED_WEBSPACE MANAGED_WEBSPACE(MANAGED_SERVER),
DOMAIN_DNS_SETUP, // TODO.spec: experimental
DOMAIN_EMAIL_SUBMISSION_SETUP; // TODO.spec: experimental
private final HsBookingItemType parentItemType;
HsBookingItemType() {
this.parentItemType = null;
}
HsBookingItemType(final HsBookingItemType parentItemType) {
this.parentItemType = parentItemType;
}
@Override
public List<String> edges() {
return ofNullable(parentItemType)
.map(p -> (nodeName() + " *--> " + p.nodeName()))
.stream().toList();
}
@Override
public String nodeName() {
return "BI_" + name();
}
} }

View File

@ -0,0 +1,9 @@
package net.hostsharing.hsadminng.hs.booking.item;
import java.util.List;
public interface Node {
String nodeName();
List<String> edges();
}

View File

@ -8,7 +8,7 @@ import java.util.List;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable; import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_EMAIL_SETUP; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_EMAIL_MAILBOX_SETUP;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.EMAIL_ADDRESS; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.EMAIL_ADDRESS;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MARIADB_DATABASE; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MARIADB_DATABASE;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MARIADB_USER; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MARIADB_USER;
@ -88,7 +88,7 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> { return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
final var unixUserCount = ofNullable(entity.getRelatedHostingAsset()) final var unixUserCount = ofNullable(entity.getRelatedHostingAsset())
.map(ha -> ha.getSubHostingAssets().stream() .map(ha -> ha.getSubHostingAssets().stream()
.filter(bi -> bi.getType() == DOMAIN_EMAIL_SETUP) .filter(bi -> bi.getType() == DOMAIN_EMAIL_MAILBOX_SETUP)
hsh-michaelhoennig marked this conversation as resolved

überprüfen: was soll das bedeuten?

überprüfen: was soll das bedeuten?

Das ist die Validierung der Multi-Optionen. Also ganz richtig so.

Das ist die Validierung der Multi-Optionen. Also ganz richtig so.
.flatMap(domainEMailSetup -> domainEMailSetup.getSubHostingAssets().stream() .flatMap(domainEMailSetup -> domainEMailSetup.getSubHostingAssets().stream()
.filter(subAsset -> subAsset.getType()==EMAIL_ADDRESS)) .filter(subAsset -> subAsset.getType()==EMAIL_ADDRESS))
.count()) .count())

View File

@ -50,6 +50,7 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.DELETE;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.INSERT; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.INSERT;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.SELECT; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.SELECT;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.UPDATE; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.UPDATE;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.ADMIN; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.ADMIN;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.AGENT; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.AGENT;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.GUEST; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.GUEST;
@ -204,11 +205,12 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Properti
.switchOnColumn("type", .switchOnColumn("type",
inCaseOf("DOMAIN_SETUP", then -> { inCaseOf("DOMAIN_SETUP", then -> {
then.toRole(GLOBAL, GUEST).grantPermission(INSERT); then.toRole(GLOBAL, GUEST).grantPermission(INSERT);
then.toRole(GLOBAL, ADMIN).grantPermission(SELECT); // TODO.spec: replace by a proper solution
}) })
) )
.createRole(OWNER, (with) -> { .createRole(OWNER, (with) -> {
with.owningUser(CREATOR);
with.incomingSuperRole(GLOBAL, ADMIN).unassumed(); // TODO.spec: replace by a better solution
with.incomingSuperRole("bookingItem", ADMIN); with.incomingSuperRole("bookingItem", ADMIN);
with.incomingSuperRole("parentAsset", ADMIN); with.incomingSuperRole("parentAsset", ADMIN);
with.permission(DELETE); with.permission(DELETE);

View File

@ -1,33 +1,204 @@
package net.hostsharing.hsadminng.hs.hosting.asset; package net.hostsharing.hsadminng.hs.hosting.asset;
import lombok.AllArgsConstructor;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.booking.item.Node;
public enum HsHostingAssetType { import javax.naming.NamingException;
CLOUD_SERVER, // named e.g. vm1234 import java.io.IOException;
MANAGED_SERVER, // named e.g. vm1234 import java.nio.file.Files;
MANAGED_WEBSPACE(MANAGED_SERVER), // named eg. xyz00 import java.nio.file.Path;
UNIX_USER(MANAGED_WEBSPACE), // named e.g. xyz00-abc import java.nio.file.StandardOpenOption;
DOMAIN_SETUP, // named e.g. example.org import java.util.Collection;
DOMAIN_DNS_SETUP(DOMAIN_SETUP), // named e.g. example.org import java.util.List;
DOMAIN_HTTP_SETUP(DOMAIN_SETUP), // named e.g. example.org import java.util.Set;
DOMAIN_EMAIL_SETUP(DOMAIN_SETUP), // named e.g. example.org import java.util.function.Function;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;
import static net.hostsharing.hsadminng.hs.hosting.asset.EntityTypeRelation.*;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.RelationPolicy.OPTIONAL;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.RelationPolicy.REQUIRED;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.RelationType.ASSIGNED_TO_ASSET;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.RelationType.BOOKING_ITEM;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.RelationType.PARENT_ASSET;
public enum HsHostingAssetType implements Node {
SAME_TYPE, // pseudo-type for recursive references
CLOUD_SERVER( // named e.g. vm1234
inGroup("Server"),
requires(HsBookingItemType.CLOUD_SERVER)),
MANAGED_SERVER( // named e.g. vm1234
inGroup("Server"),
requires(HsBookingItemType.MANAGED_SERVER)),
MANAGED_WEBSPACE( // named eg. xyz00
inGroup("Webspace"),
requires(HsBookingItemType.MANAGED_WEBSPACE),
optionalParent(MANAGED_SERVER)),
UNIX_USER( // named e.g. xyz00-abc
inGroup("Webspace"),
requiredParent(MANAGED_WEBSPACE)),
DOMAIN_SETUP( // named e.g. example.org
inGroup("Domain"),
optionalParent(SAME_TYPE)
),
DOMAIN_DNS_SETUP( // named e.g. example.org
inGroup("Domain"),
requiredParent(DOMAIN_SETUP)),
DOMAIN_HTTP_SETUP( // named e.g. example.org
inGroup("Domain"),
requiredParent(DOMAIN_SETUP),
assignedTo(UNIX_USER)),
DOMAIN_EMAIL_SUBMISSION_SETUP( // named e.g. example.org
inGroup("Domain"),
requiredParent(DOMAIN_SETUP),
assignedTo(MANAGED_WEBSPACE)),
DOMAIN_EMAIL_MAILBOX_SETUP( // named e.g. example.org
inGroup("Domain"),
requiredParent(DOMAIN_SETUP),
assignedTo(MANAGED_WEBSPACE)),
// TODO.spec: SECURE_MX // TODO.spec: SECURE_MX
EMAIL_ALIAS(MANAGED_WEBSPACE), // named e.g. xyz00-abc
EMAIL_ADDRESS(DOMAIN_EMAIL_SETUP), // named e.g. sample@example.org
PGSQL_USER(MANAGED_WEBSPACE), // named e.g. xyz00_abc
PGSQL_DATABASE(MANAGED_WEBSPACE), // named e.g. xyz00_abc, TODO.spec: or PGSQL_USER?
MARIADB_USER(MANAGED_WEBSPACE), // named e.g. xyz00_abc
MARIADB_DATABASE(MANAGED_WEBSPACE); // named e.g. xyz00_abc, TODO.spec: or MARIADB_USER?
EMAIL_ALIAS( // named e.g. xyz00-abc
inGroup("Webspace"),
requiredParent(MANAGED_WEBSPACE)),
public final HsHostingAssetType parentAssetType; EMAIL_ADDRESS( // named e.g. sample@example.org
inGroup("Domain"),
requiredParent(DOMAIN_EMAIL_MAILBOX_SETUP)),
HsHostingAssetType(final HsHostingAssetType parentAssetType) { PGSQL_INSTANCE( // TODO.spec: identifier to be specified
this.parentAssetType = parentAssetType; inGroup("PostgreSQL"),
requiredParent(MANAGED_SERVER)),
PGSQL_USER( // named e.g. xyz00_abc
inGroup("PostgreSQL"),
requiredParent(PGSQL_INSTANCE),
assignedTo(MANAGED_WEBSPACE)),
PGSQL_DATABASE( // named e.g. xyz00_abc
inGroup("PostgreSQL"),
requiredParent(MANAGED_WEBSPACE), // TODO.spec: or PGSQL_USER?
assignedTo(PGSQL_INSTANCE)), // TODO.spec: or swapping parent+assignedTo?
MARIADB_INSTANCE( // TODO.spec: identifier to be specified
inGroup("MariaDB"),
requiredParent(MANAGED_SERVER)), // TODO.spec: or MANAGED_WEBSPACE?
MARIADB_USER( // named e.g. xyz00_abc
inGroup("MariaDB"),
requiredParent(MARIADB_INSTANCE),
assignedTo(MANAGED_WEBSPACE)),
MARIADB_DATABASE( // named e.g. xyz00_abc
inGroup("MariaDB"),
requiredParent(MANAGED_WEBSPACE), // TODO.spec: or MARIADB_USER?
assignedTo(MARIADB_INSTANCE)), // TODO.spec: or swapping parent+assignedTo?
IP_NUMBER(
inGroup("Server"),
assignedTo(CLOUD_SERVER),
assignedTo(MANAGED_SERVER),
assignedTo(MANAGED_WEBSPACE)
);
private final String groupName;
private final EntityTypeRelation<?, ?>[] relations;
HsHostingAssetType(
final String groupName,
final EntityTypeRelation<?, ?>... relations
) {
this.groupName = groupName;
this.relations = relations;
} }
HsHostingAssetType() { HsHostingAssetType() {
this(null); this.groupName = null;
this.relations = null;
}
/// just syntactic sugar
private static String inGroup(final String groupName) {
return groupName;
}
// TODO.refa: try to get rid of the following similar methods:
public RelationPolicy bookingItemPolicy() {
return stream(relations)
.filter(r -> r.relationType == BOOKING_ITEM)
.map(r -> r.relationPolicy)
.reduce(HsHostingAssetType::onlyASingleElementExpectedException)
.orElse(RelationPolicy.FORBIDDEN);
}
public HsBookingItemType bookingItemType() {
return stream(relations)
.filter(r -> r.relationType == BOOKING_ITEM)
.map(r -> HsBookingItemType.valueOf(r.relatedType(this).toString()))
.reduce(HsHostingAssetType::onlyASingleElementExpectedException)
.orElse(null);
}
public RelationPolicy parentAssetPolicy() {
return stream(relations)
.filter(r -> r.relationType == PARENT_ASSET)
.map(r -> r.relationPolicy)
.reduce(HsHostingAssetType::onlyASingleElementExpectedException)
.orElse(RelationPolicy.FORBIDDEN);
}
public HsHostingAssetType parentAssetType() {
return stream(relations)
.filter(r -> r.relationType == PARENT_ASSET)
.map(r -> HsHostingAssetType.valueOf(r.relatedType(this).toString()))
.reduce(HsHostingAssetType::onlyASingleElementExpectedException)
.orElse(null);
}
public RelationPolicy assignedToAssetPolicy() {
return stream(relations)
.filter(r -> r.relationType == ASSIGNED_TO_ASSET)
.map(r -> r.relationPolicy)
.reduce(HsHostingAssetType::onlyASingleElementExpectedException)
.orElse(RelationPolicy.FORBIDDEN);
}
public HsHostingAssetType assignedToAssetType() {
return stream(relations)
.filter(r -> r.relationType == ASSIGNED_TO_ASSET)
.map(r -> HsHostingAssetType.valueOf(r.relatedType(this).toString()))
.reduce(HsHostingAssetType::onlyASingleElementExpectedException)
.orElse(null);
}
private static <X> X onlyASingleElementExpectedException(Object a, Object b) {
throw new IllegalStateException("Only a single element expected to match criteria.");
}
@Override
public List<String> edges() {
return stream(relations)
.map(r -> nodeName() + r.edge + r.relatedType(this).nodeName())
.toList();
}
@Override
public String nodeName() {
return "HA_" + name();
} }
public static <T extends Enum<?>> HsHostingAssetType of(final T value) { public static <T extends Enum<?>> HsHostingAssetType of(final T value) {
@ -37,4 +208,148 @@ public enum HsHostingAssetType {
static String asString(final HsHostingAssetType type) { static String asString(final HsHostingAssetType type) {
return type == null ? null : type.name(); return type == null ? null : type.name();
} }
private static String renderAsPlantUML(final String caption, final Set<String> includedHostingGroups) {
final String bookingNodes = stream(HsBookingItemType.values())
.map(t -> " entity " + t.nodeName())
.collect(joining("\n"));
final String hostingGroups = includedHostingGroups.stream().sorted()
.map(HsHostingAssetType::generateGroup)
.collect(joining("\n"));
final String hostingAssetNodes = stream(HsHostingAssetType.values())
.filter(t -> t.isInGroups(includedHostingGroups))
.map(t -> "entity " + t.nodeName())
.collect(joining("\n"));
final String bookingItemEdges = stream(HsBookingItemType.values())
.map(HsBookingItemType::edges)
.flatMap(Collection::stream)
.collect(joining("\n"));
final String hostingAssetEdges = stream(HsHostingAssetType.values())
.filter(t -> t.isInGroups(includedHostingGroups))
.map(HsHostingAssetType::edges)
.flatMap(Collection::stream)
.collect(joining("\n"));
return """
### %{caption}
```plantuml
@startuml
left to right direction
package Booking #feb28c {
%{bookingNodes}
}
package Hosting #feb28c{
%{hostingGroups}
}
%{bookingItemEdges}
%{hostingAssetEdges}
package Legend #white {
SUB_ENTITY1 *--> REQUIRED_PARENT_ENTITY
SUB_ENTITY2 *..> OPTIONAL_PARENT_ENTITY
ASSIGNED_ENTITY1 o--> REQUIRED_ASSIGNED_TO_ENTITY1
ASSIGNED_ENTITY2 o..> OPTIONAL_ASSIGNED_TO_ENTITY2
}
Booking -down[hidden]->Legend
```
"""
.replace("%{caption}", caption)
.replace("%{bookingNodes}", bookingNodes)
.replace("%{hostingGroups}", hostingGroups)
.replace("%{hostingAssetNodeStyles}", hostingAssetNodes)
.replace("%{bookingItemEdges}", bookingItemEdges)
.replace("%{hostingAssetEdges}", hostingAssetEdges);
}
private boolean isInGroups(final Set<String> assetGroups) {
return groupName != null && assetGroups.contains(groupName);
}
private static String generateGroup(final String group) {
return " package " + group + " #99bcdb {\n"
+ stream(HsHostingAssetType.values())
.filter(t -> group.equals(t.groupName))
.map(t -> " entity " + t.nodeName())
.collect(joining("\n"))
+ "\n }\n";
}
static String renderAsEmbeddedPlantUml() {
final var markdown = new StringBuilder("""
## HostingAsset Type Structure
""");
// rendering all types in a single diagram is currently ignored
renderAsPlantUML("Domain", stream(HsHostingAssetType.values())
.filter(t -> t.groupName != null)
.map(t -> t.groupName)
.collect(toSet()));
markdown.append(renderAsPlantUML("Domain", Set.of("Domain", "Webspace", "Server")))
.append(renderAsPlantUML("MariaDB", Set.of("MariaDB", "Webspace", "Server")))
.append(renderAsPlantUML("PostgreSQL", Set.of("PostgreSQL", "Webspace", "Server")));
markdown.append("""
This code generated was by %{this}.main, do not amend manually.
"""
.replace("%{this}", HsHostingAssetType.class.getSimpleName()));
return markdown.toString();
}
public static void main(final String[] args) throws IOException, NamingException {
Files.writeString(
Path.of("doc/hs-hosting-asset-type-structure.md"),
renderAsEmbeddedPlantUml(),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
}
public enum RelationPolicy {
FORBIDDEN, OPTIONAL, REQUIRED
}
public enum RelationType {
BOOKING_ITEM,
PARENT_ASSET,
ASSIGNED_TO_ASSET
}
}
@AllArgsConstructor
class EntityTypeRelation<E, T extends Node> {
final HsHostingAssetType.RelationPolicy relationPolicy;
final HsHostingAssetType.RelationType relationType;
final Function<HsHostingAssetEntity, E> getter;
private final T relatedType;
final String edge;
public T relatedType(final HsHostingAssetType referringType) {
//noinspection unchecked
return relatedType == HsHostingAssetType.SAME_TYPE ? (T) referringType : relatedType;
}
static EntityTypeRelation<HsBookingItemEntity, HsBookingItemType> requires(final HsBookingItemType bookingItemType) {
return new EntityTypeRelation<>(REQUIRED, BOOKING_ITEM, HsHostingAssetEntity::getBookingItem, bookingItemType, " *==> ");
}
static EntityTypeRelation<HsHostingAssetEntity, HsHostingAssetType> optionalParent(final HsHostingAssetType hostingAssetType) {
return new EntityTypeRelation<>(OPTIONAL, PARENT_ASSET, HsHostingAssetEntity::getParentAsset, hostingAssetType, " o..> ");
}
static EntityTypeRelation<HsHostingAssetEntity, HsHostingAssetType> requiredParent(final HsHostingAssetType hostingAssetType) {
return new EntityTypeRelation<>(REQUIRED, PARENT_ASSET, HsHostingAssetEntity::getParentAsset, hostingAssetType, " *==> ");
}
static EntityTypeRelation<HsHostingAssetEntity, HsHostingAssetType> assignedTo(final HsHostingAssetType hostingAssetType) {
return new EntityTypeRelation<>(REQUIRED, ASSIGNED_TO_ASSET, HsHostingAssetEntity::getAssignedToAsset, hostingAssetType, " o..> ");
}
} }

View File

@ -1,17 +1,16 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validators; package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER;
class HsCloudServerHostingAssetValidator extends HsHostingAssetEntityValidator { class HsCloudServerHostingAssetValidator extends HsHostingAssetEntityValidator {
HsCloudServerHostingAssetValidator() { HsCloudServerHostingAssetValidator() {
super( super(
BookingItem.mustBeOfType(HsBookingItemType.CLOUD_SERVER), CLOUD_SERVER,
ParentAsset.mustBeNull(),
AssignedToAsset.mustBeNull(),
AlarmContact.isOptional(), AlarmContact.isOptional(),
NO_EXTRA_PROPERTIES); NO_EXTRA_PROPERTIES);
} }

View File

@ -2,7 +2,6 @@ package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import net.hostsharing.hsadminng.system.SystemProcess; import net.hostsharing.hsadminng.system.SystemProcess;
import java.util.List; import java.util.List;
@ -10,6 +9,7 @@ import java.util.regex.Pattern;
import static java.util.Arrays.stream; import static java.util.Arrays.stream;
import static java.util.Optional.ofNullable; import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_DNS_SETUP;
import static net.hostsharing.hsadminng.hs.validation.ArrayProperty.arrayOf; 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.BooleanProperty.booleanProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty; import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
@ -30,11 +30,11 @@ class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidato
static final String RR_REGEX_IN_TTL = static final String RR_REGEX_IN_TTL =
RR_REGEX_NAME + RR_REGEX_IN + RR_REGEX_TTL + RR_RECORD_TYPE + RR_RECORD_DATA + RR_COMMENT; RR_REGEX_NAME + RR_REGEX_IN + RR_REGEX_TTL + RR_RECORD_TYPE + RR_RECORD_DATA + RR_COMMENT;
public static final String IDENTIFIER_SUFFIX = "|DNS";
HsDomainDnsSetupHostingAssetValidator() { HsDomainDnsSetupHostingAssetValidator() {
super( BookingItem.mustBeNull(), super(
ParentAsset.mustBeOfType(HsHostingAssetType.DOMAIN_SETUP), DOMAIN_DNS_SETUP,
AssignedToAsset.mustBeNull(),
AlarmContact.isOptional(), AlarmContact.isOptional(),
integerProperty("TTL").min(0).withDefault(21600), integerProperty("TTL").min(0).withDefault(21600),
@ -60,14 +60,14 @@ class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidato
@Override @Override
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) { protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
return Pattern.compile("^" + assetEntity.getParentAsset().getIdentifier() + "$"); return Pattern.compile("^" + assetEntity.getParentAsset().getIdentifier() + Pattern.quote(IDENTIFIER_SUFFIX) + "$");
} }
@Override @Override
public void preprocessEntity(final HsHostingAssetEntity entity) { public void preprocessEntity(final HsHostingAssetEntity entity) {
super.preprocessEntity(entity); super.preprocessEntity(entity);
if (entity.getIdentifier() == null) { if (entity.getIdentifier() == null) {
ofNullable(entity.getParentAsset()).ifPresent(pa -> entity.setIdentifier(pa.getIdentifier())); ofNullable(entity.getParentAsset()).ifPresent(pa -> entity.setIdentifier(pa.getIdentifier() + IDENTIFIER_SUFFIX));
} }
} }
@ -77,7 +77,7 @@ class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidato
final var result = super.validateContext(assetEntity); final var result = super.validateContext(assetEntity);
// TODO.spec: define which checks should get raised to error level // TODO.spec: define which checks should get raised to error level
final var namedCheckZone = new SystemProcess("named-checkzone", assetEntity.getIdentifier()); final var namedCheckZone = new SystemProcess("named-checkzone", fqdn(assetEntity));
if (namedCheckZone.execute(toZonefileString(assetEntity)) != 0) { if (namedCheckZone.execute(toZonefileString(assetEntity)) != 0) {
// yes, named-checkzone writes error messages to stdout // yes, named-checkzone writes error messages to stdout
stream(namedCheckZone.getStdOut().split("\n")) stream(namedCheckZone.getStdOut().split("\n"))
@ -99,8 +99,12 @@ class HsDomainDnsSetupHostingAssetValidator extends HsHostingAssetEntityValidato
{userRRs} {userRRs}
""" """
.replace("{domain}", assetEntity.getIdentifier()) .replace("{domain}", fqdn(assetEntity))
.replace("{ttl}", getPropertyValue(assetEntity, "TTL")) .replace("{ttl}", getPropertyValue(assetEntity, "TTL"))
.replace("{userRRs}", getPropertyValues(assetEntity, "user-RR") ); .replace("{userRRs}", getPropertyValues(assetEntity, "user-RR") );
} }
private String fqdn(final HsHostingAssetEntity assetEntity) {
return assetEntity.getIdentifier().substring(0, assetEntity.getIdentifier().length()-IDENTIFIER_SUFFIX.length());
}
} }

View File

@ -2,8 +2,11 @@ package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_SETUP;
class HsDomainSetupHostingAssetValidator extends HsHostingAssetEntityValidator { class HsDomainSetupHostingAssetValidator extends HsHostingAssetEntityValidator {
public static final String DOMAIN_NAME_REGEX = "^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,6}"; public static final String DOMAIN_NAME_REGEX = "^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,6}";
@ -11,15 +14,43 @@ class HsDomainSetupHostingAssetValidator extends HsHostingAssetEntityValidator {
private final Pattern identifierPattern; private final Pattern identifierPattern;
HsDomainSetupHostingAssetValidator() { HsDomainSetupHostingAssetValidator() {
super( BookingItem.mustBeNull(), super( DOMAIN_SETUP,
ParentAsset.mustBeNull(),
AssignedToAsset.mustBeNull(),
AlarmContact.isOptional(), AlarmContact.isOptional(),
NO_EXTRA_PROPERTIES); NO_EXTRA_PROPERTIES);
this.identifierPattern = Pattern.compile(DOMAIN_NAME_REGEX); this.identifierPattern = Pattern.compile(DOMAIN_NAME_REGEX);
} }
@Override
public List<String> validateEntity(final HsHostingAssetEntity assetEntity) {
// TODO.impl: for newly created entities, check the permission of setting up a domain
//
// reject, if the domain is any of these:
// hostsharing.com|net|org|coop, // just to be on the safe side
// [^.}+, // top-level-domain
// co.uk, org.uk, gov.uk, ac.uk, sch.uk,
// com.au, net.au, org.au, edu.au, gov.au, asn.au, id.au,
// co.jp, ne.jp, or.jp, ac.jp, go.jp,
// com.cn, net.cn, org.cn, gov.cn, edu.cn, ac.cn,
// com.br, net.br, org.br, gov.br, edu.br, mil.br, art.br,
// co.in, net.in, org.in, gen.in, firm.in, ind.in,
// com.mx, net.mx, org.mx, gob.mx, edu.mx,
// gov.it, edu.it,
// co.nz, net.nz, org.nz, govt.nz, ac.nz, school.nz, geek.nz, kiwi.nz,
// co.kr, ne.kr, or.kr, go.kr, re.kr, pe.kr
//
// allow if
// - user has Admin/Agent-role for all its sub-domains and the direct parent-Domain which are set up at at Hostsharing
// - domain has DNS zone with TXT record approval
// - parent-domain has DNS zone with TXT record approval
// - dom
//
// TXT-Record check:
// new InitialDirContext().getAttributes("dns:_netblocks.google.com", new String[] { "TXT"}).get("TXT").getAll();
return super.validateEntity(assetEntity);
}
@Override @Override
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) { protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
return identifierPattern; return identifierPattern;

View File

@ -15,9 +15,7 @@ class HsEMailAliasHostingAssetValidator extends HsHostingAssetEntityValidator {
public static final int EMAIL_ADDRESS_MAX_LENGTH = 320; // according to RFC 5321 and RFC 5322 public static final int EMAIL_ADDRESS_MAX_LENGTH = 320; // according to RFC 5321 and RFC 5322
HsEMailAliasHostingAssetValidator() { HsEMailAliasHostingAssetValidator() {
super( BookingItem.mustBeNull(), super( HsHostingAssetType.EMAIL_ALIAS,
ParentAsset.mustBeOfType(HsHostingAssetType.MANAGED_WEBSPACE),
AssignedToAsset.mustBeNull(),
AlarmContact.isOptional(), AlarmContact.isOptional(),
arrayOf( arrayOf(

View File

@ -9,7 +9,6 @@ import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator; import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import net.hostsharing.hsadminng.hs.validation.ValidatableProperty; import net.hostsharing.hsadminng.hs.validation.ValidatableProperty;
import jakarta.validation.constraints.NotNull;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -26,21 +25,31 @@ public abstract class HsHostingAssetEntityValidator extends HsEntityValidator<Hs
static final ValidatableProperty<?, ?>[] NO_EXTRA_PROPERTIES = new ValidatableProperty<?, ?>[0]; static final ValidatableProperty<?, ?>[] NO_EXTRA_PROPERTIES = new ValidatableProperty<?, ?>[0];
private final HsHostingAssetEntityValidator.BookingItem bookingItemValidation; private final ReferenceValidator<HsBookingItemEntity, HsBookingItemType> bookingItemReferenceValidation;
private final HsHostingAssetEntityValidator.ParentAsset parentAssetValidation; private final ReferenceValidator<HsHostingAssetEntity, HsHostingAssetType> parentAssetReferenceValidation;
private final HsHostingAssetEntityValidator.AssignedToAsset assignedToAssetValidation; private final ReferenceValidator<HsHostingAssetEntity, HsHostingAssetType> assignedToAssetReferenceValidation;
private final HsHostingAssetEntityValidator.AlarmContact alarmContactValidation; private final HsHostingAssetEntityValidator.AlarmContact alarmContactValidation;
HsHostingAssetEntityValidator( HsHostingAssetEntityValidator(
@NotNull final BookingItem bookingItemValidation, final HsHostingAssetType assetType,
@NotNull final ParentAsset parentAssetValidation, final AlarmContact alarmContactValidation,
@NotNull final AssignedToAsset assignedToAssetValidation,
@NotNull final AlarmContact alarmContactValidation,
final ValidatableProperty<?, ?>... properties) { final ValidatableProperty<?, ?>... properties) {
super(properties); super(properties);
this.bookingItemValidation = bookingItemValidation; this.bookingItemReferenceValidation = new ReferenceValidator<>(
this.parentAssetValidation = parentAssetValidation; assetType.bookingItemPolicy(),
this.assignedToAssetValidation = assignedToAssetValidation; assetType.bookingItemType(),
HsHostingAssetEntity::getBookingItem,
HsBookingItemEntity::getType);
this.parentAssetReferenceValidation = new ReferenceValidator<>(
assetType.parentAssetPolicy(),
assetType.parentAssetType(),
HsHostingAssetEntity::getParentAsset,
HsHostingAssetEntity::getType);
this.assignedToAssetReferenceValidation = new ReferenceValidator<>(
assetType.assignedToAssetPolicy(),
assetType.assignedToAssetType(),
HsHostingAssetEntity::getAssignedToAsset,
HsHostingAssetEntity::getType);
this.alarmContactValidation = alarmContactValidation; this.alarmContactValidation = alarmContactValidation;
} }
@ -63,11 +72,11 @@ public abstract class HsHostingAssetEntityValidator extends HsEntityValidator<Hs
private List<String> validateEntityReferencesAndProperties(final HsHostingAssetEntity assetEntity) { private List<String> validateEntityReferencesAndProperties(final HsHostingAssetEntity assetEntity) {
return Stream.of( return Stream.of(
validateReferencedEntity(assetEntity, "bookingItem", bookingItemValidation::validate), validateReferencedEntity(assetEntity, "bookingItem", bookingItemReferenceValidation::validate),
validateReferencedEntity(assetEntity, "parentAsset", parentAssetValidation::validate), validateReferencedEntity(assetEntity, "parentAsset", parentAssetReferenceValidation::validate),
validateReferencedEntity(assetEntity, "assignedToAsset", assignedToAssetValidation::validate), validateReferencedEntity(assetEntity, "assignedToAsset", assignedToAssetReferenceValidation::validate),
validateReferencedEntity(assetEntity, "alarmContact", alarmContactValidation::validate), validateReferencedEntity(assetEntity, "alarmContact", alarmContactValidation::validate),
validateProperties(assetEntity)) validateProperties(assetEntity))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.flatMap(List::stream) .flatMap(List::stream)
.filter(Objects::nonNull) .filter(Objects::nonNull)
@ -87,25 +96,28 @@ public abstract class HsHostingAssetEntityValidator extends HsEntityValidator<Hs
private static List<String> optionallyValidate(final HsHostingAssetEntity assetEntity) { private static List<String> optionallyValidate(final HsHostingAssetEntity assetEntity) {
return assetEntity != null return assetEntity != null
? enrich(prefix(assetEntity.toShortString(), "parentAsset"), ? enrich(
HsHostingAssetEntityValidatorRegistry.forType(assetEntity.getType()).validateContext(assetEntity)) prefix(assetEntity.toShortString(), "parentAsset"),
HsHostingAssetEntityValidatorRegistry.forType(assetEntity.getType()).validateContext(assetEntity))
: emptyList(); : emptyList();
} }
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) { private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {
return bookingItem != null return bookingItem != null
? enrich(prefix(bookingItem.toShortString(), "bookingItem"), ? enrich(
HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validateContext(bookingItem)) prefix(bookingItem.toShortString(), "bookingItem"),
HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validateContext(bookingItem))
: emptyList(); : emptyList();
} }
protected List<String> validateAgainstSubEntities(final HsHostingAssetEntity assetEntity) { protected List<String> validateAgainstSubEntities(final HsHostingAssetEntity assetEntity) {
return enrich(prefix(assetEntity.toShortString(), "config"), return enrich(
prefix(assetEntity.toShortString(), "config"),
stream(propertyValidators) stream(propertyValidators)
.filter(ValidatableProperty::isTotalsValidator) .filter(ValidatableProperty::isTotalsValidator)
.map(prop -> validateMaxTotalValue(assetEntity, prop)) .map(prop -> validateMaxTotalValue(assetEntity, prop))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.toList()); .toList());
} }
// TODO.test: check, if there are any hosting assets which need this validation at all // TODO.test: check, if there are any hosting assets which need this validation at all
@ -130,114 +142,79 @@ public abstract class HsHostingAssetEntityValidator extends HsEntityValidator<Hs
final var expectedIdentifierPattern = identifierPattern(assetEntity); final var expectedIdentifierPattern = identifierPattern(assetEntity);
if (assetEntity.getIdentifier() == null || if (assetEntity.getIdentifier() == null ||
!expectedIdentifierPattern.matcher(assetEntity.getIdentifier()).matches()) { !expectedIdentifierPattern.matcher(assetEntity.getIdentifier()).matches()) {
return List.of("'identifier' expected to match '"+expectedIdentifierPattern+"', but is '" + assetEntity.getIdentifier() + "'"); return List.of(
"'identifier' expected to match '" + expectedIdentifierPattern + "', but is '" + assetEntity.getIdentifier()
+ "'");
} }
return Collections.emptyList(); return Collections.emptyList();
} }
protected abstract Pattern identifierPattern(HsHostingAssetEntity assetEntity); protected abstract Pattern identifierPattern(HsHostingAssetEntity assetEntity);
static abstract class ReferenceValidator<S, T> { static class ReferenceValidator<S, T> {
private final Policy policy; private final HsHostingAssetType.RelationPolicy policy;
private final T subEntityType; private final T referencedEntityType;
private final Function<HsHostingAssetEntity, S> subEntityGetter; private final Function<HsHostingAssetEntity, S> referencedEntityGetter;
private final Function<S,T> subEntityTypeGetter; private final Function<S, T> referencedEntityTypeGetter;
public ReferenceValidator( public ReferenceValidator(
final Policy policy, final HsHostingAssetType.RelationPolicy policy,
final T subEntityType, final T subEntityType,
final Function<HsHostingAssetEntity, S> subEntityGetter, final Function<HsHostingAssetEntity, S> referencedEntityGetter,
final Function<S, T> subEntityTypeGetter) { final Function<S, T> referencedEntityTypeGetter) {
this.policy = policy; this.policy = policy;
this.subEntityType = subEntityType; this.referencedEntityType = subEntityType;
this.subEntityGetter = subEntityGetter; this.referencedEntityGetter = referencedEntityGetter;
this.subEntityTypeGetter = subEntityTypeGetter; this.referencedEntityTypeGetter = referencedEntityTypeGetter;
} }
public ReferenceValidator( public ReferenceValidator(
final Policy policy, final HsHostingAssetType.RelationPolicy policy,
final Function<HsHostingAssetEntity, S> subEntityGetter) { final Function<HsHostingAssetEntity, S> referencedEntityGetter) {
this.policy = policy; this.policy = policy;
this.subEntityType = null; this.referencedEntityType = null;
this.subEntityGetter = subEntityGetter; this.referencedEntityGetter = referencedEntityGetter;
this.subEntityTypeGetter = e -> null; this.referencedEntityTypeGetter = e -> null;
}
enum Policy {
OPTIONAL, FORBIDDEN, REQUIRED
} }
List<String> validate(final HsHostingAssetEntity assetEntity, final String referenceFieldName) { List<String> validate(final HsHostingAssetEntity assetEntity, final String referenceFieldName) {
final var subEntity = subEntityGetter.apply(assetEntity); final var actualEntity = referencedEntityGetter.apply(assetEntity);
if (policy == Policy.REQUIRED && subEntity == null) { final var actualEntityType = actualEntity != null ? referencedEntityTypeGetter.apply(actualEntity) : null;
return List.of(referenceFieldName + "' must not be null but is null");
} switch (policy) {
if (policy == Policy.FORBIDDEN && subEntity != null) { case REQUIRED:
return List.of(referenceFieldName + "' must be null but is set to "+ assetEntity.getBookingItem().toShortString()); if (actualEntityType != referencedEntityType) {
} return List.of(actualEntityType == null
final var subItemType = subEntity != null ? subEntityTypeGetter.apply(subEntity) : null; ? referenceFieldName + "' must be of type " + referencedEntityType + " but is null"
if (subEntityType != null && subItemType != subEntityType) { : referenceFieldName + "' must be of type " + referencedEntityType + " but is of type " + actualEntityType);
return List.of(referenceFieldName + "' must be of type " + subEntityType + " but is of type " + subItemType); }
break;
case OPTIONAL:
if (actualEntityType != null && actualEntityType != referencedEntityType) {
return List.of(referenceFieldName + "' must be null or of type " + referencedEntityType + " but is of type "
+ actualEntityType);
}
break;
case FORBIDDEN:
if (actualEntityType != null) {
return List.of(referenceFieldName + "' must be null but is of type " + actualEntityType);
}
break;
} }
return emptyList(); return emptyList();
} }
} }
static class BookingItem extends ReferenceValidator<HsBookingItemEntity, HsBookingItemType> {
BookingItem(final Policy policy, final HsBookingItemType bookingItemType) {
super(policy, bookingItemType, HsHostingAssetEntity::getBookingItem, HsBookingItemEntity::getType);
}
static BookingItem mustBeNull() {
return new BookingItem(Policy.FORBIDDEN, null);
}
static BookingItem mustBeOfType(final HsBookingItemType hsBookingItemType) {
return new BookingItem(Policy.REQUIRED, hsBookingItemType);
}
}
static class ParentAsset extends ReferenceValidator<HsHostingAssetEntity, HsHostingAssetType> {
ParentAsset(final ReferenceValidator.Policy policy, final HsHostingAssetType parentAssetType) {
super(policy, parentAssetType, HsHostingAssetEntity::getParentAsset, HsHostingAssetEntity::getType);
}
static ParentAsset mustBeNull() {
return new ParentAsset(Policy.FORBIDDEN, null);
}
static ParentAsset mustBeOfType(final HsHostingAssetType hostingAssetType) {
return new ParentAsset(Policy.REQUIRED, hostingAssetType);
}
static ParentAsset mustBeNullOrOfType(final HsHostingAssetType hostingAssetType) {
return new ParentAsset(Policy.OPTIONAL, hostingAssetType);
}
}
static class AssignedToAsset extends ReferenceValidator<HsHostingAssetEntity, HsHostingAssetType> {
AssignedToAsset(final ReferenceValidator.Policy policy, final HsHostingAssetType assignedToAssetType) {
super(policy, assignedToAssetType, HsHostingAssetEntity::getAssignedToAsset, HsHostingAssetEntity::getType);
}
static AssignedToAsset mustBeNull() {
return new AssignedToAsset(Policy.FORBIDDEN, null);
}
}
static class AlarmContact extends ReferenceValidator<HsOfficeContactEntity, Enum<?>> { static class AlarmContact extends ReferenceValidator<HsOfficeContactEntity, Enum<?>> {
AlarmContact(final ReferenceValidator.Policy policy) { AlarmContact(final HsHostingAssetType.RelationPolicy policy) {
super(policy, HsHostingAssetEntity::getAlarmContact); super(policy, HsHostingAssetEntity::getAlarmContact);
} }
static AlarmContact isOptional() { static AlarmContact isOptional() {
return new AlarmContact(Policy.OPTIONAL); return new AlarmContact(HsHostingAssetType.RelationPolicy.OPTIONAL);
} }
} }

View File

@ -1,10 +1,10 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validators; package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty; import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty;
import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty; import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty;
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty; import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
@ -13,9 +13,7 @@ class HsManagedServerHostingAssetValidator extends HsHostingAssetEntityValidator
public HsManagedServerHostingAssetValidator() { public HsManagedServerHostingAssetValidator() {
super( super(
BookingItem.mustBeOfType(HsBookingItemType.MANAGED_SERVER), MANAGED_SERVER,
ParentAsset.mustBeNull(), // until we introduce a hosting asset for 'HOST'
AssignedToAsset.mustBeNull(),
AlarmContact.isOptional(), // hostmaster alert address is implicitly added AlarmContact.isOptional(), // hostmaster alert address is implicitly added
// monitoring // monitoring

View File

@ -1,16 +1,15 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validators; package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
class HsManagedWebspaceHostingAssetValidator extends HsHostingAssetEntityValidator { class HsManagedWebspaceHostingAssetValidator extends HsHostingAssetEntityValidator {
public HsManagedWebspaceHostingAssetValidator() { public HsManagedWebspaceHostingAssetValidator() {
super(BookingItem.mustBeOfType(HsBookingItemType.MANAGED_WEBSPACE), super(
ParentAsset.mustBeOfType(HsHostingAssetType.MANAGED_SERVER), // the (shared or private) ManagedServer MANAGED_WEBSPACE,
AssignedToAsset.mustBeNull(),
AlarmContact.isOptional(), // hostmaster alert address is implicitly added AlarmContact.isOptional(), // hostmaster alert address is implicitly added
NO_EXTRA_PROPERTIES); NO_EXTRA_PROPERTIES);
} }

View File

@ -17,9 +17,8 @@ class HsUnixUserHostingAssetValidator extends HsHostingAssetEntityValidator {
private static final int DASH_LENGTH = "-".length(); private static final int DASH_LENGTH = "-".length();
HsUnixUserHostingAssetValidator() { HsUnixUserHostingAssetValidator() {
super( BookingItem.mustBeNull(), super(
ParentAsset.mustBeOfType(HsHostingAssetType.MANAGED_WEBSPACE), HsHostingAssetType.UNIX_USER,
AssignedToAsset.mustBeNull(),
AlarmContact.isOptional(), AlarmContact.isOptional(),
integerProperty("SSD hard quota").unit("GB").maxFrom("SSD").optional(), integerProperty("SSD hard quota").unit("GB").maxFrom("SSD").optional(),

View File

@ -1,6 +1,7 @@
package net.hostsharing.hsadminng.hs.validation; package net.hostsharing.hsadminng.hs.validation;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
@ -18,7 +19,7 @@ public abstract class HsEntityValidator<E extends PropertiesProvider> {
public final ValidatableProperty<?, ?>[] propertyValidators; public final ValidatableProperty<?, ?>[] propertyValidators;
public HsEntityValidator(final ValidatableProperty<?, ?>... validators) { public <T extends Enum <T>> HsEntityValidator(final ValidatableProperty<?, ?>... validators) {
propertyValidators = validators; propertyValidators = validators;
stream(propertyValidators).forEach(p -> p.deferredInit(propertyValidators)); stream(propertyValidators).forEach(p -> p.deferredInit(propertyValidators));
} }

View File

@ -149,7 +149,7 @@ create or replace function cleanIdentifier(rawIdentifier varchar)
declare declare
cleanIdentifier varchar; cleanIdentifier varchar;
begin begin
cleanIdentifier := regexp_replace(rawIdentifier, '[^A-Za-z0-9\-._]+', '', 'g'); cleanIdentifier := regexp_replace(rawIdentifier, '[^A-Za-z0-9\-._|]+', '', 'g');
return cleanIdentifier; return cleanIdentifier;
end; $$; end; $$;

View File

@ -36,9 +36,9 @@ subgraph asset["`**asset**`"]
style asset:permissions fill:#dd4901,stroke:white style asset:permissions fill:#dd4901,stroke:white
perm:asset:INSERT{{asset:INSERT}} perm:asset:INSERT{{asset:INSERT}}
perm:asset:SELECT{{asset:SELECT}}
perm:asset:DELETE{{asset:DELETE}} perm:asset:DELETE{{asset:DELETE}}
perm:asset:UPDATE{{asset:UPDATE}} perm:asset:UPDATE{{asset:UPDATE}}
perm:asset:SELECT{{asset:SELECT}}
end end
end end
@ -80,6 +80,9 @@ subgraph parentAsset["`**parentAsset**`"]
end end
end end
%% granting roles to users
user:creator ==> role:asset:OWNER
%% granting roles to roles %% granting roles to roles
role:bookingItem:OWNER -.-> role:bookingItem:ADMIN role:bookingItem:OWNER -.-> role:bookingItem:ADMIN
role:bookingItem:ADMIN -.-> role:bookingItem:AGENT role:bookingItem:ADMIN -.-> role:bookingItem:AGENT
@ -87,6 +90,7 @@ role:bookingItem:AGENT -.-> role:bookingItem:TENANT
role:global:ADMIN -.-> role:alarmContact:OWNER role:global:ADMIN -.-> role:alarmContact:OWNER
role:alarmContact:OWNER -.-> role:alarmContact:ADMIN role:alarmContact:OWNER -.-> role:alarmContact:ADMIN
role:alarmContact:ADMIN -.-> role:alarmContact:REFERRER role:alarmContact:ADMIN -.-> role:alarmContact:REFERRER
role:global:ADMIN ==>|XX| role:asset:OWNER
role:bookingItem:ADMIN ==> role:asset:OWNER role:bookingItem:ADMIN ==> role:asset:OWNER
role:parentAsset:ADMIN ==> role:asset:OWNER role:parentAsset:ADMIN ==> role:asset:OWNER
role:asset:OWNER ==> role:asset:ADMIN role:asset:OWNER ==> role:asset:ADMIN
@ -104,7 +108,6 @@ role:alarmContact:ADMIN ==> role:asset:TENANT
role:global:ADMIN ==> perm:asset:INSERT role:global:ADMIN ==> perm:asset:INSERT
role:parentAsset:ADMIN ==> perm:asset:INSERT role:parentAsset:ADMIN ==> perm:asset:INSERT
role:global:GUEST ==> perm:asset:INSERT role:global:GUEST ==> perm:asset:INSERT
role:global:ADMIN ==> perm:asset:SELECT
role:asset:OWNER ==> perm:asset:DELETE role:asset:OWNER ==> perm:asset:DELETE
role:asset:ADMIN ==> perm:asset:UPDATE role:asset:ADMIN ==> perm:asset:UPDATE
role:asset:TENANT ==> perm:asset:SELECT role:asset:TENANT ==> perm:asset:SELECT

View File

@ -50,8 +50,10 @@ begin
hsHostingAssetOWNER(NEW), hsHostingAssetOWNER(NEW),
permissions => array['DELETE'], permissions => array['DELETE'],
incomingSuperRoles => array[ incomingSuperRoles => array[
globalADMIN(unassumed()),
hsBookingItemADMIN(newBookingItem), hsBookingItemADMIN(newBookingItem),
hsHostingAssetADMIN(newParentAsset)] hsHostingAssetADMIN(newParentAsset)],
userUuids => array[currentUserUuid()]
); );
perform createRoleWithGrants( perform createRoleWithGrants(
@ -85,10 +87,6 @@ begin
IF NEW.type = 'DOMAIN_SETUP' THEN IF NEW.type = 'DOMAIN_SETUP' THEN
END IF; END IF;
call grantPermissionToRole(createPermission(NEW.uuid, 'SELECT'), globalAdmin());
call leaveTriggerForObjectUuid(NEW.uuid); call leaveTriggerForObjectUuid(NEW.uuid);
end; $$; end; $$;

View File

@ -71,15 +71,15 @@ begin
defaultPrefix := relatedDebitor.defaultPrefix; defaultPrefix := relatedDebitor.defaultPrefix;
insert into hs_hosting_asset insert into hs_hosting_asset
(uuid, bookingitemuuid, type, parentAssetUuid, assignedToAssetUuid, identifier, caption, config) (uuid, bookingitemuuid, type, parentAssetUuid, assignedToAssetUuid, identifier, caption, config)
values (managedServerUuid, relatedManagedServerBookingItem.uuid, 'MANAGED_SERVER', null, null, 'vm10' || debitorNumberSuffix, 'some ManagedServer', '{ "monit_max_cpu_usage": 90, "monit_max_ram_usage": 80, "monit_max_ssd_usage": 70 }'::jsonb), values (managedServerUuid, relatedManagedServerBookingItem.uuid, 'MANAGED_SERVER', null, null, 'vm10' || debitorNumberSuffix, 'some ManagedServer', '{ "monit_max_cpu_usage": 90, "monit_max_ram_usage": 80, "monit_max_ssd_usage": 70 }'::jsonb),
(uuid_generate_v4(), relatedCloudServerBookingItem.uuid, 'CLOUD_SERVER', null, null, 'vm20' || debitorNumberSuffix, 'another CloudServer', '{}'::jsonb), (uuid_generate_v4(), relatedCloudServerBookingItem.uuid, 'CLOUD_SERVER', null, null, 'vm20' || debitorNumberSuffix, 'another CloudServer', '{}'::jsonb),
(managedWebspaceUuid, relatedManagedWebspaceBookingItem.uuid, 'MANAGED_WEBSPACE', managedServerUuid, null, defaultPrefix || '01', 'some Webspace', '{}'::jsonb), (managedWebspaceUuid, relatedManagedWebspaceBookingItem.uuid, 'MANAGED_WEBSPACE', managedServerUuid, null, defaultPrefix || '01', 'some Webspace', '{}'::jsonb),
(uuid_generate_v4(), null, 'EMAIL_ALIAS', managedWebspaceUuid, null, defaultPrefix || '01-web', 'some E-Mail-Alias', '{ "target": [ "office@example.org", "archive@example.com" ] }'::jsonb), (uuid_generate_v4(), null, 'EMAIL_ALIAS', managedWebspaceUuid, null, defaultPrefix || '01-web', 'some E-Mail-Alias', '{ "target": [ "office@example.org", "archive@example.com" ] }'::jsonb),
(webUnixUserUuid, null, 'UNIX_USER', managedWebspaceUuid, null, defaultPrefix || '01-web', 'some UnixUser for Website', '{ "SSD-soft-quota": "128", "SSD-hard-quota": "256", "HDD-soft-quota": "512", "HDD-hard-quota": "1024"}'::jsonb), (webUnixUserUuid, null, 'UNIX_USER', managedWebspaceUuid, null, defaultPrefix || '01-web', 'some UnixUser for Website', '{ "SSD-soft-quota": "128", "SSD-hard-quota": "256", "HDD-soft-quota": "512", "HDD-hard-quota": "1024"}'::jsonb),
(domainSetupUuid, null, 'DOMAIN_SETUP', null, null, defaultPrefix || '.example.org', 'some Domain-Setup', '{}'::jsonb), (domainSetupUuid, null, 'DOMAIN_SETUP', null, null, defaultPrefix || '.example.org', 'some Domain-Setup', '{}'::jsonb),
(uuid_generate_v4(), null, 'DOMAIN_DNS_SETUP', domainSetupUuid, null, defaultPrefix || '.example.org', 'some Domain-DNS-Setup', '{}'::jsonb), (uuid_generate_v4(), null, 'DOMAIN_DNS_SETUP', domainSetupUuid, null, defaultPrefix || '.example.org|DNS', 'some Domain-DNS-Setup', '{}'::jsonb),
(uuid_generate_v4(), null, 'DOMAIN_HTTP_SETUP', domainSetupUuid, webUnixUserUuid, defaultPrefix || '.example.org', 'some Domain-HTTP-Setup', '{ "option-htdocsfallback": true, "use-fcgiphpbin": "/usr/lib/cgi-bin/php", "validsubdomainnames": "*"}'::jsonb); (uuid_generate_v4(), null, 'DOMAIN_HTTP_SETUP', domainSetupUuid, webUnixUserUuid, defaultPrefix || '.example.org|HTTP', 'some Domain-HTTP-Setup', '{ "option-htdocsfallback": true, "use-fcgiphpbin": "/usr/lib/cgi-bin/php", "validsubdomainnames": "*"}'::jsonb);
end; $$; end; $$;
--// --//

View File

@ -40,8 +40,10 @@ public class ArchitectureTest {
"..test.pac", "..test.pac",
"..test.dom", "..test.dom",
"..context", "..context",
"..hash",
"..generated..", "..generated..",
"..persistence..", "..persistence..",
"..system..",
"..validation..", "..validation..",
"..hs.office.bankaccount", "..hs.office.bankaccount",
"..hs.office.contact", "..hs.office.contact",
@ -110,6 +112,13 @@ public class ArchitectureTest {
.should().onlyDependOnClassesThat() .should().onlyDependOnClassesThat()
.resideOutsideOfPackage(NET_HOSTSHARING_HSADMINNG); .resideOutsideOfPackage(NET_HOSTSHARING_HSADMINNG);
@ArchTest
@SuppressWarnings("unused")
public static final ArchRule hashPackageRule = classes()
.that().resideInAPackage("..hash..")
.should().onlyDependOnClassesThat()
.resideOutsideOfPackage(NET_HOSTSHARING_HSADMINNG);
@ArchTest @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static final ArchRule errorsPackageRule = classes() public static final ArchRule errorsPackageRule = classes()
@ -117,6 +126,13 @@ public class ArchitectureTest {
.should().onlyDependOnClassesThat() .should().onlyDependOnClassesThat()
.resideOutsideOfPackage(NET_HOSTSHARING_HSADMINNG); .resideOutsideOfPackage(NET_HOSTSHARING_HSADMINNG);
@ArchTest
@SuppressWarnings("unused")
public static final ArchRule systemPackageRule = classes()
.that().resideInAPackage("..system..")
.should().onlyDependOnClassesThat()
.resideOutsideOfPackage(NET_HOSTSHARING_HSADMINNG);
@ArchTest @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static final ArchRule testPackagesRule = classes() public static final ArchRule testPackagesRule = classes()

View File

@ -152,7 +152,7 @@ class HsManagedServerBookingItemValidatorUnitTest {
"xyz00_%c%c", "xyz00_%c%c",
2, HsHostingAssetType.MARIADB_DATABASE 2, HsHostingAssetType.MARIADB_DATABASE
), ),
generateDomainEmailSetupsWithEMailAddresses(26, HsHostingAssetType.DOMAIN_EMAIL_SETUP, generateDomainEmailSetupsWithEMailAddresses(26, HsHostingAssetType.DOMAIN_EMAIL_MAILBOX_SETUP,
"%c%c.example.com", "%c%c.example.com",
10, HsHostingAssetType.EMAIL_ADDRESS 10, HsHostingAssetType.EMAIL_ADDRESS
) )

View File

@ -235,6 +235,47 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
assertThat(newUserUuid).isNotNull(); assertThat(newUserUuid).isNotNull();
} }
@Test
void globalAdmin_canAddTopLevelAsset() {
context.define("superuser-alex@hostsharing.net");
final var location = RestAssured // @formatter:off
.given()
.header("current-user", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON)
.body("""
{
"type": "DOMAIN_SETUP",
"identifier": "example.com",
"caption": "some unrelated domain-setup",
"config": {}
}
""")
.port(port)
.when()
.post("http://localhost/api/hs/hosting/assets")
.then().log().all().assertThat()
.statusCode(201)
.contentType(ContentType.JSON)
.body("", lenientlyEquals("""
{
"type": "DOMAIN_SETUP",
"identifier": "example.com",
"caption": "some unrelated domain-setup",
"config": {}
}
"""))
.header("Location", matchesRegex("http://localhost:[1-9][0-9]*/api/hs/hosting/assets/[^/]*"))
.extract().header("Location"); // @formatter:on
// finally, the new asset can be accessed under the generated UUID
final var newWebspace = UUID.fromString(
location.substring(location.lastIndexOf('/') + 1));
assertThat(newWebspace).isNotNull();
toCleanup(HsHostingAssetEntity.class, newWebspace);
}
@Test @Test
void propertyValidationsArePerformend_whenAddingAsset() { void propertyValidationsArePerformend_whenAddingAsset() {

View File

@ -131,9 +131,10 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
initialGrantNames, initialGrantNames,
// global-admin // global-admin
"{ grant perm:hs_hosting_asset#fir00:SELECT to role:global#global:ADMIN by system and assume }", // workaround "{ grant role:hs_hosting_asset#fir00:OWNER to role:global#global:ADMIN by system }", // workaround
// owner // owner
"{ grant role:hs_hosting_asset#fir00:OWNER to user:superuser-alex@hostsharing.net by hs_hosting_asset#fir00:OWNER and assume }",
"{ grant role:hs_hosting_asset#fir00:OWNER to role:hs_booking_item#fir01:ADMIN by system and assume }", "{ grant role:hs_hosting_asset#fir00:OWNER to role:hs_booking_item#fir01:ADMIN by system and assume }",
"{ grant role:hs_hosting_asset#fir00:OWNER to role:hs_hosting_asset#vm1011:ADMIN by system and assume }", "{ grant role:hs_hosting_asset#fir00:OWNER to role:hs_hosting_asset#vm1011:ADMIN by system and assume }",
"{ grant perm:hs_hosting_asset#fir00:DELETE to role:hs_hosting_asset#fir00:OWNER by system and assume }", "{ grant perm:hs_hosting_asset#fir00:DELETE to role:hs_hosting_asset#fir00:OWNER by system and assume }",
@ -158,37 +159,38 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
@Test @Test
public void anyUser_canCreateNewDomainSetupAsset() { public void anyUser_canCreateNewDomainSetupAsset() {
// given
context("superuser-alex@hostsharing.net");
final var assetCount = assetRepo.count();
// when // when
context("person-SmithPeter@example.com"); context("person-SmithPeter@example.com");
final var result = attempt(em, () -> { final var result = attempt(em, () -> {
final var newAsset = HsHostingAssetEntity.builder() final var newAsset = HsHostingAssetEntity.builder()
.caption("some new domain setup")
.type(DOMAIN_SETUP) .type(DOMAIN_SETUP)
.identifier("example.org") .identifier("example.net")
.caption("some new domain setup")
.build(); .build();
return toCleanup(assetRepo.save(newAsset)); return assetRepo.save(newAsset);
}); });
// then // then
// ... the domain setup was created and returned
result.assertSuccessful(); result.assertSuccessful();
assertThat(result.returnedValue()).isNotNull().extracting(HsHostingAssetEntity::getUuid).isNotNull(); assertThat(result.returnedValue()).isNotNull().extracting(HsHostingAssetEntity::getUuid).isNotNull();
assertThat(result.returnedValue().isLoaded()).isFalse(); assertThat(result.returnedValue().isLoaded()).isFalse();
context("superuser-alex@hostsharing.net");
// ... the creating user can read the new domain setup
context("person-SmithPeter@example.com");
assertThatAssetIsPersisted(result.returnedValue());
// ... a global admin can see the new domain setup as well if the domain OWNER role is assumed
context("superuser-alex@hostsharing.net", "hs_hosting_asset#example.net:OWNER"); // only works with the assumed role
assertThatAssetIsPersisted(result.returnedValue()); assertThatAssetIsPersisted(result.returnedValue());
assertThat(assetRepo.count()).isEqualTo(assetCount + 1);
} }
private void assertThatAssetIsPersisted(final HsHostingAssetEntity saved) { private void assertThatAssetIsPersisted(final HsHostingAssetEntity saved) {
final var context = em.clear();
attempt(em, () -> { attempt(em, () -> {
final var found = assetRepo.findByUuid(saved.getUuid()); final var found = assetRepo.findByUuid(saved.getUuid());
assertThat(found).isNotEmpty().map(HsHostingAssetEntity::toString).get().isEqualTo(saved.toString()); assertThat(found).isNotEmpty().map(HsHostingAssetEntity::toString).contains(saved.toString());
}); });
} }
} }

View File

@ -0,0 +1,219 @@
package net.hostsharing.hsadminng.hs.hosting.asset;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class HsHostingAssetTypeUnitTest {
@Test
void generatedPlantUML() {
final var result = HsHostingAssetType.renderAsEmbeddedPlantUml();
assertThat(result).isEqualTo("""
## HostingAsset Type Structure
### Domain
```plantuml
@startuml
left to right direction
package Booking #feb28c {
entity BI_PRIVATE_CLOUD
entity BI_CLOUD_SERVER
entity BI_MANAGED_SERVER
entity BI_MANAGED_WEBSPACE
entity BI_DOMAIN_DNS_SETUP
entity BI_DOMAIN_EMAIL_SUBMISSION_SETUP
}
package Hosting #feb28c{
package Domain #99bcdb {
entity HA_DOMAIN_SETUP
entity HA_DOMAIN_DNS_SETUP
entity HA_DOMAIN_HTTP_SETUP
entity HA_DOMAIN_EMAIL_SUBMISSION_SETUP
entity HA_DOMAIN_EMAIL_MAILBOX_SETUP
entity HA_EMAIL_ADDRESS
}
package Server #99bcdb {
entity HA_CLOUD_SERVER
entity HA_MANAGED_SERVER
entity HA_IP_NUMBER
}
package Webspace #99bcdb {
entity HA_MANAGED_WEBSPACE
entity HA_UNIX_USER
entity HA_EMAIL_ALIAS
}
}
BI_CLOUD_SERVER *--> BI_PRIVATE_CLOUD
BI_MANAGED_SERVER *--> BI_PRIVATE_CLOUD
BI_MANAGED_WEBSPACE *--> BI_MANAGED_SERVER
HA_CLOUD_SERVER *==> BI_CLOUD_SERVER
HA_MANAGED_SERVER *==> BI_MANAGED_SERVER
HA_MANAGED_WEBSPACE *==> BI_MANAGED_WEBSPACE
HA_MANAGED_WEBSPACE o..> HA_MANAGED_SERVER
HA_UNIX_USER *==> HA_MANAGED_WEBSPACE
HA_DOMAIN_SETUP o..> HA_DOMAIN_SETUP
HA_DOMAIN_DNS_SETUP *==> HA_DOMAIN_SETUP
HA_DOMAIN_HTTP_SETUP *==> HA_DOMAIN_SETUP
HA_DOMAIN_HTTP_SETUP o..> HA_UNIX_USER
HA_DOMAIN_EMAIL_SUBMISSION_SETUP *==> HA_DOMAIN_SETUP
HA_DOMAIN_EMAIL_SUBMISSION_SETUP o..> HA_MANAGED_WEBSPACE
HA_DOMAIN_EMAIL_MAILBOX_SETUP *==> HA_DOMAIN_SETUP
HA_DOMAIN_EMAIL_MAILBOX_SETUP o..> HA_MANAGED_WEBSPACE
HA_EMAIL_ALIAS *==> HA_MANAGED_WEBSPACE
HA_EMAIL_ADDRESS *==> HA_DOMAIN_EMAIL_MAILBOX_SETUP
HA_IP_NUMBER o..> HA_CLOUD_SERVER
HA_IP_NUMBER o..> HA_MANAGED_SERVER
HA_IP_NUMBER o..> HA_MANAGED_WEBSPACE
package Legend #white {
SUB_ENTITY1 *--> REQUIRED_PARENT_ENTITY
SUB_ENTITY2 *..> OPTIONAL_PARENT_ENTITY
ASSIGNED_ENTITY1 o--> REQUIRED_ASSIGNED_TO_ENTITY1
ASSIGNED_ENTITY2 o..> OPTIONAL_ASSIGNED_TO_ENTITY2
}
Booking -down[hidden]->Legend
```
### MariaDB
```plantuml
@startuml
left to right direction
package Booking #feb28c {
entity BI_PRIVATE_CLOUD
entity BI_CLOUD_SERVER
entity BI_MANAGED_SERVER
entity BI_MANAGED_WEBSPACE
entity BI_DOMAIN_DNS_SETUP
entity BI_DOMAIN_EMAIL_SUBMISSION_SETUP
}
package Hosting #feb28c{
package MariaDB #99bcdb {
entity HA_MARIADB_INSTANCE
entity HA_MARIADB_USER
entity HA_MARIADB_DATABASE
}
package Server #99bcdb {
entity HA_CLOUD_SERVER
entity HA_MANAGED_SERVER
entity HA_IP_NUMBER
}
package Webspace #99bcdb {
entity HA_MANAGED_WEBSPACE
entity HA_UNIX_USER
entity HA_EMAIL_ALIAS
}
}
BI_CLOUD_SERVER *--> BI_PRIVATE_CLOUD
BI_MANAGED_SERVER *--> BI_PRIVATE_CLOUD
BI_MANAGED_WEBSPACE *--> BI_MANAGED_SERVER
HA_CLOUD_SERVER *==> BI_CLOUD_SERVER
HA_MANAGED_SERVER *==> BI_MANAGED_SERVER
HA_MANAGED_WEBSPACE *==> BI_MANAGED_WEBSPACE
HA_MANAGED_WEBSPACE o..> HA_MANAGED_SERVER
HA_UNIX_USER *==> HA_MANAGED_WEBSPACE
HA_EMAIL_ALIAS *==> HA_MANAGED_WEBSPACE
HA_MARIADB_INSTANCE *==> HA_MANAGED_SERVER
HA_MARIADB_USER *==> HA_MARIADB_INSTANCE
HA_MARIADB_USER o..> HA_MANAGED_WEBSPACE
HA_MARIADB_DATABASE *==> HA_MANAGED_WEBSPACE
HA_MARIADB_DATABASE o..> HA_MARIADB_INSTANCE
HA_IP_NUMBER o..> HA_CLOUD_SERVER
HA_IP_NUMBER o..> HA_MANAGED_SERVER
HA_IP_NUMBER o..> HA_MANAGED_WEBSPACE
package Legend #white {
SUB_ENTITY1 *--> REQUIRED_PARENT_ENTITY
SUB_ENTITY2 *..> OPTIONAL_PARENT_ENTITY
ASSIGNED_ENTITY1 o--> REQUIRED_ASSIGNED_TO_ENTITY1
ASSIGNED_ENTITY2 o..> OPTIONAL_ASSIGNED_TO_ENTITY2
}
Booking -down[hidden]->Legend
```
### PostgreSQL
```plantuml
@startuml
left to right direction
package Booking #feb28c {
entity BI_PRIVATE_CLOUD
entity BI_CLOUD_SERVER
entity BI_MANAGED_SERVER
entity BI_MANAGED_WEBSPACE
entity BI_DOMAIN_DNS_SETUP
entity BI_DOMAIN_EMAIL_SUBMISSION_SETUP
}
package Hosting #feb28c{
package PostgreSQL #99bcdb {
entity HA_PGSQL_INSTANCE
entity HA_PGSQL_USER
entity HA_PGSQL_DATABASE
}
package Server #99bcdb {
entity HA_CLOUD_SERVER
entity HA_MANAGED_SERVER
entity HA_IP_NUMBER
}
package Webspace #99bcdb {
entity HA_MANAGED_WEBSPACE
entity HA_UNIX_USER
entity HA_EMAIL_ALIAS
}
}
BI_CLOUD_SERVER *--> BI_PRIVATE_CLOUD
BI_MANAGED_SERVER *--> BI_PRIVATE_CLOUD
BI_MANAGED_WEBSPACE *--> BI_MANAGED_SERVER
HA_CLOUD_SERVER *==> BI_CLOUD_SERVER
HA_MANAGED_SERVER *==> BI_MANAGED_SERVER
HA_MANAGED_WEBSPACE *==> BI_MANAGED_WEBSPACE
HA_MANAGED_WEBSPACE o..> HA_MANAGED_SERVER
HA_UNIX_USER *==> HA_MANAGED_WEBSPACE
HA_EMAIL_ALIAS *==> HA_MANAGED_WEBSPACE
HA_PGSQL_INSTANCE *==> HA_MANAGED_SERVER
HA_PGSQL_USER *==> HA_PGSQL_INSTANCE
HA_PGSQL_USER o..> HA_MANAGED_WEBSPACE
HA_PGSQL_DATABASE *==> HA_MANAGED_WEBSPACE
HA_PGSQL_DATABASE o..> HA_PGSQL_INSTANCE
HA_IP_NUMBER o..> HA_CLOUD_SERVER
HA_IP_NUMBER o..> HA_MANAGED_SERVER
HA_IP_NUMBER o..> HA_MANAGED_WEBSPACE
package Legend #white {
SUB_ENTITY1 *--> REQUIRED_PARENT_ENTITY
SUB_ENTITY2 *..> OPTIONAL_PARENT_ENTITY
ASSIGNED_ENTITY1 o--> REQUIRED_ASSIGNED_TO_ENTITY1
ASSIGNED_ENTITY2 o..> OPTIONAL_ASSIGNED_TO_ENTITY2
}
Booking -down[hidden]->Legend
```
This code generated was by HsHostingAssetType.main, do not amend manually.
""");
}
}

View File

@ -33,7 +33,7 @@ class HsCloudServerHostingAssetValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'CLOUD_SERVER:vm1234.bookingItem' must not be null but is null", "'CLOUD_SERVER:vm1234.bookingItem' must be of type CLOUD_SERVER but is null",
"'CLOUD_SERVER:vm1234.config.RAM' is not expected but is set to '2000'"); "'CLOUD_SERVER:vm1234.config.RAM' is not expected but is set to '2000'");
} }
@ -84,14 +84,14 @@ class HsCloudServerHostingAssetValidatorUnitTest {
} }
@Test @Test
void validatesParentAndAssignedToAssetMustNotBeSet() { void rejectsInvalidReferencedEntities() {
// given // given
final var mangedServerHostingAssetEntity = HsHostingAssetEntity.builder() final var mangedServerHostingAssetEntity = HsHostingAssetEntity.builder()
.type(CLOUD_SERVER) .type(CLOUD_SERVER)
.identifier("xyz00") .identifier("vm1234")
.parentAsset(HsHostingAssetEntity.builder().build())
.assignedToAsset(HsHostingAssetEntity.builder().build())
.bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.CLOUD_SERVER).build()) .bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.CLOUD_SERVER).build())
.parentAsset(HsHostingAssetEntity.builder().type(MANAGED_SERVER).build())
.assignedToAsset(HsHostingAssetEntity.builder().type(CLOUD_SERVER).build())
.build(); .build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType()); final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType());
@ -100,7 +100,7 @@ class HsCloudServerHostingAssetValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'CLOUD_SERVER:xyz00.parentAsset' must be null but is set to D-???????-?:null", "'CLOUD_SERVER:vm1234.parentAsset' must be null but is of type MANAGED_SERVER",
"'CLOUD_SERVER:xyz00.assignedToAsset' must be null but is set to D-???????-?:null"); "'CLOUD_SERVER:vm1234.assignedToAsset' must be null but is of type CLOUD_SERVER");
} }
} }

View File

@ -31,7 +31,7 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest {
return HsHostingAssetEntity.builder() return HsHostingAssetEntity.builder()
.type(DOMAIN_DNS_SETUP) .type(DOMAIN_DNS_SETUP)
.parentAsset(validDomainSetupEntity) .parentAsset(validDomainSetupEntity)
.identifier("example.org") .identifier("example.org|DNS")
.config(Map.ofEntries( .config(Map.ofEntries(
entry("user-RR", Array.of( entry("user-RR", Array.of(
"@ 1814400 IN XXX example.org. root.example.org ( 1234 10800 900 604800 86400 )", "@ 1814400 IN XXX example.org. root.example.org ( 1234 10800 900 604800 86400 )",
@ -74,19 +74,20 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest {
void preprocessesTakesIdentifierFromParent() { void preprocessesTakesIdentifierFromParent() {
// given // given
final var givenEntity = validEntityBuilder().build(); final var givenEntity = validEntityBuilder().build();
assertThat(givenEntity.getParentAsset().getIdentifier()).as("preconditon failed").isEqualTo("example.org");
final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType()); final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType());
// when // when
validator.preprocessEntity(givenEntity); validator.preprocessEntity(givenEntity);
// then // then
assertThat(givenEntity.getIdentifier()).isEqualTo(givenEntity.getParentAsset().getIdentifier()); assertThat(givenEntity.getIdentifier()).isEqualTo("example.org|DNS");
} }
@Test @Test
void rejectsInvalidIdentifier() { void rejectsInvalidIdentifier() {
// given // given
final var givenEntity = validEntityBuilder().identifier("wrong.org").build(); final var givenEntity = validEntityBuilder().identifier("example.org").build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType()); final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType());
// when // when
@ -94,14 +95,14 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest {
// then // then
assertThat(result).containsExactly( assertThat(result).containsExactly(
"'identifier' expected to match '^example.org$', but is 'wrong.org'" "'identifier' expected to match '^example.org\\Q|DNS\\E$', but is 'example.org'"
); );
} }
@Test @Test
void acceptsValidIdentifier() { void acceptsValidIdentifier() {
// given // given
final var givenEntity = validEntityBuilder().identifier(validDomainSetupEntity.getIdentifier()).build(); final var givenEntity = validEntityBuilder().identifier(validDomainSetupEntity.getIdentifier()+"|DNS").build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType()); final var validator = HsHostingAssetEntityValidatorRegistry.forType(givenEntity.getType());
// when // when
@ -112,12 +113,12 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest {
} }
@Test @Test
void validatesReferencedEntities() { void rejectsInvalidReferencedEntities() {
// given // given
final var mangedServerHostingAssetEntity = validEntityBuilder() final var mangedServerHostingAssetEntity = validEntityBuilder()
.parentAsset(HsHostingAssetEntity.builder().build())
.assignedToAsset(HsHostingAssetEntity.builder().build())
.bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.CLOUD_SERVER).build()) .bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.CLOUD_SERVER).build())
.parentAsset(null)
.assignedToAsset(HsHostingAssetEntity.builder().type(DOMAIN_SETUP).build())
.build(); .build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType()); final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType());
@ -126,9 +127,9 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'DOMAIN_DNS_SETUP:example.org.bookingItem' must be null but is set to D-???????-?:null", "'DOMAIN_DNS_SETUP:example.org|DNS.bookingItem' must be null but is of type CLOUD_SERVER",
"'DOMAIN_DNS_SETUP:example.org.parentAsset' must be of type DOMAIN_SETUP but is of type null", "'DOMAIN_DNS_SETUP:example.org|DNS.parentAsset' must be of type DOMAIN_SETUP but is null",
"'DOMAIN_DNS_SETUP:example.org.assignedToAsset' must be null but is set to D-???????-?:null"); "'DOMAIN_DNS_SETUP:example.org|DNS.assignedToAsset' must be null but is of type DOMAIN_SETUP");
} }
@Test @Test
@ -162,9 +163,9 @@ class HsDomainDnsSetupHostingAssetValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'DOMAIN_DNS_SETUP:example.org.config.TTL' is expected to be of type class java.lang.Integer, but is of type 'String'", "'DOMAIN_DNS_SETUP:example.org|DNS.config.TTL' is expected to be of type class java.lang.Integer, but is of type 'String'",
"'DOMAIN_DNS_SETUP:example.org.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 '@ 1814400 IN 1814400 BAD1 TTL only allowed once' does not match any",
"'DOMAIN_DNS_SETUP:example.org.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-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");
} }
@Test @Test

View File

@ -12,6 +12,7 @@ import java.util.Map;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_SETUP; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_SETUP;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
class HsDomainSetupHostingAssetValidatorUnitTest { class HsDomainSetupHostingAssetValidatorUnitTest {
@ -93,8 +94,8 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
void validatesReferencedEntities() { void validatesReferencedEntities() {
// given // given
final var mangedServerHostingAssetEntity = validEntityBuilder() final var mangedServerHostingAssetEntity = validEntityBuilder()
.parentAsset(HsHostingAssetEntity.builder().build()) .parentAsset(HsHostingAssetEntity.builder().type(CLOUD_SERVER).build())
.assignedToAsset(HsHostingAssetEntity.builder().build()) .assignedToAsset(HsHostingAssetEntity.builder().type(MANAGED_SERVER).build())
.bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.CLOUD_SERVER).build()) .bookingItem(HsBookingItemEntity.builder().type(HsBookingItemType.CLOUD_SERVER).build())
.build(); .build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType()); final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType());
@ -104,8 +105,8 @@ class HsDomainSetupHostingAssetValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'DOMAIN_SETUP:example.org.bookingItem' must be null but is set to D-???????-?:null", "'DOMAIN_SETUP:example.org.bookingItem' must be null but is of type CLOUD_SERVER",
"'DOMAIN_SETUP:example.org.parentAsset' must be null but is set to D-???????-?:null", "'DOMAIN_SETUP:example.org.parentAsset' must be null or of type DOMAIN_SETUP but is of type CLOUD_SERVER",
"'DOMAIN_SETUP:example.org.assignedToAsset' must be null but is set to D-???????-?:null"); "'DOMAIN_SETUP:example.org.assignedToAsset' must be null but is of type MANAGED_SERVER");
} }
} }

View File

@ -107,8 +107,8 @@ class HsEMailAliasHostingAssetValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'EMAIL_ALIAS:abc00-office.bookingItem' must be null but is set to D-1234500:test project:test project booking item", "'EMAIL_ALIAS:abc00-office.bookingItem' must be null but is of type MANAGED_SERVER",
"'EMAIL_ALIAS:abc00-office.parentAsset' must be of type MANAGED_WEBSPACE but is of type MANAGED_SERVER", "'EMAIL_ALIAS:abc00-office.parentAsset' must be of type MANAGED_WEBSPACE but is of type MANAGED_SERVER",
"'EMAIL_ALIAS:abc00-office.assignedToAsset' must be null but is set to D-1234500:test project:test project booking item"); "'EMAIL_ALIAS:abc00-office.assignedToAsset' must be null but is of type MANAGED_SERVER");
} }
} }

View File

@ -10,6 +10,7 @@ import java.util.Map;
import static java.util.Map.entry; import static java.util.Map.entry;
import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_CLOUD_SERVER_BOOKING_ITEM; import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_CLOUD_SERVER_BOOKING_ITEM;
import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_MANAGED_SERVER_BOOKING_ITEM; import static net.hostsharing.hsadminng.hs.booking.item.TestHsBookingItem.TEST_MANAGED_SERVER_BOOKING_ITEM;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -22,8 +23,8 @@ class HsManagedServerHostingAssetValidatorUnitTest {
.type(MANAGED_SERVER) .type(MANAGED_SERVER)
.identifier("vm1234") .identifier("vm1234")
.bookingItem(TEST_MANAGED_SERVER_BOOKING_ITEM) .bookingItem(TEST_MANAGED_SERVER_BOOKING_ITEM)
.parentAsset(HsHostingAssetEntity.builder().build()) .parentAsset(HsHostingAssetEntity.builder().type(CLOUD_SERVER).build())
.assignedToAsset(HsHostingAssetEntity.builder().build()) .assignedToAsset(HsHostingAssetEntity.builder().type(CLOUD_SERVER).build())
.config(Map.ofEntries( .config(Map.ofEntries(
entry("monit_max_hdd_usage", "90"), entry("monit_max_hdd_usage", "90"),
entry("monit_max_cpu_usage", 2), entry("monit_max_cpu_usage", 2),
@ -37,8 +38,8 @@ class HsManagedServerHostingAssetValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'MANAGED_SERVER:vm1234.parentAsset' must be null but is set to D-1234500:test project:test project booking item", "'MANAGED_SERVER:vm1234.parentAsset' must be null but is of type CLOUD_SERVER",
"'MANAGED_SERVER:vm1234.assignedToAsset' must be null but is set to D-1234500:test project:test project booking item", "'MANAGED_SERVER:vm1234.assignedToAsset' must be null but is of type CLOUD_SERVER",
"'MANAGED_SERVER:vm1234.config.monit_max_cpu_usage' is expected to be at least 10 but is 2", "'MANAGED_SERVER:vm1234.config.monit_max_cpu_usage' is expected to be at least 10 but is 2",
"'MANAGED_SERVER:vm1234.config.monit_max_ram_usage' is expected to be at most 100 but is 101", "'MANAGED_SERVER:vm1234.config.monit_max_ram_usage' is expected to be at most 100 but is 101",
"'MANAGED_SERVER:vm1234.config.monit_max_hdd_usage' is expected to be of type class java.lang.Integer, but is of type 'String'"); "'MANAGED_SERVER:vm1234.config.monit_max_hdd_usage' is expected to be of type class java.lang.Integer, but is of type 'String'");
@ -63,14 +64,14 @@ class HsManagedServerHostingAssetValidatorUnitTest {
} }
@Test @Test
void validatesParentAndAssignedToAssetMustNotBeSet() { void rejectsInvalidReferencedEntities() {
// given // given
final var mangedServerHostingAssetEntity = HsHostingAssetEntity.builder() final var mangedServerHostingAssetEntity = HsHostingAssetEntity.builder()
.type(MANAGED_SERVER) .type(MANAGED_SERVER)
.identifier("xyz00") .identifier("xyz00")
.parentAsset(HsHostingAssetEntity.builder().build())
.assignedToAsset(HsHostingAssetEntity.builder().build())
.bookingItem(TEST_CLOUD_SERVER_BOOKING_ITEM) .bookingItem(TEST_CLOUD_SERVER_BOOKING_ITEM)
.parentAsset(HsHostingAssetEntity.builder().type(CLOUD_SERVER).build())
.assignedToAsset(HsHostingAssetEntity.builder().type(MANAGED_SERVER).build())
.build(); .build();
final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType()); final var validator = HsHostingAssetEntityValidatorRegistry.forType(mangedServerHostingAssetEntity.getType());
@ -80,7 +81,7 @@ class HsManagedServerHostingAssetValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'MANAGED_SERVER:xyz00.bookingItem' must be of type MANAGED_SERVER but is of type CLOUD_SERVER", "'MANAGED_SERVER:xyz00.bookingItem' must be of type MANAGED_SERVER but is of type CLOUD_SERVER",
"'MANAGED_SERVER:xyz00.parentAsset' must be null but is set to D-1234500:test project:test cloud server booking item", "'MANAGED_SERVER:xyz00.parentAsset' must be null but is of type CLOUD_SERVER",
"'MANAGED_SERVER:xyz00.assignedToAsset' must be null but is set to D-1234500:test project:test cloud server booking item"); "'MANAGED_SERVER:xyz00.assignedToAsset' must be null but is of type MANAGED_SERVER");
} }
} }

View File

@ -10,6 +10,7 @@ import java.util.Map;
import java.util.stream.Stream; import java.util.stream.Stream;
import static java.util.Map.entry; import static java.util.Map.entry;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static net.hostsharing.hsadminng.hs.booking.project.TestHsBookingProject.TEST_PROJECT; import static net.hostsharing.hsadminng.hs.booking.project.TestHsBookingProject.TEST_PROJECT;
@ -142,7 +143,7 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
} }
@Test @Test
void validatesEntityReferences() { void rejectsInvalidEntityReferences() {
// given // given
final var validator = HsHostingAssetEntityValidatorRegistry.forType(MANAGED_WEBSPACE); final var validator = HsHostingAssetEntityValidatorRegistry.forType(MANAGED_WEBSPACE);
final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder() final var mangedWebspaceHostingAssetEntity = HsHostingAssetEntity.builder()
@ -153,7 +154,7 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
.resources(Map.ofEntries(entry("SSD", 25), entry("Traffic", 250))) .resources(Map.ofEntries(entry("SSD", 25), entry("Traffic", 250)))
.build()) .build())
.parentAsset(cloudServerAssetEntity) .parentAsset(cloudServerAssetEntity)
.assignedToAsset(HsHostingAssetEntity.builder().build()) .assignedToAsset(HsHostingAssetEntity.builder().type(CLOUD_SERVER).build())
.identifier("abc00") .identifier("abc00")
.build(); .build();
@ -163,7 +164,7 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
// then // then
assertThat(result).containsExactly( assertThat(result).containsExactly(
"'MANAGED_WEBSPACE:abc00.bookingItem' must be of type MANAGED_WEBSPACE but is of type MANAGED_SERVER", "'MANAGED_WEBSPACE:abc00.bookingItem' must be of type MANAGED_WEBSPACE but is of type MANAGED_SERVER",
"'MANAGED_WEBSPACE:abc00.parentAsset' must be of type MANAGED_SERVER but is of type CLOUD_SERVER", "'MANAGED_WEBSPACE:abc00.parentAsset' must be null or of type MANAGED_SERVER but is of type CLOUD_SERVER",
"'MANAGED_WEBSPACE:abc00.assignedToAsset' must be null but is set to D-???????-?:some ManagedServer"); "'MANAGED_WEBSPACE:abc00.assignedToAsset' must be null but is of type CLOUD_SERVER");
} }
} }

View File

@ -9,7 +9,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable; import static org.assertj.core.api.Assertions.catchThrowable;
import static org.junit.jupiter.api.condition.OS.LINUX; import static org.junit.jupiter.api.condition.OS.LINUX;
class SystemProcessTest { class SystemProcessUnitTest {
@Test @Test
@EnabledOnOs(LINUX) @EnabledOnOs(LINUX)