From ae2500fb2fa9a180b4db30ede59eb001126536c4 Mon Sep 17 00:00:00 2001 From: Michal Vala Date: Fri, 11 Oct 2019 17:52:38 +0200 Subject: [PATCH] load devfile schema for validation by it's apiVersion (#14834) Signed-off-by: Michal Vala --- .../workspace/server/devfile/Constants.java | 10 +++- .../server/devfile/DevfileService.java | 3 +- .../devfile/schema/DevfileSchemaProvider.java | 46 +++++++++++----- .../validator/DevfileSchemaValidator.java | 23 +++++++- .../resources/schema/{ => 1.0.0}/devfile.json | 7 +-- .../server/devfile/DevfileServiceTest.java | 4 +- .../schema/DevfileSchemaProviderTest.java | 54 +++++++++++++++++++ .../validator/DevfileSchemaValidatorTest.java | 19 +++++++ 8 files changed, 144 insertions(+), 22 deletions(-) rename wsmaster/che-core-api-workspace/src/main/resources/schema/{ => 1.0.0}/devfile.json (99%) create mode 100644 wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/schema/DevfileSchemaProviderTest.java diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/Constants.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/Constants.java index f0f644755e3..be1b2d28132 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/Constants.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/Constants.java @@ -11,14 +11,22 @@ */ package org.eclipse.che.api.workspace.server.devfile; +import java.util.Collections; +import java.util.List; + public class Constants { private Constants() {} - public static final String SCHEMA_LOCATION = "schema/devfile.json"; + public static final String SCHEMAS_LOCATION = "schema/"; + + public static final String SCHEMA_FILENAME = "devfile.json"; public static final String CURRENT_API_VERSION = "1.0.0"; + public static final List SUPPORTED_VERSIONS = + Collections.singletonList(CURRENT_API_VERSION); + public static final String EDITOR_COMPONENT_TYPE = "cheEditor"; public static final String PLUGIN_COMPONENT_TYPE = "chePlugin"; diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileService.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileService.java index 56ed823e2d1..35c57824325 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileService.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/DevfileService.java @@ -12,6 +12,7 @@ package org.eclipse.che.api.workspace.server.devfile; import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.eclipse.che.api.workspace.server.devfile.Constants.CURRENT_API_VERSION; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -52,7 +53,7 @@ public DevfileService(DevfileSchemaProvider schemaCachedProvider) { }) public Response getSchema() throws ServerException { try { - return Response.ok(schemaCachedProvider.getSchemaContent()).build(); + return Response.ok(schemaCachedProvider.getSchemaContent(CURRENT_API_VERSION)).build(); } catch (IOException e) { throw new ServerException(e); } diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/schema/DevfileSchemaProvider.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/schema/DevfileSchemaProvider.java index 04b55ec1183..ca3b2d068e6 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/schema/DevfileSchemaProvider.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/schema/DevfileSchemaProvider.java @@ -11,35 +11,57 @@ */ package org.eclipse.che.api.workspace.server.devfile.schema; -import static org.eclipse.che.api.workspace.server.devfile.Constants.SCHEMA_LOCATION; +import static org.eclipse.che.api.workspace.server.devfile.Constants.SCHEMAS_LOCATION; +import static org.eclipse.che.api.workspace.server.devfile.Constants.SCHEMA_FILENAME; +import static org.eclipse.che.api.workspace.server.devfile.Constants.SUPPORTED_VERSIONS; import static org.eclipse.che.commons.lang.IoUtil.getResource; import static org.eclipse.che.commons.lang.IoUtil.readAndCloseQuietly; import java.io.IOException; import java.io.StringReader; import java.lang.ref.SoftReference; +import java.util.HashMap; +import java.util.Map; import javax.inject.Singleton; /** Loads a schema content and stores it in soft reference. */ @Singleton public class DevfileSchemaProvider { - private SoftReference schemaRef = new SoftReference<>(null); + private Map> schemas = new HashMap<>(); - public String getSchemaContent() throws IOException { - String schema = schemaRef.get(); - if (schema == null) { - schema = loadFile(); - schemaRef = new SoftReference<>(schema); + public String getSchemaContent(String version) throws IOException { + if (schemas.containsKey(version)) { + String schema = schemas.get(version).get(); + if (schema != null) { + return schema; + } else { + return loadAndPut(version); + } + } else { + return loadAndPut(version); } - return schema; } - public StringReader getAsReader() throws IOException { - return new StringReader(getSchemaContent()); + public StringReader getAsReader(String version) throws IOException { + return new StringReader(getSchemaContent(version)); } - private String loadFile() throws IOException { - return readAndCloseQuietly(getResource(SCHEMA_LOCATION)); + private String loadFile(String version) throws IOException { + try { + return readAndCloseQuietly(getResource(SCHEMAS_LOCATION + version + "/" + SCHEMA_FILENAME)); + } catch (IOException ioe) { + throw new IOException( + String.format( + "Unable to load devfile schema with version '%s'. Supported versions are '%s'", + version, SUPPORTED_VERSIONS), + ioe); + } + } + + private String loadAndPut(String version) throws IOException { + final String schema = loadFile(version); + schemas.put(version, new SoftReference<>(schema)); + return schema; } } diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/validator/DevfileSchemaValidator.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/validator/DevfileSchemaValidator.java index dbea4a20b13..24548dbcc7d 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/validator/DevfileSchemaValidator.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/devfile/validator/DevfileSchemaValidator.java @@ -12,6 +12,7 @@ package org.eclipse.che.api.workspace.server.devfile.validator; import static java.lang.String.format; +import static org.eclipse.che.api.workspace.server.devfile.Constants.SUPPORTED_VERSIONS; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -19,7 +20,9 @@ import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; import javax.json.JsonReader; @@ -37,7 +40,7 @@ public class DevfileSchemaValidator { private final JsonValidationService service = JsonValidationService.newInstance(); private ObjectMapper yamlMapper; private ObjectMapper jsonMapper; - private JsonSchema schema; + private Map schemasByVersion; private ErrorMessageComposer errorMessageComposer; @Inject @@ -46,7 +49,10 @@ public DevfileSchemaValidator(DevfileSchemaProvider schemaProvider) { this.jsonMapper = new ObjectMapper(); this.errorMessageComposer = new ErrorMessageComposer(); try { - this.schema = service.readSchema(schemaProvider.getAsReader()); + this.schemasByVersion = new HashMap<>(); + for (String version : SUPPORTED_VERSIONS) { + this.schemasByVersion.put(version, service.readSchema(schemaProvider.getAsReader(version))); + } } catch (IOException e) { throw new RuntimeException("Unable to read devfile json schema for validation.", e); } @@ -75,6 +81,19 @@ private void validate(JsonNode contentNode) throws DevfileFormatException { try { List validationErrors = new ArrayList<>(); ProblemHandler handler = ProblemHandler.collectingTo(validationErrors); + if (!contentNode.hasNonNull("apiVersion")) { + throw new DevfileFormatException( + "Devfile schema validation failed. Error: The object must have a property whose name is \"apiVersion\"."); + } + String apiVersion = contentNode.get("apiVersion").asText(); + + if (!schemasByVersion.containsKey(apiVersion)) { + throw new DevfileFormatException( + String.format( + "Version '%s' of the devfile is not supported. Supported versions are '%s'.", + apiVersion, SUPPORTED_VERSIONS)); + } + JsonSchema schema = schemasByVersion.get(apiVersion); try (JsonReader reader = service.createReader( new StringReader(jsonMapper.writeValueAsString(contentNode)), schema, handler)) { diff --git a/wsmaster/che-core-api-workspace/src/main/resources/schema/devfile.json b/wsmaster/che-core-api-workspace/src/main/resources/schema/1.0.0/devfile.json similarity index 99% rename from wsmaster/che-core-api-workspace/src/main/resources/schema/devfile.json rename to wsmaster/che-core-api-workspace/src/main/resources/schema/1.0.0/devfile.json index 6cda1d3a12d..0f2ebbd89cb 100644 --- a/wsmaster/che-core-api-workspace/src/main/resources/schema/devfile.json +++ b/wsmaster/che-core-api-workspace/src/main/resources/schema/1.0.0/devfile.json @@ -33,11 +33,8 @@ "additionalProperties": false, "properties": { "apiVersion": { - "type": "string", - "title": "Devfile API Version", - "examples": [ - "1.0.0" - ] + "const": "1.0.0", + "title": "Devfile API Version" }, "metadata": { "type": "object", diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileServiceTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileServiceTest.java index 7248c5df721..e8512ea736c 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileServiceTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/DevfileServiceTest.java @@ -12,6 +12,7 @@ package org.eclipse.che.api.workspace.server.devfile; import static com.jayway.restassured.RestAssured.given; +import static org.eclipse.che.api.workspace.server.devfile.Constants.CURRENT_API_VERSION; import static org.everrest.assured.JettyHttpServer.*; import static org.testng.Assert.assertEquals; @@ -54,6 +55,7 @@ public void shouldRetrieveSchema() throws Exception { .get(SECURE_PATH + "/devfile"); assertEquals(response.getStatusCode(), 200); - assertEquals(response.getBody().asString(), schemaProvider.getSchemaContent()); + assertEquals( + response.getBody().asString(), schemaProvider.getSchemaContent(CURRENT_API_VERSION)); } } diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/schema/DevfileSchemaProviderTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/schema/DevfileSchemaProviderTest.java new file mode 100644 index 00000000000..a6b3ece997d --- /dev/null +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/schema/DevfileSchemaProviderTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +package org.eclipse.che.api.workspace.server.devfile.schema; + +import static org.eclipse.che.api.workspace.server.devfile.Constants.CURRENT_API_VERSION; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.io.IOException; +import java.io.StringReader; +import org.testng.annotations.Test; + +public class DevfileSchemaProviderTest { + + private final DevfileSchemaProvider devfileSchemaProvider = new DevfileSchemaProvider(); + + @Test + public void shouldGetProperDevfileSchemaContent() throws IOException { + String content = devfileSchemaProvider.getSchemaContent(CURRENT_API_VERSION); + assertNotNull(content); + assertTrue(content.contains("This schema describes the structure of the devfile object")); + } + + @Test + public void shouldGetProperDevfileSchemaContentAsReader() throws IOException { + StringReader contentReader = devfileSchemaProvider.getAsReader(CURRENT_API_VERSION); + assertNotNull(contentReader); + + StringBuilder contentBuilder = new StringBuilder(); + int c; + while ((c = contentReader.read()) != -1) { + contentBuilder.append((char) c); + } + assertTrue( + contentBuilder + .toString() + .contains("This schema describes the structure of the devfile object")); + } + + @Test(expectedExceptions = IOException.class) + public void shouldThrowExceptionWhenInvalidVersionRequested() throws IOException { + devfileSchemaProvider.getSchemaContent("this_is_clearly_not_a_valid_schema_version"); + } +} diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/validator/DevfileSchemaValidatorTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/validator/DevfileSchemaValidatorTest.java index 8ff9864a135..bd5361db8d1 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/validator/DevfileSchemaValidatorTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/devfile/validator/DevfileSchemaValidatorTest.java @@ -16,6 +16,7 @@ import static org.testng.Assert.fail; import java.io.IOException; +import org.eclipse.che.api.workspace.server.devfile.Constants; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException; import org.eclipse.che.api.workspace.server.devfile.schema.DevfileSchemaProvider; import org.testng.annotations.BeforeClass; @@ -85,6 +86,24 @@ public void shouldThrowExceptionOnValidationOfNonValidDevfile( fail("DevfileFormatException expected to be thrown but is was not"); } + @Test + public void shouldThrowExceptionWhenDevfileHasUnsupportedApiVersion() throws Exception { + try { + String devfile = + "---\n" + "apiVersion: 111.111\n" + "metadata:\n" + " name: test-invalid-apiversion\n"; + schemaValidator.validateYaml(devfile); + } catch (DevfileFormatException e) { + assertEquals( + e.getMessage(), + "Version '111.111' of the devfile is not supported. " + + "Supported versions are '" + + Constants.SUPPORTED_VERSIONS + + "'."); + return; + } + fail("DevfileFormatException expected to be thrown but is was not"); + } + @DataProvider public Object[][] invalidDevfiles() { return new Object[][] {