From 05f9e8142e200bd920e1a761e32a08704dfbfd64 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 3 May 2024 17:28:27 +0200 Subject: [PATCH] add /api/hs/hosting/asset-types and /api/hs/hosting/asset-types/{assetType} endpoints --- .../asset/HsHostingAssetPropsController.java | 42 +++++ .../HsHostingAssetPropertyValidator.java | 24 +++ .../validator/HsHostingAssetValidator.java | 24 +++ .../hs-hosting/hs-hosting-asset-schemas.yaml | 19 +-- .../hs-hosting-asset-types-props.yaml | 26 +++ .../hs-hosting/hs-hosting-asset-types.yaml | 19 +++ .../hs-hosting/hs-hosting-assets.yaml | 4 +- .../api-definition/hs-hosting/hs-hosting.yaml | 8 +- ...ingAssetPropsControllerAcceptanceTest.java | 156 ++++++++++++++++++ 9 files changed, 307 insertions(+), 15 deletions(-) create mode 100644 src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsController.java create mode 100644 src/main/resources/api-definition/hs-hosting/hs-hosting-asset-types-props.yaml create mode 100644 src/main/resources/api-definition/hs-hosting/hs-hosting-asset-types.yaml create mode 100644 src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsController.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsController.java new file mode 100644 index 00000000..e15b906d --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsController.java @@ -0,0 +1,42 @@ +package net.hostsharing.hsadminng.hs.hosting.asset; + +import net.hostsharing.hsadminng.hs.hosting.asset.validator.HsHostingAssetValidator; +import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.api.HsHostingAssetPropsApi; +import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetTypeResource; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; + + +@RestController +public class HsHostingAssetPropsController implements HsHostingAssetPropsApi { + + @Override + public ResponseEntity> listAssetTypes() { + final var resource = HsHostingAssetValidator.types().stream() + .map(Enum::name) + .toList(); + return ResponseEntity.ok(resource); + } + + @Override + public ResponseEntity> listAssetTypeProps( + final HsHostingAssetTypeResource assetType) { + + final var propValidators = HsHostingAssetValidator.forType(HsHostingAssetType.of(assetType)); +// final List> resource = propValidators.properties() +// .stream() +// .map(HsHostingAssetPropsController::simplifyTypeProperty) +// .toList(); + final List> resource = propValidators.properties(); + return ResponseEntity.ok((List)resource); + } + +// private static Map simplifyTypeProperty(Map source) { +// final var target = new LinkedHashMap<>(source); +// target.put("type", source,) +// return target; +// } +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validator/HsHostingAssetPropertyValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validator/HsHostingAssetPropertyValidator.java index 71a1ec13..0dea3e77 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validator/HsHostingAssetPropertyValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validator/HsHostingAssetPropertyValidator.java @@ -1,5 +1,6 @@ package net.hostsharing.hsadminng.hs.hosting.asset.validator; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.Setter; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType; @@ -59,6 +60,14 @@ public abstract class HsHostingAssetPropertyValidator { throw new IllegalStateException(typeDef.getKey() + "[" + propertyName + "] not fully initialized, please call either .required() or .optional()" ); } } + + public Map toMap(final ObjectMapper mapper) { + final Map map = mapper.convertValue(this, Map.class); + map.put("type", simpleTypeName()); + return map; + } + + protected abstract String simpleTypeName(); } @Setter @@ -90,6 +99,11 @@ class IntegerPropertyValidator extends HsHostingAssetPropertyValidator{ result.add("'" + propertyName + "' is expected to be multiple of " + step + " but is " + propValue); } } + + @Override + protected String simpleTypeName() { + return "integer"; + } } @Setter @@ -116,6 +130,11 @@ class EnumPropertyValidator extends HsHostingAssetPropertyValidator { result.add("'" + propertyName + "' is expected to be one of " + Arrays.toString(values) + " but is '" + propValue + "'"); } } + + @Override + protected String simpleTypeName() { + return "enumeration"; + } } @Setter @@ -145,4 +164,9 @@ class BooleanPropertyValidator extends HsHostingAssetPropertyValidator } } } + + @Override + protected String simpleTypeName() { + return "boolean"; + } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validator/HsHostingAssetValidator.java b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validator/HsHostingAssetValidator.java index 26d4c5e4..b31cd68d 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validator/HsHostingAssetValidator.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/hosting/asset/validator/HsHostingAssetValidator.java @@ -1,11 +1,16 @@ package net.hostsharing.hsadminng.hs.hosting.asset.validator; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Set; import static java.util.Arrays.stream; import static net.hostsharing.hsadminng.hs.hosting.asset.validator.EnumPropertyValidator.enumProperty; @@ -60,6 +65,10 @@ public class HsHostingAssetValidator { propertyValidators = validators; } + public static Set types() { + return validators.keySet(); + } + public List validate(final HsHostingAssetEntity assetEntity) { final var result = new ArrayList(); assetEntity.getConfig().keySet().forEach( givenPropName -> { @@ -72,4 +81,19 @@ public class HsHostingAssetValidator { }); return result; } + + public List> properties() { + final var mapper = new ObjectMapper(); + mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + return Arrays.stream(propertyValidators) + .map(propertyValidator -> propertyValidator.toMap(mapper)) + .map(HsHostingAssetValidator::asKeyValueMap) + .toList(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static Map asKeyValueMap(final Map map) { + return (Map) map; + } + } diff --git a/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-schemas.yaml b/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-schemas.yaml index f3ecb6a3..9f33b45d 100644 --- a/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-schemas.yaml +++ b/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-schemas.yaml @@ -80,18 +80,11 @@ components: # forces generating a java.lang.Object containing a Map, instead of class AssetConfiguration anyOf: - type: object - properties: - CPU: - type: integer - minimum: 1 - maximum: 16 - SSD: - type: integer - minimum: 16 - maximum: 4096 - HDD: - type: integer - minimum: 16 - maximum: 4096 additionalProperties: true + HsHostingAssetProps: + # FIXME: add proper schema spec + anyOf: + - type: object + additionalProperties: true + diff --git a/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-types-props.yaml b/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-types-props.yaml new file mode 100644 index 00000000..c7723c22 --- /dev/null +++ b/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-types-props.yaml @@ -0,0 +1,26 @@ +get: + summary: Returns a list of available asset properties for the given type. + description: Returns the list of available properties and their validations for a given asset type. + tags: + - hs-hosting-asset-props + operationId: listAssetTypeProps + parameters: + - name: assetType + in: path + required: true + schema: + $ref: 'hs-hosting-asset-schemas.yaml#/components/schemas/HsHostingAssetType' + description: The asset type whose properties are to be returned. + responses: + "200": + description: OK + content: + 'application/json': + schema: + type: array + items: + $ref: 'hs-hosting-asset-schemas.yaml#/components/schemas/HsHostingAssetProps' + "401": + $ref: 'error-responses.yaml#/components/responses/Unauthorized' + "403": + $ref: 'error-responses.yaml#/components/responses/Forbidden' diff --git a/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-types.yaml b/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-types.yaml new file mode 100644 index 00000000..f1ab17e0 --- /dev/null +++ b/src/main/resources/api-definition/hs-hosting/hs-hosting-asset-types.yaml @@ -0,0 +1,19 @@ +get: + summary: Returns a list of available asset types. + description: Returns the list of asset types to enable an adaptive UI. + tags: + - hs-hosting-asset-props + operationId: listAssetTypes + responses: + "200": + description: OK + content: + 'application/json': + schema: + type: array + items: + type: string + "401": + $ref: 'error-responses.yaml#/components/responses/Unauthorized' + "403": + $ref: 'error-responses.yaml#/components/responses/Forbidden' diff --git a/src/main/resources/api-definition/hs-hosting/hs-hosting-assets.yaml b/src/main/resources/api-definition/hs-hosting/hs-hosting-assets.yaml index 8b81ecc7..a08a36a1 100644 --- a/src/main/resources/api-definition/hs-hosting/hs-hosting-assets.yaml +++ b/src/main/resources/api-definition/hs-hosting/hs-hosting-assets.yaml @@ -13,18 +13,20 @@ get: schema: type: string format: uuid + description: The UUID of the debitor, whose hosting assets are to be listed. - name: parentAssetUuid in: query required: false schema: type: string format: uuid + description: The UUID of the parentAsset, whose hosting assets are to be listed. - name: type in: query required: false schema: $ref: 'hs-hosting-asset-schemas.yaml#/components/schemas/HsHostingAssetType' - description: The UUID of the debitor, whose hosting assets are to be listed. + description: The type of hosting assets to be listed. responses: "200": description: OK diff --git a/src/main/resources/api-definition/hs-hosting/hs-hosting.yaml b/src/main/resources/api-definition/hs-hosting/hs-hosting.yaml index 4f8f29d5..e83b8436 100644 --- a/src/main/resources/api-definition/hs-hosting/hs-hosting.yaml +++ b/src/main/resources/api-definition/hs-hosting/hs-hosting.yaml @@ -8,10 +8,16 @@ servers: paths: - # Items + # Assets /api/hs/hosting/assets: $ref: "hs-hosting-assets.yaml" /api/hs/hosting/assets/{assetUuid}: $ref: "hs-hosting-assets-with-uuid.yaml" + + /api/hs/hosting/asset-types: + $ref: "hs-hosting-asset-types.yaml" + + /api/hs/hosting/asset-types/{assetType}: + $ref: "hs-hosting-asset-types-props.yaml" diff --git a/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java new file mode 100644 index 00000000..92f7e2b8 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/hosting/asset/HsHostingAssetPropsControllerAcceptanceTest.java @@ -0,0 +1,156 @@ +package net.hostsharing.hsadminng.hs.hosting.asset; + +import io.restassured.RestAssured; +import net.hostsharing.hsadminng.HsadminNgApplication; +import net.hostsharing.hsadminng.rbac.test.JpaAttempt; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; + +import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; + +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = { HsadminNgApplication.class, JpaAttempt.class } +) +class HsHostingAssetPropsControllerAcceptanceTest { + + @LocalServerPort + private Integer port; + + @Test + void anyone_canListAvailableAssetTypes() { + + RestAssured // @formatter:off + .given() + .port(port) + .when() + .get("http://localhost/api/hs/hosting/asset-types") + .then().log().all().assertThat() + .statusCode(200) + .contentType("application/json") + .body("", lenientlyEquals(""" + [ + "MANAGED_SERVER", + "MANAGED_WEBSPACE", + "CLOUD_SERVER" + ] + """)); + // @formatter:on + } + + @Test + void globalAdmin_canListPropertiesOfGivenAssetType() { + + RestAssured // @formatter:off + .given() + .port(port) + .when() + .get("http://localhost/api/hs/hosting/asset-types/" + HsHostingAssetType.MANAGED_SERVER) + .then().log().all().assertThat() + .statusCode(200) + .contentType("application/json") + .body("", lenientlyEquals(""" + [ + { + "type": "integer", + "propertyName": "CPUs", + "required": true, + "unit": null, + "min": 1, + "max": 32, + "step": null + }, + { + "type": "integer", + "propertyName": "RAM", + "required": true, + "unit": "GB", + "min": 1, + "max": 128, + "step": null + }, + { + "type": "integer", + "propertyName": "SSD", + "required": true, + "unit": "GB", + "min": 25, + "max": 1000, + "step": 25 + }, + { + "type": "integer", + "propertyName": "HDD", + "required": false, + "unit": "GB", + "min": 0, + "max": 4000, + "step": 250 + }, + { + "type": "integer", + "propertyName": "Traffic", + "required": true, + "unit": "GB", + "min": 250, + "max": 10000, + "step": 250 + }, + { + "type": "enumeration", + "propertyName": "SLA-Platform", + "required": false, + "values": [ + "BASIC", + "EXT8H", + "EXT4H", + "EXT2H" + ] + }, + { + "type": "boolean", + "propertyName": "SLA-EMail", + "required": false, + "falseIf": { + "SLA-Platform": "BASIC" + } + }, + { + "type": "boolean", + "propertyName": "SLA-Maria", + "required": false, + "falseIf": { + "SLA-Platform": "BASIC" + } + }, + { + "type": "boolean", + "propertyName": "SLA-PgSQL", + "required": false, + "falseIf": { + "SLA-Platform": "BASIC" + } + }, + { + "type": "boolean", + "propertyName": "SLA-Office", + "required": false, + "falseIf": { + "SLA-Platform": "BASIC" + } + }, + { + "type": "boolean", + "propertyName": "SLA-Web", + "required": false, + "falseIf": { + "SLA-Platform": "BASIC" + } + } + ] + """)); + // @formatter:on + } + +}