diff --git a/.github/workflows/verify.yaml b/.github/workflows/verify.yaml index d8c9764b30b..48b65904ce3 100644 --- a/.github/workflows/verify.yaml +++ b/.github/workflows/verify.yaml @@ -38,6 +38,33 @@ jobs: - name: Run Javadoc run: ./gradlew javadoc + Verify-Api-Version: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # Checks to see if any change in any of the management API modules was done. If so, it sets the "mgmt" filter to "true" + - uses: dorny/paths-filter@v3.0.2 + id: filter + with: + filters: | + mgmt: + - 'extensions/control-plane/api/management-api/**/*.java' + + # Use the filter to check if files in any of the management API modules were changed + # in the PR. If they were, check also, if the "api-version.json" file was appropriately modified + + - name: Inspect changeset if version file changed + if: ${{ steps.filter.outputs.mgmt == 'true' }} + run: | + # check if changeset contains the `api-version.json` file + changes=$(git diff --name-status main... | grep "api-version.json" | wc -l) + + if [ $changes -lt 1 ]; then + echo "::error file=./extensions/common/api/management-api-configuration/src/main/resources/api-version.json,line=1::Bumping the version is required after a change to the Management API" + return 1; + fi + + Unit-Tests: runs-on: ubuntu-latest env: diff --git a/extensions/common/api/management-api-configuration/src/main/resources/api-version.json b/extensions/common/api/management-api-configuration/src/main/resources/api-version.json new file mode 100644 index 00000000000..590fdb52cc4 --- /dev/null +++ b/extensions/common/api/management-api-configuration/src/main/resources/api-version.json @@ -0,0 +1,4 @@ +{ + "version": "3.0.0", + "date": "2024-05-17T07:12:44Z" +} \ No newline at end of file diff --git a/extensions/control-plane/api/management-api/version-api/build.gradle.kts b/extensions/control-plane/api/management-api/version-api/build.gradle.kts new file mode 100644 index 00000000000..7f740b86785 --- /dev/null +++ b/extensions/control-plane/api/management-api/version-api/build.gradle.kts @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 Amadeus + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Amadeus - Initial API and Implementation + * + */ + + +plugins { + `java-library` + id("io.swagger.core.v3.swagger-gradle-plugin") +} + +dependencies { + implementation(project(":extensions:common:api:api-core")) + implementation(project(":extensions:common:api:management-api-configuration")) + + implementation(libs.jakarta.rsApi) + + testImplementation(project(":extensions:common:http")) + testImplementation(project(":core:common:junit")) + testImplementation(testFixtures(project(":extensions:common:http:jersey-core"))) + testImplementation(libs.restAssured) + testImplementation(libs.awaitility) +} + +edcBuild { + swagger { + apiGroup.set("management-api") + } +} + + diff --git a/extensions/control-plane/api/management-api/version-api/src/main/java/org/eclipse/edc/connector/api/management/version/VersionApi.java b/extensions/control-plane/api/management-api/version-api/src/main/java/org/eclipse/edc/connector/api/management/version/VersionApi.java new file mode 100644 index 00000000000..1d757ba83a4 --- /dev/null +++ b/extensions/control-plane/api/management-api/version-api/src/main/java/org/eclipse/edc/connector/api/management/version/VersionApi.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.connector.api.management.version; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.json.JsonObject; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.spi.types.domain.secret.Secret.EDC_SECRET_TYPE; +import static org.eclipse.edc.spi.types.domain.secret.Secret.EDC_SECRET_VALUE; + +@OpenAPIDefinition( + info = @Info(description = "This contains the version API that provides information about the exact version of the management API", title = "Version API")) +@Tag(name = "Version") +public interface VersionApi { + + @Operation(description = "Gets the exact SemVer string of the Management API", + operationId = "getVersion", + responses = { + @ApiResponse(responseCode = "200", description = "The secret", + content = @Content(schema = @Schema(implementation = SecretOutputSchema.class))) + } + ) + JsonObject getVersion(); + + + @ArraySchema() + @Schema(name = "SecretOutput", example = SecretOutputSchema.SECRET_OUTPUT_EXAMPLE) + record SecretOutputSchema( + @Schema(name = ID) + String id, + @Schema(name = TYPE, example = EDC_SECRET_TYPE) + String type, + @Schema(name = EDC_SECRET_VALUE, requiredMode = REQUIRED) + String value + ) { + public static final String SECRET_OUTPUT_EXAMPLE = """ + { + "@context": { "@vocab": "https://w3id.org/edc/v0.0.1/ns/" }, + "@id": "secret-id", + "@type": "https://w3id.org/edc/v0.0.1/ns/Secret", + "value": "secret-value" + } + """; + } + +} diff --git a/extensions/control-plane/api/management-api/version-api/src/main/java/org/eclipse/edc/connector/api/management/version/VersionApiController.java b/extensions/control-plane/api/management-api/version-api/src/main/java/org/eclipse/edc/connector/api/management/version/VersionApiController.java new file mode 100644 index 00000000000..0f675420f8f --- /dev/null +++ b/extensions/control-plane/api/management-api/version-api/src/main/java/org/eclipse/edc/connector/api/management/version/VersionApiController.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.connector.api.management.version; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.json.JsonBuilderFactory; +import jakarta.json.JsonObject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import org.eclipse.edc.spi.EdcException; + +import java.io.IOException; +import java.util.Map; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; + +@Consumes(APPLICATION_JSON) +@Produces(APPLICATION_JSON) +@Path("/version") +public class VersionApiController implements VersionApi { + public static final String API_VERSION_JSON_FILE = "api-version.json"; + public static final String VERSION_PROPERTY = "version"; + public static final String DATE_PROPERTY = "date"; + private static final TypeReference> MAP_TYPE = new TypeReference<>() { + }; + private final ClassLoader resourceClassLoader; + private final JsonBuilderFactory jsonBuilderFactory; + private final ObjectMapper objectMapper; + private JsonObject versionObject; + + public VersionApiController(ClassLoader resourceClassLoader, JsonBuilderFactory jsonBuilderFactory, ObjectMapper objectMapper) { + this.resourceClassLoader = resourceClassLoader; + this.jsonBuilderFactory = jsonBuilderFactory; + this.objectMapper = objectMapper; + } + + @GET + @Path("/") + @Override + public JsonObject getVersion() { + if (versionObject == null) { + try (var versionContent = resourceClassLoader.getResourceAsStream(API_VERSION_JSON_FILE)) { + if (versionContent == null) { + throw new EdcException("Version file not found or not readable."); + } + var content = objectMapper.readValue(versionContent, MAP_TYPE); + versionObject = jsonBuilderFactory.createObjectBuilder() + .add(EDC_NAMESPACE + VERSION_PROPERTY, content.get(VERSION_PROPERTY)) + .add(EDC_NAMESPACE + DATE_PROPERTY, content.get(DATE_PROPERTY)) + .build(); + + } catch (IOException e) { + throw new EdcException(e); + } + } + return versionObject; + } +} diff --git a/extensions/control-plane/api/management-api/version-api/src/main/java/org/eclipse/edc/connector/api/management/version/VersionApiExtension.java b/extensions/control-plane/api/management-api/version-api/src/main/java/org/eclipse/edc/connector/api/management/version/VersionApiExtension.java new file mode 100644 index 00000000000..54f3d153978 --- /dev/null +++ b/extensions/control-plane/api/management-api/version-api/src/main/java/org/eclipse/edc/connector/api/management/version/VersionApiExtension.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.connector.api.management.version; + +import jakarta.json.Json; +import org.eclipse.edc.connector.api.management.configuration.ManagementApiConfiguration; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.types.TypeManager; +import org.eclipse.edc.web.spi.WebService; + +import java.util.Map; + +@Extension(value = VersionApiExtension.NAME) +public class VersionApiExtension implements ServiceExtension { + + public static final String NAME = "Management API: Version Information"; + + @Inject + private WebService webService; + + @Inject + private ManagementApiConfiguration managementConfig; + + @Inject + private TypeManager typeManager; + + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + webService.registerResource(managementConfig.getContextAlias(), new VersionApiController(getClassLoader(), Json.createBuilderFactory(Map.of()), typeManager.getMapper())); + } + + public ClassLoader getClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } +} diff --git a/extensions/control-plane/api/management-api/version-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/control-plane/api/management-api/version-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 00000000000..4cf1ec5eacb --- /dev/null +++ b/extensions/control-plane/api/management-api/version-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,15 @@ +# +# Copyright (c) 2024 Amadeus +# +# This program and the accompanying materials are made available under the +# terms of the Apache License, Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# +# Contributors: +# Amadeus - initial API and implementation +# +# + +org.eclipse.edc.connector.api.management.version.VersionApiExtension diff --git a/extensions/control-plane/api/management-api/version-api/src/test/java/org/eclipse/edc/connector/api/management/version/VersionApiControllerTest.java b/extensions/control-plane/api/management-api/version-api/src/test/java/org/eclipse/edc/connector/api/management/version/VersionApiControllerTest.java new file mode 100644 index 00000000000..0b7afdd9249 --- /dev/null +++ b/extensions/control-plane/api/management-api/version-api/src/test/java/org/eclipse/edc/connector/api/management/version/VersionApiControllerTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.connector.api.management.version; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.restassured.specification.RequestSpecification; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonString; +import org.eclipse.edc.junit.annotations.ApiTest; +import org.eclipse.edc.web.jersey.testfixtures.RestControllerTestBase; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static io.restassured.RestAssured.given; +import static io.restassured.http.ContentType.JSON; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; + +@ApiTest +class VersionApiControllerTest extends RestControllerTestBase { + + + @BeforeEach + void setup() { + } + + @Test + void getVersion() { + var result = baseRequest() + .get("/version") + .then() + .statusCode(200) + .contentType(JSON) + .extract().body().as(JsonObject.class); + + var version = result.get(EDC_NAMESPACE + "version"); + assertThat(version).isInstanceOf(JsonString.class); + assertThat(((JsonString) version).getString()).hasToString("3.0.0"); + } + + @Override + protected Object controller() { + return new VersionApiController(Thread.currentThread().getContextClassLoader(), Json.createBuilderFactory(Map.of()), new ObjectMapper()); + } + + + private RequestSpecification baseRequest() { + return given() + .baseUri("http://localhost:" + port) + .when(); + } +} diff --git a/extensions/control-plane/api/management-api/version-api/src/test/java/org/eclipse/edc/connector/api/management/version/VersionApiExtensionTest.java b/extensions/control-plane/api/management-api/version-api/src/test/java/org/eclipse/edc/connector/api/management/version/VersionApiExtensionTest.java new file mode 100644 index 00000000000..b04a7cea4b9 --- /dev/null +++ b/extensions/control-plane/api/management-api/version-api/src/test/java/org/eclipse/edc/connector/api/management/version/VersionApiExtensionTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.connector.api.management.version; + +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; +import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; +import org.eclipse.edc.web.spi.WebService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.eclipse.edc.spi.types.domain.secret.Secret.EDC_SECRET_TYPE; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(DependencyInjectionExtension.class) +class VersionApiExtensionTest { + + private final TypeTransformerRegistry managementApiTypeTransformerRegistry = mock(); + private final JsonObjectValidatorRegistry validatorRegistry = mock(); + private final WebService webService = mock(); + + @BeforeEach + void setUp(ServiceExtensionContext context) { + var typeTransformerRegistry = mock(TypeTransformerRegistry.class); + when(typeTransformerRegistry.forContext("management-api")).thenReturn(managementApiTypeTransformerRegistry); + context.registerService(TypeTransformerRegistry.class, typeTransformerRegistry); + context.registerService(JsonObjectValidatorRegistry.class, validatorRegistry); + context.registerService(WebService.class, webService); + } + + @Test + void initialize_shouldRegisterControllers(VersionApiExtension extension, ServiceExtensionContext context) { + extension.initialize(context); + + verify(webService).registerResource(any(), isA(VersionApiController.class)); + } + + @Test + void initialize_shouldRegisterValidators(VersionApiExtension extension, ServiceExtensionContext context) { + extension.initialize(context); + + verify(validatorRegistry).register(eq(EDC_SECRET_TYPE), any()); + } + + @Test + void initialize_shouldRegisterTransformers(VersionApiExtension extension, ServiceExtensionContext context) { + extension.initialize(context); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 67a5a943492..809303c61be 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -161,6 +161,7 @@ include(":extensions:control-plane:api:control-plane-api-client") include(":extensions:control-plane:api:management-api") include(":extensions:control-plane:api:management-api:asset-api") include(":extensions:control-plane:api:management-api:secrets-api") +include(":extensions:control-plane:api:management-api:version-api") include(":extensions:control-plane:api:management-api:catalog-api") include(":extensions:control-plane:api:management-api:contract-agreement-api") include(":extensions:control-plane:api:management-api:contract-definition-api") diff --git a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/VersionApiEndToEndTest.java b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/VersionApiEndToEndTest.java new file mode 100644 index 00000000000..e0c459cf25b --- /dev/null +++ b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/VersionApiEndToEndTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024 Amadeus + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Amadeus - initial API and implementation + * + */ + +package org.eclipse.edc.test.e2e.managementapi; + +import org.eclipse.edc.junit.annotations.EndToEndTest; +import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_PREFIX; +import static org.eclipse.edc.test.e2e.managementapi.Runtimes.inMemoryRuntime; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.notNullValue; + +/** + * Secret V1 endpoints end-to-end tests + */ +public class VersionApiEndToEndTest { + + abstract static class Tests extends ManagementApiEndToEndTestBase { + + Tests(EdcRuntimeExtension runtime) { + super(runtime); + } + + @Test + void getVersion() { + + baseRequest() + .get("/version") + .then() + .statusCode(200) + .body(notNullValue()) + .body(CONTEXT, hasEntry(EDC_PREFIX, EDC_NAMESPACE)) + .body("version", equalTo("3.0.0")) + .body("date", notNullValue()); + } + } + + @Nested + @EndToEndTest + class InMemory extends Tests { + + @RegisterExtension + public static final EdcRuntimeExtension RUNTIME = inMemoryRuntime(); + + InMemory() { + super(RUNTIME); + } + + } +} \ No newline at end of file diff --git a/system-tests/management-api/management-api-test-runtime/build.gradle.kts b/system-tests/management-api/management-api-test-runtime/build.gradle.kts index ca27ce7be04..19901891ada 100644 --- a/system-tests/management-api/management-api-test-runtime/build.gradle.kts +++ b/system-tests/management-api/management-api-test-runtime/build.gradle.kts @@ -25,6 +25,7 @@ dependencies { implementation(project(":extensions:common:json-ld")) implementation(project(":extensions:control-plane:api:management-api")) implementation(project(":extensions:control-plane:api:management-api:secrets-api")) + implementation(project(":extensions:control-plane:api:management-api:version-api")) implementation(project(":extensions:data-plane:data-plane-client")) implementation(project(":core:data-plane-selector:data-plane-selector-core"))