add /api/hs/hosting/asset-types and /api/hs/hosting/asset-types/{assetType} endpoints

This commit is contained in:
Michael Hoennig 2024-05-03 17:28:27 +02:00
parent faaca44bea
commit 05f9e8142e
9 changed files with 307 additions and 15 deletions

View File

@ -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<List<String>> listAssetTypes() {
final var resource = HsHostingAssetValidator.types().stream()
.map(Enum::name)
.toList();
return ResponseEntity.ok(resource);
}
@Override
public ResponseEntity<List<Object>> listAssetTypeProps(
final HsHostingAssetTypeResource assetType) {
final var propValidators = HsHostingAssetValidator.forType(HsHostingAssetType.of(assetType));
// final List<Map<String, Object>> resource = propValidators.properties()
// .stream()
// .map(HsHostingAssetPropsController::simplifyTypeProperty)
// .toList();
final List<Map<String, Object>> resource = propValidators.properties();
return ResponseEntity.ok((List)resource);
}
// private static Map<String, Object> simplifyTypeProperty(Map<String, Object> source) {
// final var target = new LinkedHashMap<>(source);
// target.put("type", source,)
// return target;
// }
}

View File

@ -1,5 +1,6 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validator; package net.hostsharing.hsadminng.hs.hosting.asset.validator;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Setter; import lombok.Setter;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
@ -59,6 +60,14 @@ public abstract class HsHostingAssetPropertyValidator<T> {
throw new IllegalStateException(typeDef.getKey() + "[" + propertyName + "] not fully initialized, please call either .required() or .optional()" ); throw new IllegalStateException(typeDef.getKey() + "[" + propertyName + "] not fully initialized, please call either .required() or .optional()" );
} }
} }
public Map<String, Object> toMap(final ObjectMapper mapper) {
final Map<String, Object> map = mapper.convertValue(this, Map.class);
map.put("type", simpleTypeName());
return map;
}
protected abstract String simpleTypeName();
} }
@Setter @Setter
@ -90,6 +99,11 @@ class IntegerPropertyValidator extends HsHostingAssetPropertyValidator<Integer>{
result.add("'" + propertyName + "' is expected to be multiple of " + step + " but is " + propValue); result.add("'" + propertyName + "' is expected to be multiple of " + step + " but is " + propValue);
} }
} }
@Override
protected String simpleTypeName() {
return "integer";
}
} }
@Setter @Setter
@ -116,6 +130,11 @@ class EnumPropertyValidator extends HsHostingAssetPropertyValidator<String> {
result.add("'" + propertyName + "' is expected to be one of " + Arrays.toString(values) + " but is '" + propValue + "'"); result.add("'" + propertyName + "' is expected to be one of " + Arrays.toString(values) + " but is '" + propValue + "'");
} }
} }
@Override
protected String simpleTypeName() {
return "enumeration";
}
} }
@Setter @Setter
@ -145,4 +164,9 @@ class BooleanPropertyValidator extends HsHostingAssetPropertyValidator<Boolean>
} }
} }
} }
@Override
protected String simpleTypeName() {
return "boolean";
}
} }

View File

@ -1,11 +1,16 @@
package net.hostsharing.hsadminng.hs.hosting.asset.validator; 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.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType; import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import static java.util.Arrays.stream; import static java.util.Arrays.stream;
import static net.hostsharing.hsadminng.hs.hosting.asset.validator.EnumPropertyValidator.enumProperty; import static net.hostsharing.hsadminng.hs.hosting.asset.validator.EnumPropertyValidator.enumProperty;
@ -60,6 +65,10 @@ public class HsHostingAssetValidator {
propertyValidators = validators; propertyValidators = validators;
} }
public static Set<HsHostingAssetType> types() {
return validators.keySet();
}
public List<String> validate(final HsHostingAssetEntity assetEntity) { public List<String> validate(final HsHostingAssetEntity assetEntity) {
final var result = new ArrayList<String>(); final var result = new ArrayList<String>();
assetEntity.getConfig().keySet().forEach( givenPropName -> { assetEntity.getConfig().keySet().forEach( givenPropName -> {
@ -72,4 +81,19 @@ public class HsHostingAssetValidator {
}); });
return result; return result;
} }
public List<Map<String, Object>> 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<String, Object> asKeyValueMap(final Map map) {
return (Map<String, Object>) map;
}
} }

View File

@ -80,18 +80,11 @@ components:
# forces generating a java.lang.Object containing a Map, instead of class AssetConfiguration # forces generating a java.lang.Object containing a Map, instead of class AssetConfiguration
anyOf: anyOf:
- type: object - 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 additionalProperties: true
HsHostingAssetProps:
# FIXME: add proper schema spec
anyOf:
- type: object
additionalProperties: true

View File

@ -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'

View File

@ -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'

View File

@ -13,18 +13,20 @@ get:
schema: schema:
type: string type: string
format: uuid format: uuid
description: The UUID of the debitor, whose hosting assets are to be listed.
- name: parentAssetUuid - name: parentAssetUuid
in: query in: query
required: false required: false
schema: schema:
type: string type: string
format: uuid format: uuid
description: The UUID of the parentAsset, whose hosting assets are to be listed.
- name: type - name: type
in: query in: query
required: false required: false
schema: schema:
$ref: 'hs-hosting-asset-schemas.yaml#/components/schemas/HsHostingAssetType' $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: responses:
"200": "200":
description: OK description: OK

View File

@ -8,10 +8,16 @@ servers:
paths: paths:
# Items # Assets
/api/hs/hosting/assets: /api/hs/hosting/assets:
$ref: "hs-hosting-assets.yaml" $ref: "hs-hosting-assets.yaml"
/api/hs/hosting/assets/{assetUuid}: /api/hs/hosting/assets/{assetUuid}:
$ref: "hs-hosting-assets-with-uuid.yaml" $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"

View File

@ -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
}
}