diff --git a/docs/region-configuration.md b/docs/region-configuration.md index d6234116..4cd4f549 100644 --- a/docs/region-configuration.md +++ b/docs/region-configuration.md @@ -11,20 +11,14 @@ See [regions.json](/onyxia-api/src/main/resources/regions.json) for a complete e - [Region configuration](#region-configuration) - [Main region properties](#main-region-properties) - [Services properties](#services-properties) - - [CustomInitScript properties](#custominitscript-properties) - [Server properties](#server-properties) - [K8sPublicEndpoint properties](#k8spublicendpoint-properties) - [Quotas properties](#quotas-properties) - [Expose properties](#expose-properties) - [istio](#istio) - [CertManager](#certManager) - - [Default configuration properties](#default-configuration-properties) - - [Kafka](#kafka) - - [Sliders](#sliders) - - [Resources](#resources) - [Data properties](#data-properties) - [S3](#s3) - - [Atlas](#atlas) - [Vault properties](#vault-properties) - [Git properties](#git-properties) - [ProxyConfiguration properties](#proxyconfiguration-properties) @@ -70,34 +64,14 @@ Users can work on Onyxia as a User or as a Group to which they belong. Each user | `groupPrefix` | | not used | | | `authenticationMode` | serviceAccount | serviceAccount, impersonate or tokenPassthrough : on serviceAccount mode Onyxia API uses its own serviceAccount (by default admin or cluster-admin), with impersonate mode Onyxia requests the API with user's permissions (helm option `--kube-as-user`). With tokenPassthrough, the authentication token is passed to the API server. | | | `expose` | | When users request to expose their service, only subdomain of this object domain are allowed | See [Expose properties](#expose-properties) | -| `monitoring` | | Define the URL pattern of the monitoring service that is to be launched with each service. Only for client purposes. | {URLPattern: "https://$NAMESPACE-$INSTANCE.mymonitoring.sspcloud.fr"} | -| `initScript` | | Define where to fetch a script that will be launched on some service on startup. | "https://inseefrlab.github.io/onyxia/onyxia-init.sh" | +| `monitoring` | | Define the URL pattern of the monitoring service that is to be launched with each service. Only for client purposes. | {URLPattern: "https://$NAMESPACE-$INSTANCE.mymonitoring.sspcloud.fr"} | | | `allowedURIPattern` | "^https://" | Init scripts set by the user have to respect this pattern. | | | `server` | | Define the configuration of the services provider API server, this value is not served on the API as it contains credentials for the API. | See [Server properties](#server-properties) | | `k8sPublicEndpoint` | | Define external access to Kubernetes API if available. It helps Onyxia users to directly connect to Kubernetes outside the datalab | See [K8sPublicEndpoint properties](#k8sPublicEndpoint-properties) | | `quotas` | | Properties setting quotas on how many resources a user can get on the services provider. | See [Quotas properties](#quotas-properties) | -| `defaultConfiguration` | | Default configuration on services that a user can override. For client purposes only. | See [Default Configuration](#default-configuration-properties) | -| `customInitScript` | | This can be used to customize user environments using a regional script executed by some users' pods. | See [CustomInitScript properties](#custominitscript-properties) -| `openshiftSCC` | | This can be used to inject SCC (Security Context Constraints) in openshift clusters | See [OpenshiftSCC properties](#openshiftSCC-properties) | -| `customValues` | | This can be used to specify custom values that will be available for helm chart injection in the web app. Nested values are supported. | ` "customValues": {"myCustomKey": "myValue", "myNestedCustomKey": {"nestedKey": "nestedValue"} }` | -### CustomInitScript properties -These properties define how to reach the **service provider API**. - -| Key | Description | Example | -| --------------------- | ------------------------------------------------------------------ | ---- | -| `URL` | URL of the init script | "api.kub.sspcloud.fr" | -| `checksum` | checksum of the init script | | - -### OpenshiftSCC properties -These properties define if SCC should be injected in services for openshift clusters - -| Key | Description | Example | -| --------------------- | ------------------------------------------------------------------ | ---- | -| `enabled` | defaults to `false` | `true` | -| `scc` | name of the SCC | `anyuid` | ### Server properties @@ -168,62 +142,6 @@ A quota follows the Kubernetes model which is composed of: -### Default configuration properties - -| Key | Default | Description | -| --------------------- | ------- | ------------------------------------------------------------------ | -| `IPProtection` | false | Whether or not the default behavior of the reverse proxy serving the service is to block a request from an IP other than the one from which it has been created. For client purposes only. | -| `networkPolicy` | false | Whether or not services can be reached by pods outside of the current namespace. For client purposes only. | -| `from` | NA | List of allowed sources (Kubernetes network policies format for from) to reach user HTTP services. Used to allow ingress access to users' services | -| `nodeSelector` | NA | This node selector can be injected in a service to restrain on which node it can be launched | -| `tolerations` | NA | This node selector can be injected in a service to force it to run on nodes with this taint | -| `startupProbe` | NA | This startup probe can be injected into a service. It can help you in an environment with a slow network to specify a long duration before killing a container | -| `kafka` | | See [Kafka](#kafka) | -| `sliders` | | See [Sliders](#sliders) | -| `Resources` | | See [Resources](#resources) | - -#### Kafka - -Kafka can be used to get some events in the user chart like Hive metastore. - -| Key | Default | Description | -| --------------------- | ------- | ------------------------------------------------------------------ | -| `URL` | N.A | brokerURL | -| `topicName` | N.A | topic name for those events | - -#### Sliders - -Sliders specify some slider parameters that may overwrite some defaults. - -| Key | Default | Description | -| --------------------- | ------- | ------------------------------------------------------------------ | -| `cpu` | N.A | cpu slider parameters | -| `memory` | N.A | memory slider parameters | -| `gpu` | N.A | gpu slider parameters | -| `disk` | N.A | disk slider parameters | - - -| Key | Default | Description | -| --------------------- | ------- | ------------------------------------------------------------------ | -| `sliderMin` | N.A | sliderMin | -| `sliderMax` | N.A | sliderMax | -| `sliderStep` | N.A | sliderStep | -| `sliderUnit` | N.A | sliderUnit | - -#### Resources - -Resources specify some values that may overwrite some defaults. - -| Key | Default | Description | -| --------------------- | ------- | ------------------------------------------------------------------ | -| `cpuRequest` | N.A | overwrite default CPU request if asked by helm-charts | -| `cpuLimit` | N.A | overwrite default CPU limit if asked by helm-charts | -| `memoryRequest` | N.A | overwrite default memory request if asked by helm-charts | -| `memoryLimit` | N.A | overwrite default memory limit if asked by helm-charts | -| `disk` | N.A | overwrite default disk size if asked by helm-charts | -| `gpu` | N.A | overwrite default GPU if asked by helm-charts | - - ## Data properties ### S3 @@ -367,17 +285,7 @@ type Region = { }; }; ``` - -### Atlas - -Atlas is a data management tool. - -It can be used to add additional features to the file explorer to transform it into a data explorer - -| Key | Default | Description | Example | -| --------------------- | ------- | ------------------------------------------------------------------ | ---- | -| `URL` | | URL of the atlas service for the region. | "https://atlas.change.me" | -| `oidcConfiguration` | | Allow override of openidconnect authentication for this specific service. If not defined then global Onyxia authentication will be used. | {clientID: "onyxia", issuerURI: "https://auth.lab.sspcloud.fr/auth"} | +| ## Vault properties diff --git a/onyxia-api/pom.xml b/onyxia-api/pom.xml index 5f1bbb7f..8e84ef99 100644 --- a/onyxia-api/pom.xml +++ b/onyxia-api/pom.xml @@ -100,6 +100,18 @@ jackson-databind + + com.github.erosb + everit-json-schema + 1.14.2 + + + + javax.annotation + javax.annotation-api + 1.3.2 + + io.fabric8 kubernetes-server-mock diff --git a/onyxia-api/src/main/java/fr/insee/onyxia/api/controller/RestExceptionHandler.java b/onyxia-api/src/main/java/fr/insee/onyxia/api/controller/RestExceptionHandler.java index 8e684ea3..e23c703b 100644 --- a/onyxia-api/src/main/java/fr/insee/onyxia/api/controller/RestExceptionHandler.java +++ b/onyxia-api/src/main/java/fr/insee/onyxia/api/controller/RestExceptionHandler.java @@ -1,6 +1,11 @@ package fr.insee.onyxia.api.controller; +import fr.insee.onyxia.api.controller.exception.SchemaNotFoundException; +import java.util.List; +import java.util.stream.Collectors; +import org.everit.json.schema.ValidationException; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.security.access.AccessDeniedException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; @@ -12,4 +17,59 @@ public class RestExceptionHandler { @ResponseStatus(value = HttpStatus.FORBIDDEN) @ExceptionHandler(AccessDeniedException.class) public void handleAccessDeniedException(Exception ignored) {} + + @ExceptionHandler(SchemaNotFoundException.class) + public ResponseEntity handleSchemaNotFoundException(SchemaNotFoundException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(ValidationException.class) + public ResponseEntity handleValidationException(ValidationException ex) { + List errors = + ex.getCausingExceptions().stream() + .map(ValidationException::getMessage) + .collect(Collectors.toList()); + + ErrorResponse errorResponse = + new ErrorResponse(HttpStatus.BAD_REQUEST.value(), "Validation failed", errors); + + return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); + } + + // Define the ErrorResponse class within the GlobalExceptionHandler + public static class ErrorResponse { + private int status; + private String message; + private List errors; + + public ErrorResponse(int status, String message, List errors) { + this.status = status; + this.message = message; + this.errors = errors; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public List getErrors() { + return errors; + } + + public void setErrors(List errors) { + this.errors = errors; + } + } } diff --git a/onyxia-api/src/main/java/fr/insee/onyxia/api/controller/exception/SchemaNotFoundException.java b/onyxia-api/src/main/java/fr/insee/onyxia/api/controller/exception/SchemaNotFoundException.java new file mode 100644 index 00000000..3387fe49 --- /dev/null +++ b/onyxia-api/src/main/java/fr/insee/onyxia/api/controller/exception/SchemaNotFoundException.java @@ -0,0 +1,8 @@ +package fr.insee.onyxia.api.controller.exception; + +public class SchemaNotFoundException extends RuntimeException { + + public SchemaNotFoundException(String schemaName) { + super("Schema not found: " + schemaName); + } +} diff --git a/onyxia-api/src/main/java/fr/insee/onyxia/api/controller/pub/JsonSchemaController.java b/onyxia-api/src/main/java/fr/insee/onyxia/api/controller/pub/JsonSchemaController.java new file mode 100644 index 00000000..00f4f695 --- /dev/null +++ b/onyxia-api/src/main/java/fr/insee/onyxia/api/controller/pub/JsonSchemaController.java @@ -0,0 +1,37 @@ +package fr.insee.onyxia.api.controller.pub; + +import com.fasterxml.jackson.databind.JsonNode; +import fr.insee.onyxia.api.controller.exception.SchemaNotFoundException; +import fr.insee.onyxia.api.services.JsonSchemaRegistryService; +import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/public/schemas") +public class JsonSchemaController { + + private final JsonSchemaRegistryService jsonSchemaRegistryService; + + @Autowired + public JsonSchemaController(JsonSchemaRegistryService jsonSchemaRegistryService) { + this.jsonSchemaRegistryService = jsonSchemaRegistryService; + } + + @GetMapping + public Map listSchemas() { + return jsonSchemaRegistryService.listSchemas(); + } + + @GetMapping("/{schemaName}") + public JsonNode getSchema(@PathVariable String schemaName) { + JsonNode schema = jsonSchemaRegistryService.getSchema(schemaName); + if (schema == null) { + throw new SchemaNotFoundException(schemaName); + } + return schema; + } +} diff --git a/onyxia-api/src/main/java/fr/insee/onyxia/api/dao/universe/CatalogLoader.java b/onyxia-api/src/main/java/fr/insee/onyxia/api/dao/universe/CatalogLoader.java index 5eaa802a..93173209 100644 --- a/onyxia-api/src/main/java/fr/insee/onyxia/api/dao/universe/CatalogLoader.java +++ b/onyxia-api/src/main/java/fr/insee/onyxia/api/dao/universe/CatalogLoader.java @@ -4,19 +4,25 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.zafarkhaja.semver.Version; import fr.insee.onyxia.api.configuration.CatalogWrapper; +import fr.insee.onyxia.api.services.JsonSchemaResolutionService; import fr.insee.onyxia.model.catalog.Pkg; import fr.insee.onyxia.model.helm.Chart; import fr.insee.onyxia.model.helm.Repository; import java.io.*; -import java.util.Iterator; -import java.util.List; -import java.util.Optional; +import java.util.*; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; +import org.everit.json.schema.Schema; +import org.everit.json.schema.loader.SchemaLoader; +import org.json.JSONObject; +import org.json.JSONTokener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; @@ -31,13 +37,16 @@ public class CatalogLoader { private static final Logger LOGGER = LoggerFactory.getLogger(CatalogLoader.class); private final ResourceLoader resourceLoader; - + private final JsonSchemaResolutionService jsonSchemaResolutionService; private final ObjectMapper mapperHelm; public CatalogLoader( - ResourceLoader resourceLoader, @Qualifier("helm") ObjectMapper mapperHelm) { + ResourceLoader resourceLoader, + @Qualifier("helm") ObjectMapper mapperHelm, + JsonSchemaResolutionService jsonSchemaResolutionService) { this.resourceLoader = resourceLoader; this.mapperHelm = mapperHelm; + this.jsonSchemaResolutionService = jsonSchemaResolutionService; } public void updateCatalog(CatalogWrapper cw) { @@ -184,10 +193,11 @@ public void extractDataFromTgz(InputStream in, Chart chart) throws IOException { ObjectMapper mapper = new ObjectMapper(); mapper.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { tarIn.transferTo(baos); - chart.setConfig(mapper.readTree(baos.toString(UTF_8))); + chart.setConfig( + jsonSchemaResolutionService.resolveReferences( + mapper.readTree(baos.toString(UTF_8)))); } } else if (entryName.endsWith(chartName + "/values.yaml") && !entryName.endsWith("charts/" + chartName + "/values.yaml")) { @@ -199,4 +209,80 @@ public void extractDataFromTgz(InputStream in, Chart chart) throws IOException { } } } + + public JsonNode resolveInternalReferences(JsonNode schemaNode, ObjectMapper objectMapper) + throws IOException { + // Convert the main schema JSON node to JSONObject + JSONObject schemaJson = new JSONObject(new JSONTokener(schemaNode.toString())); + + // Create a SchemaLoader + SchemaLoader loader = + SchemaLoader.builder() + .schemaJson(schemaJson) + .resolutionScope("file:///") // Setting a base URI for relative references + .build(); + + // Load and expand the schema + Schema schema = loader.load().build(); + + // Convert the resolved schema back to JsonNode + JSONObject resolvedSchemaJson = new JSONObject(schema.toString()); + return objectMapper.readTree(resolvedSchemaJson.toString()); + } + + public JsonNode resolveInternalReferences(JsonNode schemaNode) { + return resolveInternalReferences(schemaNode, schemaNode); + } + + private JsonNode resolveInternalReferences(JsonNode schemaNode, JsonNode rootNode) { + if (schemaNode.isObject()) { + ObjectNode objectNode = (ObjectNode) schemaNode; + Map updates = new HashMap<>(); + + Iterator> fields = objectNode.fields(); + while (fields.hasNext()) { + Map.Entry field = fields.next(); + if (field.getKey().equals("$ref") && field.getValue().isTextual()) { + String ref = field.getValue().asText(); + if (ref.startsWith("#/definitions/")) { + String refName = ref.substring("#/definitions/".length()); + JsonNode refNode = rootNode.at("/definitions/" + refName); + if (!refNode.isMissingNode()) { + JsonNode resolvedNode = + resolveInternalReferences(refNode.deepCopy(), rootNode); + updates.putAll(convertToMap((ObjectNode) resolvedNode)); + updates.put("$ref", null); + } + } + } else { + updates.put( + field.getKey(), resolveInternalReferences(field.getValue(), rootNode)); + } + } + + for (Map.Entry update : updates.entrySet()) { + if (update.getValue() == null) { + objectNode.remove(update.getKey()); + } else { + objectNode.set(update.getKey(), update.getValue()); + } + } + } else if (schemaNode.isArray()) { + ArrayNode arrayNode = (ArrayNode) schemaNode; + for (int i = 0; i < arrayNode.size(); i++) { + arrayNode.set(i, resolveInternalReferences(arrayNode.get(i), rootNode)); + } + } + return schemaNode; + } + + private Map convertToMap(ObjectNode objectNode) { + Map map = new HashMap<>(); + Iterator> fields = objectNode.fields(); + while (fields.hasNext()) { + Map.Entry field = fields.next(); + map.put(field.getKey(), field.getValue()); + } + return map; + } } diff --git a/onyxia-api/src/main/java/fr/insee/onyxia/api/services/JsonSchemaRegistryService.java b/onyxia-api/src/main/java/fr/insee/onyxia/api/services/JsonSchemaRegistryService.java new file mode 100644 index 00000000..315ea49d --- /dev/null +++ b/onyxia-api/src/main/java/fr/insee/onyxia/api/services/JsonSchemaRegistryService.java @@ -0,0 +1,89 @@ +package fr.insee.onyxia.api.services; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Service +public class JsonSchemaRegistryService { + + private static final String SCHEMA_DIRECTORY = "/schemas"; // Resource directory + private final ObjectMapper objectMapper; + private final Map schemaRegistry; + + @Value("${external.schema.directory:/external-schemas}") // External directory path + private String externalSchemaDirectory; + + public JsonSchemaRegistryService() { + this.objectMapper = new ObjectMapper(); + this.schemaRegistry = new HashMap<>(); + } + + @PostConstruct + private void loadSchemas() throws IOException, URISyntaxException { + // Load initial schemas from resources + loadResourceSchemas(); + + // Load schemas from the external directory if it exists + loadExternalSchemas(); + } + + private void loadResourceSchemas() throws IOException, URISyntaxException { + Path resourcePath = + Paths.get(JsonSchemaRegistryService.class.getResource(SCHEMA_DIRECTORY).toURI()); + Files.walk(resourcePath) + .filter(Files::isRegularFile) + .forEach(path -> loadSchema(resourcePath, path)); + } + + private void loadExternalSchemas() throws IOException { + Path externalPath = Paths.get(externalSchemaDirectory); + if (Files.exists(externalPath)) { + Files.walk(externalPath) + .filter(Files::isRegularFile) + .forEach(path -> loadSchema(externalPath, path)); + } + } + + private void loadSchema(Path basePath, Path path) { + try { + JsonNode schema = objectMapper.readTree(path.toFile()); + String key = generateKey(basePath, path); + schemaRegistry.put(key, schema); + } catch (IOException e) { + throw new RuntimeException("Failed to load schema: " + path.getFileName(), e); + } + } + + private String generateKey(Path basePath, Path path) { + // Remove the base directory part from the path and replace the file separators with dots + Path relativePath = basePath.relativize(path); + return relativePath.toString().replace(File.separatorChar, '/'); + } + + public void refreshExternalSchemas() throws IOException { + loadExternalSchemas(); + } + + public Map listSchemas() { + return new HashMap<>(schemaRegistry); + } + + public JsonNode getSchema(String schemaName) { + return schemaRegistry.get(schemaName); + } + + public void overwriteSchema(String schemaName, JsonNode newSchema) { + schemaRegistry.put(schemaName, newSchema); + } +} diff --git a/onyxia-api/src/main/java/fr/insee/onyxia/api/services/JsonSchemaResolutionService.java b/onyxia-api/src/main/java/fr/insee/onyxia/api/services/JsonSchemaResolutionService.java new file mode 100644 index 00000000..9c43b77b --- /dev/null +++ b/onyxia-api/src/main/java/fr/insee/onyxia/api/services/JsonSchemaResolutionService.java @@ -0,0 +1,95 @@ +package fr.insee.onyxia.api.services; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class JsonSchemaResolutionService { + + private final ObjectMapper objectMapper; + private final JsonSchemaRegistryService registryService; + + @Autowired + public JsonSchemaResolutionService(JsonSchemaRegistryService registryService) { + this.objectMapper = new ObjectMapper(); + this.registryService = registryService; + } + + public JsonNode resolveReferences(JsonNode schemaNode) { + return resolveReferences(schemaNode, schemaNode); + } + + private JsonNode resolveReferences(JsonNode schemaNode, JsonNode rootNode) { + if (schemaNode.isObject()) { + ObjectNode objectNode = (ObjectNode) schemaNode; + Iterator> fields = objectNode.fields(); + Map updates = new HashMap<>(); + + while (fields.hasNext()) { + Map.Entry field = fields.next(); + JsonNode fieldValue = field.getValue(); + + if (field.getKey().equals("$ref") && fieldValue.isTextual()) { + String ref = fieldValue.asText(); + JsonNode refNode = null; + if (ref.startsWith("#/definitions/")) { + refNode = rootNode.at(ref.substring(1)); + } else { + refNode = registryService.getSchema(ref); + } + + if (refNode != null && !refNode.isMissingNode()) { + JsonNode resolvedNode = resolveReferences(refNode.deepCopy(), rootNode); + updates.putAll(convertToMap((ObjectNode) resolvedNode)); + updates.put("$ref", null); + } + } else if (fieldValue.isObject() + && fieldValue.has("x-onyxia") + && fieldValue.get("x-onyxia").has("overwriteSchemaWith")) { + String overrideSchemaName = + fieldValue.get("x-onyxia").get("overwriteSchemaWith").asText(); + JsonNode overrideSchemaNode = registryService.getSchema(overrideSchemaName); + + if (overrideSchemaNode != null && !overrideSchemaNode.isMissingNode()) { + JsonNode resolvedNode = + resolveReferences(overrideSchemaNode.deepCopy(), rootNode); + updates.put(field.getKey(), resolvedNode); + } + } else if (fieldValue.isObject() || fieldValue.isArray()) { + updates.put(field.getKey(), resolveReferences(fieldValue, rootNode)); + } + } + + for (Map.Entry update : updates.entrySet()) { + if (update.getValue() == null) { + objectNode.remove(update.getKey()); + } else { + objectNode.set(update.getKey(), update.getValue()); + } + } + } else if (schemaNode.isArray()) { + ArrayNode arrayNode = (ArrayNode) schemaNode; + for (int i = 0; i < arrayNode.size(); i++) { + arrayNode.set(i, resolveReferences(arrayNode.get(i), rootNode)); + } + } + return schemaNode; + } + + private Map convertToMap(ObjectNode objectNode) { + Map map = new HashMap<>(); + Iterator> fields = objectNode.fields(); + while (fields.hasNext()) { + Map.Entry field = fields.next(); + map.put(field.getKey(), field.getValue()); + } + return map; + } +} diff --git a/onyxia-api/src/main/java/fr/insee/onyxia/api/services/impl/HelmAppsService.java b/onyxia-api/src/main/java/fr/insee/onyxia/api/services/impl/HelmAppsService.java index 5be9b072..817ee04b 100644 --- a/onyxia-api/src/main/java/fr/insee/onyxia/api/services/impl/HelmAppsService.java +++ b/onyxia-api/src/main/java/fr/insee/onyxia/api/services/impl/HelmAppsService.java @@ -40,6 +40,11 @@ import java.util.concurrent.TimeoutException; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.FastDateFormat; +import org.everit.json.schema.Schema; +import org.everit.json.schema.ValidationException; +import org.everit.json.schema.loader.SchemaLoader; +import org.json.JSONObject; +import org.json.JSONTokener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -104,7 +109,15 @@ public Collection installApp( Map fusion, final boolean skipTlsVerify, final String caFile) - throws IOException, TimeoutException, InterruptedException { + throws IOException, TimeoutException, InterruptedException, ValidationException { + + JSONObject jsonSchema = new JSONObject(new JSONTokener(pkg.getConfig().toString())); + // Load the schema + Schema schema = SchemaLoader.load(jsonSchema); + // Convert the options map to a JSONObject + JSONObject jsonObject = new JSONObject(fusion); + // Validate the options object against the schema + schema.validate(jsonObject); File values = File.createTempFile("values", ".yaml"); mapperHelm.writeValue(values, fusion); diff --git a/onyxia-api/src/main/resources/schemas/ide/custom-image.json b/onyxia-api/src/main/resources/schemas/ide/custom-image.json new file mode 100644 index 00000000..d9dd8f6d --- /dev/null +++ b/onyxia-api/src/main/resources/schemas/ide/custom-image.json @@ -0,0 +1,7 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "custom image", + "type": "boolean", + "description": "use a custom jupyter docker image", + "default": false +} \ No newline at end of file diff --git a/onyxia-api/src/main/resources/schemas/ide/git.json b/onyxia-api/src/main/resources/schemas/ide/git.json new file mode 100644 index 00000000..419dfeb6 --- /dev/null +++ b/onyxia-api/src/main/resources/schemas/ide/git.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GIT", + "description": "Git user configuration", + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Add git config inside your environment", + "default": true + }, + "name": { + "type": "string", + "description": "user name for git", + "default": "", + "x-onyxia": { + "overwriteDefaultWith": "{{git.name}}" + }, + "hidden": { + "value": false, + "path": "enabled", + "isPathRelative": true + } + }, + "email": { + "type": "string", + "description": "user email for git", + "default": "", + "x-onyxia": { + "overwriteDefaultWith": "{{git.email}}" + }, + "hidden": { + "value": false, + "path": "enabled", + "isPathRelative": true + } + }, + "cache": { + "type": "string", + "description": "duration in seconds of the credentials cache duration", + "default": "", + "x-onyxia": { + "overwriteDefaultWith": "{{git.credentials_cache_duration}}" + }, + "hidden": { + "value": false, + "path": "enabled", + "isPathRelative": true + } + }, + "token": { + "type": "string", + "description": "personal access token", + "default": "", + "render": "password", + "x-onyxia": { + "overwriteDefaultWith": "{{git.token}}" + }, + "hidden": { + "value": false, + "path": "enabled", + "isPathRelative": true + } + }, + "repository": { + "type": "string", + "description": "Repository url", + "default": "", + "x-onyxia": { + "overwriteDefaultWith": "{{git.project}}" + }, + "hidden": { + "value": false, + "path": "enabled", + "isPathRelative": true + } + }, + "branch": { + "type": "string", + "description": "Branch automatically checked out", + "default": "", + "hidden": { + "value": false, + "path": "enabled", + "isPathRelative": true + } + } + } +} \ No newline at end of file diff --git a/onyxia-api/src/main/resources/schemas/ide/ingress.json b/onyxia-api/src/main/resources/schemas/ide/ingress.json new file mode 100644 index 00000000..83af0879 --- /dev/null +++ b/onyxia-api/src/main/resources/schemas/ide/ingress.json @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Ingress", + "description": "Ingress parameters", + "type": "object", + "properties": { + "enabled": { + "description": "Enable Ingress", + "type": "boolean", + "default": true, + "x-onyxia": { + "hidden": true, + "overwriteDefaultWith": "k8s.ingress" + } + }, + "hostname": { + "type": "string", + "form": true, + "title": "Hostname", + "x-onyxia": { + "hidden": true, + "overwriteDefaultWith": "{{project.id}}-{{k8s.randomSubdomain}}-0.{{k8s.domain}}" + } + }, + "userHostname": { + "type": "string", + "form": true, + "title": "Hostname", + "x-onyxia": { + "hidden": true, + "overwriteDefaultWith": "{{project.id}}-{{k8s.randomSubdomain}}-user.{{k8s.domain}}" + } + }, + "ingressClassName": { + "type": "string", + "form": true, + "title": "ingressClassName", + "default": "", + "x-onyxia": { + "hidden": true, + "overwriteDefaultWith": "{{k8s.ingressClassName}}" + } + }, + "useCertManager": { + "type": "boolean", + "description": "Whether CertManager should be used to generate a certificate", + "default": false, + "x-onyxia": { + "hidden": true, + "overwriteDefaultWith": "k8s.useCertManager" + } + }, + "certManagerClusterIssuer":{ + "type": "string", + "description": "certManager cluster issuer", + "title": "CertManager Cluster Issuer", + "default": "", + "x-onyxia": { + "hidden": true, + "overwriteDefaultWith": "k8s.certManagerClusterIssuer" + } + } + } +} \ No newline at end of file diff --git a/onyxia-api/src/main/resources/schemas/ide/init.json b/onyxia-api/src/main/resources/schemas/ide/init.json new file mode 100644 index 00000000..686cd406 --- /dev/null +++ b/onyxia-api/src/main/resources/schemas/ide/init.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Init", + "description": "Init parameters", + "type": "object", + "properties": { + "regionInit": { + "type": "string", + "description": "region initialization script", + "default": "", + "x-onyxia": { + "hidden": true, + "overwriteDefaultWith": "{{k8s.initScriptUrl}}" + } + }, + "personalInit": { + "type": "string", + "description": "user initialization script", + "default": "" + }, + "personalInitArgs": { + "type": "string", + "description": "args for user initialization script", + "default": "" + } + } +} \ No newline at end of file diff --git a/onyxia-api/src/main/resources/schemas/ide/message.json b/onyxia-api/src/main/resources/schemas/ide/message.json new file mode 100644 index 00000000..e7c91d13 --- /dev/null +++ b/onyxia-api/src/main/resources/schemas/ide/message.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "message", + "type": "object", + "description": "add a message in the notes", + "x-onyxia":{ + "hidden": true + }, + "properties": { + "fr":{ + "type": "string", + "description":"message à ajouter dans les notes", + "default": "" + }, + "en":{ + "type": "string", + "description": "message to add in notes", + "default": "" + } + } +} diff --git a/onyxia-api/src/main/resources/schemas/ide/network-policy.json b/onyxia-api/src/main/resources/schemas/ide/network-policy.json new file mode 100644 index 00000000..bf4665bf --- /dev/null +++ b/onyxia-api/src/main/resources/schemas/ide/network-policy.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "Network Policy", + "description": "Define access policy to the service", + "properties": { + "enabled": { + "type": "boolean", + "title": "Enable network policy", + "description": "Only pod from the same namespace will be allowed", + "default": false + }, + "from": { + "type": "array", + "description": "Array of source allowed to have network access to your service", + "default": [] + } + } +} \ No newline at end of file diff --git a/onyxia-api/src/main/resources/schemas/ide/openshiftSCC.json b/onyxia-api/src/main/resources/schemas/ide/openshiftSCC.json new file mode 100644 index 00000000..c2898a8a --- /dev/null +++ b/onyxia-api/src/main/resources/schemas/ide/openshiftSCC.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Openshift SCC", + "description": "configuration for openshift compatibility", + "type": "object", + "properties": { + "enabled": { + "description": "enable rolebinding with openshift scc", + "type": "boolean", + "default": false + }, + "scc": { + "type": "string", + "description": "name of scc for rolebinding", + "default": "anyuid" + } + } +} diff --git a/onyxia-api/src/main/resources/schemas/ide/password.json b/onyxia-api/src/main/resources/schemas/ide/password.json new file mode 100644 index 00000000..613d0187 --- /dev/null +++ b/onyxia-api/src/main/resources/schemas/ide/password.json @@ -0,0 +1,10 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "string", + "description": "Password", + "default": "changeme", + "render": "password", + "x-onyxia": { + "overwriteDefaultWith": "{{service.oneTimePassword}}" + } +} \ No newline at end of file diff --git a/onyxia-api/src/main/resources/schemas/ide/persistance.json b/onyxia-api/src/main/resources/schemas/ide/persistance.json new file mode 100644 index 00000000..70e925e2 --- /dev/null +++ b/onyxia-api/src/main/resources/schemas/ide/persistance.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Configuration for persistence", + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Create a persistent volume", + "default": true + }, + "size": { + "type": "string", + "title": "Persistent volume size", + "description": "Size of the persistent volume", + "default": "10Gi", + "form": true, + "render": "slider", + "sliderMin": 1, + "sliderMax": 100, + "sliderStep": 1, + "sliderUnit": "Gi", + "hidden": { + "value": false, + "path": "enabled", + "isPathRelative": true + } + } + } +} \ No newline at end of file diff --git a/onyxia-api/src/main/resources/schemas/ide/resources-gpu.json b/onyxia-api/src/main/resources/schemas/ide/resources-gpu.json new file mode 100644 index 00000000..e60858f3 --- /dev/null +++ b/onyxia-api/src/main/resources/schemas/ide/resources-gpu.json @@ -0,0 +1,87 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Resources", + "description": "Your service will have at least the requested resources and never more than its limits. No limit for a resource and you can consume everything left on the host machine.", + "type": "object", + "properties": { + "requests": { + "description": "Guaranteed resources", + "type": "object", + "properties": { + "cpu": { + "description": "The amount of cpu guaranteed", + "title": "CPU", + "type": "string", + "default": "100m", + "render": "slider", + "sliderMin": 50, + "sliderMax": 40000, + "sliderStep": 50, + "sliderUnit": "m", + "sliderExtremity": "down", + "sliderExtremitySemantic": "guaranteed", + "sliderRangeId": "cpu" + }, + "memory": { + "description": "The amount of memory guaranteed", + "title": "memory", + "type": "string", + "default": "2Gi", + "render": "slider", + "sliderMin": 1, + "sliderMax": 200, + "sliderStep": 1, + "sliderUnit": "Gi", + "sliderExtremity": "down", + "sliderExtremitySemantic": "guaranteed", + "sliderRangeId": "memory" + } + } + }, + "limits": { + "description": "max resources", + "type": "object", + "properties": { + "cpu": { + "description": "The maximum amount of cpu", + "title": "CPU", + "type": "string", + "default": "30000m", + "render": "slider", + "sliderMin": 50, + "sliderMax": 40000, + "sliderStep": 50, + "sliderUnit": "m", + "sliderExtremity": "up", + "sliderExtremitySemantic": "Maximum", + "sliderRangeId": "cpu" + }, + "memory": { + "description": "The maximum amount of memory", + "title": "Memory", + "type": "string", + "default": "50Gi", + "render": "slider", + "sliderMin": 1, + "sliderMax": 200, + "sliderStep": 1, + "sliderUnit": "Gi", + "sliderExtremity": "up", + "sliderExtremitySemantic": "Maximum", + "sliderRangeId": "memory" + }, + "nvidia.com/gpu": { + "description": "GPU to allocate to this instance. This is also requested", + "type": "string", + "default": "1", + "render": "slider", + "sliderMin": 1, + "sliderMax": 3, + "sliderStep": 1, + "sliderUnit": "" + } + } + } + } + +} diff --git a/onyxia-api/src/main/resources/schemas/ide/resources.json b/onyxia-api/src/main/resources/schemas/ide/resources.json new file mode 100644 index 00000000..a1805592 --- /dev/null +++ b/onyxia-api/src/main/resources/schemas/ide/resources.json @@ -0,0 +1,76 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Resources", + "description": "Your service will have at least the requested resources and never more than its limits. No limit for a resource and you can consume everything left on the host machine.", + "type": "object", + "properties": { + "requests": { + "description": "Guaranteed resources", + "type": "object", + "properties": { + "cpu": { + "description": "The amount of cpu guaranteed", + "title": "CPU", + "type": "string", + "default": "100m", + "render": "slider", + "sliderMin": 50, + "sliderMax": 40000, + "sliderStep": 50, + "sliderUnit": "m", + "sliderExtremity": "down", + "sliderExtremitySemantic": "guaranteed", + "sliderRangeId": "cpu" + }, + "memory": { + "description": "The amount of memory guaranteed", + "title": "memory", + "type": "string", + "default": "2Gi", + "render": "slider", + "sliderMin": 1, + "sliderMax": 200, + "sliderStep": 1, + "sliderUnit": "Gi", + "sliderExtremity": "down", + "sliderExtremitySemantic": "guaranteed", + "sliderRangeId": "memory" + } + } + }, + "limits": { + "description": "max resources", + "type": "object", + "properties": { + "cpu": { + "description": "The maximum amount of cpu", + "title": "CPU", + "type": "string", + "default": "30000m", + "render": "slider", + "sliderMin": 50, + "sliderMax": 40000, + "sliderStep": 50, + "sliderUnit": "m", + "sliderExtremity": "up", + "sliderExtremitySemantic": "Maximum", + "sliderRangeId": "cpu" + }, + "memory": { + "description": "The maximum amount of memory", + "title": "Memory", + "type": "string", + "default": "50Gi", + "render": "slider", + "sliderMin": 1, + "sliderMax": 200, + "sliderStep": 1, + "sliderUnit": "Gi", + "sliderExtremity": "up", + "sliderExtremitySemantic": "Maximum", + "sliderRangeId": "memory" + } + } + } + } +} diff --git a/onyxia-api/src/main/resources/schemas/ide/role.json b/onyxia-api/src/main/resources/schemas/ide/role.json new file mode 100644 index 00000000..e5832fb6 --- /dev/null +++ b/onyxia-api/src/main/resources/schemas/ide/role.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Role", + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "allow your service to access your namespace ressources", + "default": true + }, + "role": { + "type": "string", + "description": "bind your service account to this kubernetes default role", + "default": "view", + "hidden": { + "value": false, + "path": "kubernetes/enabled" + }, + "enum": [ + "view", + "edit", + "admin" + ] + } + } +} \ No newline at end of file diff --git a/onyxia-api/src/main/resources/schemas/ide/route.json b/onyxia-api/src/main/resources/schemas/ide/route.json new file mode 100644 index 00000000..c003c0a4 --- /dev/null +++ b/onyxia-api/src/main/resources/schemas/ide/route.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Route", + "description": "Route parameters", + "type": "object", + "properties": { + "enabled": { + "description": "Enable route", + "type": "boolean", + "default": false, + "x-onyxia": { + "hidden": true, + "overwriteDefaultWith": "k8s.route" + } + }, + "hostname": { + "type": "string", + "form": true, + "title": "Hostname", + "x-onyxia": { + "hidden": true, + "overwriteDefaultWith": "{{project.id}}-{{k8s.randomSubdomain}}-0.{{k8s.domain}}" + } + }, + "userHostname": { + "type": "string", + "form": true, + "title": "Hostname", + "x-onyxia": { + "hidden": true, + "overwriteDefaultWith": "{{project.id}}-{{k8s.randomSubdomain}}-user.{{k8s.domain}}" + } + } + } +} \ No newline at end of file diff --git a/onyxia-api/src/main/resources/schemas/ide/s3.json b/onyxia-api/src/main/resources/schemas/ide/s3.json new file mode 100644 index 00000000..d519eff5 --- /dev/null +++ b/onyxia-api/src/main/resources/schemas/ide/s3.json @@ -0,0 +1,76 @@ +{ + "title": "S3 Configuration", + "description": "Configuration of temporary identity for AWS S3", + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Add S3 temporary identity inside your environment", + "default": true + }, + "accessKeyId": { + "description": "AWS Access Key", + "type": "string", + "x-onyxia": { + "overwriteDefaultWith": "{{s3.AWS_ACCESS_KEY_ID}}" + }, + "hidden": { + "value": false, + "path": "enabled", + "isPathRelative": true + } + }, + "endpoint": { + "description": "AWS S3 Endpoint", + "type": "string", + "x-onyxia": { + "overwriteDefaultWith": "{{s3.AWS_S3_ENDPOINT}}" + }, + "hidden": { + "value": false, + "path": "enabled", + "isPathRelative": true + } + }, + "defaultRegion": { + "description": "AWS S3 default region", + "type": "string", + "x-onyxia": { + "overwriteDefaultWith": "{{s3.AWS_DEFAULT_REGION}}" + }, + "hidden": { + "value": false, + "path": "enabled", + "isPathRelative": true + } + }, + "secretAccessKey": { + "description": "AWS S3 secret access key", + "type": "string", + "render": "password", + "x-onyxia": { + "overwriteDefaultWith": "{{s3.AWS_SECRET_ACCESS_KEY}}" + }, + "hidden": { + "value": false, + "path": "enabled", + "isPathRelative": true + } + }, + "sessionToken": { + "description": "AWS S3 session Token", + "type": "string", + "render": "password", + "x-onyxia": { + "overwriteDefaultWith": "{{s3.AWS_SESSION_TOKEN}}" + }, + "hidden": { + "value": false, + "path": "enabled", + "isPathRelative": true + } + } + }, + "required": ["enabled"] +} + diff --git a/onyxia-api/src/main/resources/schemas/ide/vault.json b/onyxia-api/src/main/resources/schemas/ide/vault.json new file mode 100644 index 00000000..150e4a0e --- /dev/null +++ b/onyxia-api/src/main/resources/schemas/ide/vault.json @@ -0,0 +1,72 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Vault", + "description": "Configuration of vault client", + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Add vault temporary identity inside your environment", + "default": true + }, + "token": { + "description": "token vault", + "type": "string", + "render": "password", + "x-onyxia": { + "overwriteDefaultWith": "{{vault.VAULT_TOKEN}}" + }, + "hidden": { + "value": false, + "path": "enabled", + "isPathRelative": true + } + }, + "url": { + "description": "url of vault server", + "type": "string", + "x-onyxia": { + "overwriteDefaultWith": "{{vault.VAULT_ADDR}}" + }, + "hidden": { + "value": false, + "path": "enabled", + "isPathRelative": true + } + }, + "mount": { + "description": "mount of the v2 secret engine", + "type": "string", + "x-onyxia": { + "overwriteDefaultWith": "{{vault.VAULT_MOUNT}}" + }, + "hidden": { + "value": false, + "path": "enabled", + "isPathRelative": true + } + }, + "directory": { + "description": "top level directory", + "type": "string", + "x-onyxia": { + "overwriteDefaultWith": "{{vault.VAULT_TOP_DIR}}" + }, + "hidden": { + "value": false, + "path": "enabled", + "isPathRelative": true + } + }, + "secret": { + "description": "the path of the secret to convert into a list of environment variables", + "type": "string", + "default": "", + "hidden": { + "value": false, + "path": "enabled", + "isPathRelative": true + } + } + } +} \ No newline at end of file diff --git a/onyxia-api/src/main/resources/schemas/nodeSelector.json b/onyxia-api/src/main/resources/schemas/nodeSelector.json new file mode 100644 index 00000000..79849804 --- /dev/null +++ b/onyxia-api/src/main/resources/schemas/nodeSelector.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Node Selector", + "type": "object", + "description": "Node selector constraints for the pod", + "additionalProperties": { + "type": "string", + "description": "Key-value pairs to select nodes" + }, + "x-onyxia": { + "hidden": false, + "overwriteDefaultWith": "region.nodeSelector" + } +} \ No newline at end of file diff --git a/onyxia-api/src/main/resources/schemas/tolerations.json b/onyxia-api/src/main/resources/schemas/tolerations.json new file mode 100644 index 00000000..8b85cafd --- /dev/null +++ b/onyxia-api/src/main/resources/schemas/tolerations.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Kubernetes Tolerations", + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "The taint key that the toleration applies to." + }, + "operator": { + "type": "string", + "enum": ["Exists", "Equal"], + "description": "The operator indicates the relationship between the key and value." + }, + "value": { + "type": "string", + "description": "The taint value that the toleration matches to." + }, + "effect": { + "type": "string", + "enum": ["NoSchedule", "PreferNoSchedule", "NoExecute"], + "description": "The effect indicates what action should be taken when the toleration is matched." + }, + "tolerationSeconds": { + "type": "integer", + "description": "The period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. Represented in seconds." + } + }, + "required": ["key", "operator", "effect"], + "additionalProperties": false + } + } + \ No newline at end of file diff --git a/onyxia-api/src/test/java/fr/insee/onyxia/api/dao/universe/CatalogLoaderTest.java b/onyxia-api/src/test/java/fr/insee/onyxia/api/dao/universe/CatalogLoaderTest.java index 4739faed..851bc883 100644 --- a/onyxia-api/src/test/java/fr/insee/onyxia/api/dao/universe/CatalogLoaderTest.java +++ b/onyxia-api/src/test/java/fr/insee/onyxia/api/dao/universe/CatalogLoaderTest.java @@ -7,6 +7,8 @@ import fr.insee.onyxia.api.configuration.CatalogWrapper; import fr.insee.onyxia.api.configuration.CustomObjectMapper; +import fr.insee.onyxia.api.services.JsonSchemaRegistryService; +import fr.insee.onyxia.api.services.JsonSchemaResolutionService; import fr.insee.onyxia.api.util.TestUtils; import fr.insee.onyxia.model.helm.Chart; import java.util.List; @@ -21,7 +23,13 @@ import org.springframework.util.CollectionUtils; @ExtendWith(SpringExtension.class) -@SpringBootTest(classes = {CatalogLoader.class, CustomObjectMapper.class}) +@SpringBootTest( + classes = { + CatalogLoader.class, + CustomObjectMapper.class, + JsonSchemaResolutionService.class, + JsonSchemaRegistryService.class + }) public class CatalogLoaderTest { @Autowired CatalogLoader catalogLoader; diff --git a/onyxia-model/src/main/java/fr/insee/onyxia/model/region/Region.java b/onyxia-model/src/main/java/fr/insee/onyxia/model/region/Region.java index b13af9d4..e720e450 100644 --- a/onyxia-model/src/main/java/fr/insee/onyxia/model/region/Region.java +++ b/onyxia-model/src/main/java/fr/insee/onyxia/model/region/Region.java @@ -199,50 +199,13 @@ public static class Services { private Expose expose; private Server server; private Monitoring monitoring; - private String initScript; private String allowedURIPattern = "^https://"; private Quotas quotas = new Quotas(); - private DefaultConfiguration defaultConfiguration = new DefaultConfiguration(); private K8sPublicEndpoint k8sPublicEndpoint = new K8sPublicEndpoint(); - private CustomInitScript customInitScript = new CustomInitScript(); - private OpenshiftSCC openshiftSCC = new OpenshiftSCC(); - private Map customValues = new HashMap<>(); private NamespaceAnnotationsDynamic namespaceAnnotationsDynamic = new NamespaceAnnotationsDynamic(); - public DefaultConfiguration getDefaultConfiguration() { - return defaultConfiguration; - } - - public void setDefaultConfiguration(DefaultConfiguration defaultConfiguration) { - this.defaultConfiguration = defaultConfiguration; - } - - public Map getCustomValues() { - return customValues; - } - - public void setCustomValues(Map customValues) { - this.customValues = customValues; - } - - public CustomInitScript getCustomInitScript() { - return customInitScript; - } - - public void setCustomInitScript(CustomInitScript customInitScript) { - this.customInitScript = customInitScript; - } - - public OpenshiftSCC getOpenshiftSCC() { - return openshiftSCC; - } - - public void setOpenshiftSCC(OpenshiftSCC openshiftSCC) { - this.openshiftSCC = openshiftSCC; - } - public boolean isSingleNamespace() { return singleNamespace; } @@ -340,14 +303,6 @@ public void setMonitoring(Monitoring monitoring) { this.monitoring = monitoring; } - public String getInitScript() { - return initScript; - } - - public void setInitScript(String initScript) { - this.initScript = initScript; - } - public String getAllowedURIPattern() { return allowedURIPattern; } @@ -429,250 +384,6 @@ public void setUserAttributes(List userAttributes) { } } - public static class DefaultConfiguration { - private boolean IPProtection = false; - private boolean networkPolicy = false; - private List from = new ArrayList<>(); - private List tolerations = new ArrayList<>(); - private Object nodeSelector; - private Object startupProbe; - private Kafka kafka = new Kafka(); - private Sliders sliders = new Sliders(); - private Resources resources = new Resources(); - - public boolean isIPProtection() { - return IPProtection; - } - - public void setIPProtection(boolean IPProtection) { - this.IPProtection = IPProtection; - } - - public boolean isNetworkPolicy() { - return networkPolicy; - } - - public void setNetworkPolicy(boolean networkPolicy) { - this.networkPolicy = networkPolicy; - } - - public List getFrom() { - return from; - } - - public void setFrom(List from) { - this.from = from; - } - - public List getTolerations() { - return tolerations; - } - - public void setTolerations(List tolerations) { - this.tolerations = tolerations; - } - - public Object getNodeSelector() { - return nodeSelector; - } - - public void setNodeSelector(Object nodeSelector) { - this.nodeSelector = nodeSelector; - } - - public Object getStartupProbe() { - return startupProbe; - } - - public void setStartupProbe(Object startupProbe) { - this.startupProbe = startupProbe; - } - - public Kafka getKafka() { - return kafka; - } - - public void setKafka(Kafka kafka) { - this.kafka = kafka; - } - - public Sliders getSliders() { - return sliders; - } - - public void setSliders(Sliders sliders) { - this.sliders = sliders; - } - - public Resources getResources() { - return resources; - } - - public void setResources(Resources resources) { - this.resources = resources; - } - - public static class Kafka { - @JsonProperty("URL") - private String url; - - private String topicName; - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getTopicName() { - return topicName; - } - - public void setTopicName(String topicName) { - this.topicName = topicName; - } - } - - public static class Sliders { - - Slider cpu; - Slider memory; - Slider gpu; - Slider disk; - - public Slider getCpu() { - return cpu; - } - - public void setCpu(Slider cpu) { - this.cpu = cpu; - } - - public Slider getMemory() { - return memory; - } - - public void setMemory(Slider memory) { - this.memory = memory; - } - - public Slider getGpu() { - return gpu; - } - - public void setGpu(Slider gpu) { - this.gpu = gpu; - } - - public Slider getDisk() { - return disk; - } - - public void setDisk(Slider disk) { - this.disk = disk; - } - - public static class Slider { - - Integer sliderMin; - Integer sliderMax; - Integer sliderStep; - String sliderUnit; - - public Integer getSliderMin() { - return sliderMin; - } - - public void setSliderMin(Integer sliderMin) { - this.sliderMin = sliderMin; - } - - public Integer getSliderMax() { - return sliderMax; - } - - public void setSliderMax(Integer sliderMax) { - this.sliderMax = sliderMax; - } - - public Integer getSliderStep() { - return sliderStep; - } - - public void setSliderStep(Integer sliderStep) { - this.sliderStep = sliderStep; - } - - public String getSliderUnit() { - return sliderUnit; - } - - public void setSliderUnit(String sliderUnit) { - this.sliderUnit = sliderUnit; - } - } - } - - public static class Resources { - private String cpuRequest; - private String cpuLimit; - private String memoryRequest; - private String memoryLimit; - private String disk; - private String gpu; - - public String getCpuRequest() { - return cpuRequest; - } - - public void setCpuRequest(String cpuRequest) { - this.cpuRequest = cpuRequest; - } - - public String getCpuLimit() { - return cpuLimit; - } - - public void setCpuLimit(String cpuLimit) { - this.cpuLimit = cpuLimit; - } - - public String getMemoryRequest() { - return memoryRequest; - } - - public void setMemoryRequest(String memoryRequest) { - this.memoryRequest = memoryRequest; - } - - public String getMemoryLimit() { - return memoryLimit; - } - - public void setMemoryLimit(String memoryLimit) { - this.memoryLimit = memoryLimit; - } - - public String getDisk() { - return disk; - } - - public void setDisk(String disk) { - this.disk = disk; - } - - public String getGpu() { - return gpu; - } - - public void setGpu(String gpu) { - this.gpu = gpu; - } - } - } - public static class Quotas { // could be deprecated as userQuota/groupQuota is enough private boolean enabled = false; @@ -764,19 +475,9 @@ public void setUrlPattern(String urlPattern) { @Schema(description = "") public static class Data { - private Atlas atlas; - @JsonProperty("S3") private S3 s3; - public Atlas getAtlas() { - return atlas; - } - - public void setAtlas(Atlas atlas) { - this.atlas = atlas; - } - public S3 getS3() { return s3; } @@ -786,30 +487,6 @@ public void setS3(S3 s3) { } } - public static class Atlas { - - @JsonProperty("URL") - private String url; - - private OIDCConfiguration oidcConfiguration = null; - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public OIDCConfiguration getOidcConfiguration() { - return oidcConfiguration; - } - - public void setOidcConfiguration(OIDCConfiguration oidcConfiguration) { - this.oidcConfiguration = oidcConfiguration; - } - } - @Schema(description = "Vault Configuration") public static class Vault { @@ -942,51 +619,6 @@ public void setOidcConfiguration(OIDCConfiguration oidcConfiguration) { } } - public static class CustomInitScript { - - @JsonProperty("URL") - private String url; - - private String checksum; - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getChecksum() { - return checksum; - } - - public void setChecksum(String checksum) { - this.checksum = checksum; - } - } - - public static class OpenshiftSCC { - private boolean enabled; - private String scc; - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public String getScc() { - return scc; - } - - public void setScc(String scc) { - this.scc = scc; - } - } - @Schema(description = "Configuration to be used by the S3 client associated to Onyxia") public static class S3 {