Compare commits

...

2 Commits

7 changed files with 79 additions and 18 deletions

View File

@ -16,6 +16,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import jakarta.persistence.EntityNotFoundException;
import jakarta.validation.ValidationException; import jakarta.validation.ValidationException;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -130,8 +131,12 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
return entityToSave; return entityToSave;
} }
@SuppressWarnings("unchecked")
final BiConsumer<HsHostingAssetInsertResource, HsHostingAssetEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { final BiConsumer<HsHostingAssetInsertResource, HsHostingAssetEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
entity.putConfig(KeyValueMap.from(resource.getConfig())); entity.putConfig(KeyValueMap.from(resource.getConfig()));
if (resource.getParentAssetUuid() != null) {
entity.setParentAsset(assetRepo.findByUuid(resource.getParentAssetUuid())
.orElseThrow(() -> new EntityNotFoundException("ERROR: [400] parentAssetUuid %s not found".formatted(
resource.getParentAssetUuid()))));
}
}; };
} }

View File

@ -40,7 +40,6 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.CaseDef.inOtherCas
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingCase; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingCase;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingDefaultCase; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingDefaultCase;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NULLABLE; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NULLABLE;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.DELETE; 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;
@ -137,7 +136,7 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject {
.importEntityAlias("bookingItem", HsBookingItemEntity.class, usingDefaultCase(), .importEntityAlias("bookingItem", HsBookingItemEntity.class, usingDefaultCase(),
dependsOnColumn("bookingItemUuid"), dependsOnColumn("bookingItemUuid"),
directlyFetchedByDependsOnColumn(), directlyFetchedByDependsOnColumn(),
NOT_NULL) NULLABLE)
.switchOnColumn("type", .switchOnColumn("type",
inCaseOf(CLOUD_SERVER.name(), inCaseOf(CLOUD_SERVER.name(),

View File

@ -8,6 +8,7 @@ import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext; import jakarta.persistence.PersistenceContext;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
@ -25,6 +26,7 @@ public class RbacGrantsDiagramService {
public static void writeToFile(final String title, final String graph, final String fileName) { public static void writeToFile(final String title, final String graph, final String fileName) {
new File("doc/temp").mkdirs();
try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) { try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) {
writer.write(""" writer.write("""
### all grants to %s ### all grants to %s
@ -192,8 +194,9 @@ public class RbacGrantsDiagramService {
return "[" + roleType + "\nref:" + uuid + "]"; return "[" + roleType + "\nref:" + uuid + "]";
} }
if (refType.equals("perm")) { if (refType.equals("perm")) {
final var roleType = idName.split(":")[1]; final var parts = idName.split(":");
return "{{" + roleType + "\nref:" + uuid + "}}"; final var permType = parts[2];
return "{{" + permType + "\nref:" + uuid + "}}";
} }
return ""; return "";
} }
@ -205,7 +208,7 @@ public class RbacGrantsDiagramService {
@NotNull @NotNull
private static String cleanId(final String idName) { private static String cleanId(final String idName) {
return idName.replaceAll("@.*", "") return idName.replaceAll("@.*", "")
.replace("[", "").replace("]", "").replace("(", "").replace(")", "").replace(",", ""); .replace("[", "").replace("]", "").replace("(", "").replace(")", "").replace(",", "").replace(">", ":");
} }

View File

@ -53,7 +53,11 @@ components:
bookingItemUuid: bookingItemUuid:
type: string type: string
format: uuid format: uuid
nullable: false nullable: true
parentAssetUuid:
type: string
format: uuid
nullable: true
type: type:
$ref: '#/components/schemas/HsHostingAssetType' $ref: '#/components/schemas/HsHostingAssetType'
identifier: identifier:
@ -72,7 +76,6 @@ components:
- type - type
- identifier - identifier
- caption - caption
- debitorUuid
- config - config
additionalProperties: false additionalProperties: false

View File

@ -24,13 +24,17 @@ create table if not exists hs_hosting_asset
( (
uuid uuid unique references RbacObject (uuid), uuid uuid unique references RbacObject (uuid),
version int not null default 0, version int not null default 0,
bookingItemUuid uuid not null references hs_booking_item(uuid), bookingItemUuid uuid null references hs_booking_item(uuid),
type HsHostingAssetType not null, type HsHostingAssetType not null,
parentAssetUuid uuid null references hs_hosting_asset(uuid), parentAssetUuid uuid null references hs_hosting_asset(uuid),
identifier varchar(80) not null, identifier varchar(80) not null,
caption varchar(80) not null, caption varchar(80) not null,
config jsonb not null config jsonb not null,
constraint chk_hs_hosting_asset_has_booking_item_or_parent_asset check (bookingItemUuid is not null or parentAssetUuid is not null)
); );
--// --//

View File

@ -39,8 +39,6 @@ begin
SELECT * FROM hs_hosting_asset WHERE uuid = NEW.parentAssetUuid INTO newParentServer; SELECT * FROM hs_hosting_asset WHERE uuid = NEW.parentAssetUuid INTO newParentServer;
SELECT * FROM hs_booking_item WHERE uuid = NEW.bookingItemUuid INTO newBookingItem; SELECT * FROM hs_booking_item WHERE uuid = NEW.bookingItemUuid INTO newBookingItem;
assert newBookingItem.uuid is not null, format('newBookingItem must not be null for NEW.bookingItemUuid = %s', NEW.bookingItemUuid);
perform createRoleWithGrants( perform createRoleWithGrants(
hsHostingAssetOWNER(NEW), hsHostingAssetOWNER(NEW),

View File

@ -19,6 +19,7 @@ import java.util.Map;
import java.util.UUID; import java.util.UUID;
import static java.util.Map.entry; import static java.util.Map.entry;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.matchesRegex; import static org.hamcrest.Matchers.matchesRegex;
@ -113,7 +114,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.header("current-user", "superuser-alex@hostsharing.net") .header("current-user", "superuser-alex@hostsharing.net")
.port(port) .port(port)
.when() .when()
.get("http://localhost/api/hs/hosting/assets?type=" + HsHostingAssetType.MANAGED_SERVER) .get("http://localhost/api/hs/hosting/assets?type=" + MANAGED_SERVER)
.then().log().all().assertThat() .then().log().all().assertThat()
.statusCode(200) .statusCode(200)
.contentType("application/json") .contentType("application/json")
@ -159,7 +160,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
class AddServer { class AddServer {
@Test @Test
void globalAdmin_canAddAsset() { void globalAdmin_canAddBookedAsset() {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenBookingItem = givenBookingItem("First", "some PrivateCloud"); final var givenBookingItem = givenBookingItem("First", "some PrivateCloud");
@ -173,7 +174,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
"bookingItemUuid": "%s", "bookingItemUuid": "%s",
"type": "MANAGED_SERVER", "type": "MANAGED_SERVER",
"identifier": "vm1400", "identifier": "vm1400",
"caption": "some new CloudServer", "caption": "some new ManagedServer",
"config": { "CPUs": 2, "RAM": 100, "SSD": 300, "Traffic": 250 } "config": { "CPUs": 2, "RAM": 100, "SSD": 300, "Traffic": 250 }
} }
""".formatted(givenBookingItem.getUuid())) """.formatted(givenBookingItem.getUuid()))
@ -187,7 +188,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
{ {
"type": "MANAGED_SERVER", "type": "MANAGED_SERVER",
"identifier": "vm1400", "identifier": "vm1400",
"caption": "some new CloudServer", "caption": "some new ManagedServer",
"config": { "CPUs": 2, "RAM": 100, "SSD": 300, "Traffic": 250 } "config": { "CPUs": 2, "RAM": 100, "SSD": 300, "Traffic": 250 }
} }
""")) """))
@ -200,6 +201,48 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
assertThat(newUserUuid).isNotNull(); assertThat(newUserUuid).isNotNull();
} }
@Test
void parentAssetAgent_canAddSubAsset() {
context.define("superuser-alex@hostsharing.net");
final var givenParentAsset = givenParentAsset("First", MANAGED_SERVER);
final var location = RestAssured // @formatter:off
.given()
.header("current-user", "person-FirbySusan@example.com")
.contentType(ContentType.JSON)
.body("""
{
"parentAssetUuid": "%s",
"type": "MANAGED_WEBSPACE",
"identifier": "xyz00",
"caption": "some new ManagedWebspace in client's ManagedServer",
"config": { "SSD": 100, "Traffic": 250 }
}
""".formatted(givenParentAsset.getUuid()))
.port(port)
.when()
.post("http://localhost/api/hs/hosting/assets")
.then().log().all().assertThat()
.statusCode(201)
.contentType(ContentType.JSON)
.body("", lenientlyEquals("""
{
"type": "MANAGED_WEBSPACE",
"identifier": "xyz00",
"caption": "some new ManagedWebspace in client's ManagedServer",
"config": { "SSD": 100, "Traffic": 250 }
}
"""))
.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 newUserUuid = UUID.fromString(
location.substring(location.lastIndexOf('/') + 1));
assertThat(newUserUuid).isNotNull();
}
@Test @Test
void additionalValidationsArePerformend_whenAddingAsset() { void additionalValidationsArePerformend_whenAddingAsset() {
@ -215,7 +258,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
"bookingItemUuid": "%s", "bookingItemUuid": "%s",
"type": "MANAGED_SERVER", "type": "MANAGED_SERVER",
"identifier": "vm1400", "identifier": "vm1400",
"caption": "some new CloudServer", "caption": "some new ManagedServer",
"config": { "CPUs": 0, "extra": 42 } "config": { "CPUs": 0, "extra": 42 }
} }
""".formatted(givenBookingItem.getUuid())) """.formatted(givenBookingItem.getUuid()))
@ -235,7 +278,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
} }
@Nested @Nested
class GetASset { class GetAsset {
@Test @Test
void globalAdmin_canGetArbitraryAsset() { void globalAdmin_canGetArbitraryAsset() {
@ -412,6 +455,12 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.findAny().orElseThrow(); .findAny().orElseThrow();
} }
HsHostingAssetEntity givenParentAsset(final String debitorName, final HsHostingAssetType assetType) {
final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike(debitorName).stream().findAny().orElseThrow();
final var givenAsset = assetRepo.findAllByCriteria(givenDebitor.getUuid(), null, assetType).stream().findAny().orElseThrow();
return givenAsset;
}
private HsHostingAssetEntity givenSomeTemporaryAssetForDebitorNumber(final String identifierSuffix, private HsHostingAssetEntity givenSomeTemporaryAssetForDebitorNumber(final String identifierSuffix,
final Map.Entry<String, Integer> resources) { final Map.Entry<String, Integer> resources) {
return jpaAttempt.transacted(() -> { return jpaAttempt.transacted(() -> {