From e57f4bf0c8bc766c15bf2f7d565e0ca345f8805b Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Tue, 3 Sep 2024 10:28:57 +0200 Subject: [PATCH] add-webspace-gid-and-create-webspace-main-user (#94) Co-authored-by: Michael Hoennig Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/94 Reviewed-by: Marc Sandlus --- .../hs/hosting/asset/HsHostingAsset.java | 4 ++- .../asset/HsHostingAssetController.java | 4 +-- .../HostingAssetEntitySaveProcessor.java | 29 +++++++++++++++++-- ...sManagedWebspaceHostingAssetValidator.java | 27 ++++++++++++++++- .../hs/validation/HsEntityValidator.java | 3 ++ ...sHostingAssetControllerAcceptanceTest.java | 26 +++++++++++++---- .../hs/migration/ImportHostingAssets.java | 19 +++++++++++- 7 files changed, 99 insertions(+), 13 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAsset.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAsset.java index 53e3f992..52e884e1 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAsset.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAsset.java @@ -34,6 +34,7 @@ import jakarta.persistence.OneToOne; import jakarta.persistence.PostLoad; import jakarta.persistence.Transient; import jakarta.persistence.Version; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -88,9 +89,10 @@ public abstract class HsHostingAsset implements Stringifyable, BaseEntity subHostingAssets; + private List subHostingAssets = new ArrayList<>(); @Column(name = "identifier") private String identifier; // e.g. vm1234, xyz00, example.org, xyz00_abc diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java index 4ae94c00..cb4e3446 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetController.java @@ -79,7 +79,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi { .preprocessEntity() .validateEntity() .prepareForSave() - .saveUsing(rbacAssetRepo::save) + .save() .validateContext() .mapUsing(e -> mapper.map(e, HsHostingAssetResource.class)) .revampProperties(); @@ -140,7 +140,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi { .preprocessEntity() .validateEntity() .prepareForSave() - .saveUsing(rbacAssetRepo::save) + .save() .validateContext() .mapUsing(e -> mapper.map(e, HsHostingAssetResource.class)) .revampProperties(); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HostingAssetEntitySaveProcessor.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HostingAssetEntitySaveProcessor.java index c5951f45..3e5850e5 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HostingAssetEntitySaveProcessor.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HostingAssetEntitySaveProcessor.java @@ -58,17 +58,42 @@ public class HostingAssetEntitySaveProcessor { /// hashing passwords etc. @SuppressWarnings("unchecked") public HostingAssetEntitySaveProcessor prepareForSave() { - step("prepareForSave", "saveUsing"); + step("prepareForSave", "save"); validator.prepareProperties(em, entity); return this; } + /** + * Saves the entity using the given `saveFunction`. + * + *

`validator.postPersist(em, entity)` is NOT called. + * If any postprocessing is necessary, the saveFunction has to implement this.

+ * @param saveFunction + * @return + */ public HostingAssetEntitySaveProcessor saveUsing(final Function saveFunction) { - step("saveUsing", "validateContext"); + step("save", "validateContext"); entity = saveFunction.apply(entity); return this; } + /** + * Saves the using the `EntityManager`, but does NOT ever merge the entity. + * + *

`validator.postPersist(em, entity)` is called afterwards with the entity guaranteed to be flushed to the database.

+ * @return + */ + public HostingAssetEntitySaveProcessor save() { + return saveUsing(e -> { + if (!em.contains(entity)) { + em.persist(entity); + } + em.flush(); // makes RbacEntity available as RealEntity if needed + validator.postPersist(em, entity); + return entity; + }); + } + /// validates the entity within it's parent and child hierarchy (e.g. totals validators and other limits) public HostingAssetEntitySaveProcessor validateContext() { step("validateContext", "mapUsing"); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedWebspaceHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedWebspaceHostingAssetValidator.java index dc0ece36..4579faf8 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedWebspaceHostingAssetValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validators/HsManagedWebspaceHostingAssetValidator.java @@ -1,17 +1,22 @@ package net.hostsharing.hsadminng.hs.hosting.asset.validators; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset; +import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetRealEntity; +import jakarta.persistence.EntityManager; import java.util.regex.Pattern; import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE; +import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.UNIX_USER; +import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty; class HsManagedWebspaceHostingAssetValidator extends HostingAssetEntityValidator { public HsManagedWebspaceHostingAssetValidator() { super( MANAGED_WEBSPACE, AlarmContact.isOptional(), - NO_EXTRA_PROPERTIES); // TODO.impl: groupid missing, should be equal to main user + integerProperty("groupid").readOnly() + ); } @Override @@ -22,4 +27,24 @@ class HsManagedWebspaceHostingAssetValidator extends HostingAssetEntityValidator : "[a-z][a-z0-9][a-z0-9]"; return Pattern.compile("^" + prefixPattern + "[0-9][0-9]$"); } + + @Override + public void postPersist(final EntityManager em, final HsHostingAsset webspaceAsset) { + if (!webspaceAsset.isLoaded()) { + final var unixUserAsset = HsHostingAssetRealEntity.builder() + .type(UNIX_USER) + .parentAsset(em.find(HsHostingAssetRealEntity.class, webspaceAsset.getUuid())) + .identifier(webspaceAsset.getIdentifier()) + .caption(webspaceAsset.getIdentifier() + " webspace user") + .build(); + webspaceAsset.getSubHostingAssets().add(unixUserAsset); + new HostingAssetEntitySaveProcessor(em, unixUserAsset) + .preprocessEntity() + .validateEntity() + .prepareForSave() + .save() + .validateContext(); + webspaceAsset.getConfig().put("groupid", unixUserAsset.getConfig().get("userid")); + } + } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java index ce353a7d..b2fa8a02 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/validation/HsEntityValidator.java @@ -160,4 +160,7 @@ public abstract class HsEntityValidator { public ValidatableProperty getProperty(final String propertyName) { return stream(propertyValidators).filter(pv -> pv.propertyName().equals(propertyName)).findFirst().orElse(null); } + + public void postPersist(final EntityManager em, final E entity) { + } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java index 9f689591..c668c6c9 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetControllerAcceptanceTest.java @@ -176,17 +176,31 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup "type": "MANAGED_WEBSPACE", "identifier": "fir10", "caption": "some separate ManagedWebspace HA", - "config": {} + "config": { + "groupid": 1000000 + } } """)) .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( + // the new asset can be accessed under the generated UUID + final var newWebspaceUuid = UUID.fromString( location.substring(location.lastIndexOf('/') + 1)); - assertThat(newWebspace).isNotNull(); - toCleanup(HsHostingAssetRbacEntity.class, newWebspace); + assertThat(newWebspaceUuid).isNotNull(); + toCleanup(HsHostingAssetRbacEntity.class, newWebspaceUuid); + + // and a default user got created + final var webspaceUnixUser = em.createQuery("SELECT ha FROM HsHostingAssetRealEntity ha WHERE ha.parentAsset.uuid=:webspaceUUID") + .setParameter("webspaceUUID", newWebspaceUuid) + .getSingleResult(); + assertThat(webspaceUnixUser).isNotNull().extracting(Object::toString) + .isEqualTo(""" + HsHostingAsset(UNIX_USER, fir10, fir10 webspace user, MANAGED_WEBSPACE:fir10, { + "password" : null, + "userid" : 1000000 + }) + """.trim()); } @Test @@ -325,7 +339,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup jpaAttempt.transacted(() -> { context.define("superuser-alex@hostsharing.net"); - for (int n = 0; n < UNIX_USER_PER_MULTI_OPTION -preExistingUnixUserCount+1; ++n) { + for (int n = 0; n < UNIX_USER_PER_MULTI_OPTION-preExistingUnixUserCount; ++n) { toCleanup(realAssetRepo.save( HsHostingAssetRealEntity.builder() .type(UNIX_USER) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java b/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java index 4b7375f9..677d808b 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/migration/ImportHostingAssets.java @@ -350,6 +350,18 @@ public class ImportHostingAssets extends BaseOfficeDataImport { 9596=HsHostingAsset(UNIX_USER, dph00-dph, Domain admin, MANAGED_WEBSPACE:dph00, {"SSD hard quota": 0, "SSD soft quota": 0, "locked": false, "shell": "/bin/bash", "userid": 110594}) } """); + + // now with groupids + assertThat(firstOfEach(5, packetAssets, MANAGED_WEBSPACE)) + .isEqualToIgnoringWhitespace(""" + { + 10630=HsHostingAsset(MANAGED_WEBSPACE, hsh00, HA hsh00, MANAGED_SERVER:vm1050, D-1000000:hsh default project:BI hsh00, {"groupid": 6824}), + 11094=HsHostingAsset(MANAGED_WEBSPACE, lug00, HA lug00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI lug00, {"groupid": 5803}), + 11111=HsHostingAsset(MANAGED_WEBSPACE, xyz68, HA xyz68, MANAGED_SERVER:vm1068, D-1000000:vm1068 Monitor:BI xyz68, {"groupid": 5961}), + 11112=HsHostingAsset(MANAGED_WEBSPACE, mim00, HA mim00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI mim00, {"groupid": 5964}), + 19959=HsHostingAsset(MANAGED_WEBSPACE, dph00, HA dph00, MANAGED_SERVER:vm1093, D-1101900:dph default project:BI dph00, {"groupid": 9546}) + } + """); } @Test @@ -1235,9 +1247,10 @@ public class ImportHostingAssets extends BaseOfficeDataImport { .forEach(rec -> { final var unixuser_id = rec.getInteger("unixuser_id"); final var packet_id = rec.getInteger("packet_id"); + final var parentWebspaceAsset = packetAssets.get(packet_id); final var unixUserAsset = HsHostingAssetRealEntity.builder() .type(UNIX_USER) - .parentAsset(packetAssets.get(packet_id)) + .parentAsset(parentWebspaceAsset) .identifier(rec.getString("name")) .caption(rec.getString("comment")) .isLoaded(true) // avoid overwriting imported userids with generated ids @@ -1253,6 +1266,10 @@ public class ImportHostingAssets extends BaseOfficeDataImport { ))) .build(); + if (unixUserAsset.getIdentifier().equals(parentWebspaceAsset.getIdentifier())) { + parentWebspaceAsset.getConfig().put("groupid", unixuser_id); + } + // TODO.spec: crop SSD+HDD limits if > booked if (unixUserAsset.getDirectValue("SSD hard quota", Integer.class, 0) > 1024 * unixUserAsset.getContextValue("SSD", Integer.class, 0)) {