From e3d884803e07f4692e1a1aed5f8505f19c421765 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 14 Sep 2023 13:23:59 +0100 Subject: [PATCH] Add Docker Compose support for Neo4j Closes gh-37379 --- .../spring-boot-docker-compose/build.gradle | 1 + ...DockerComposeConnectionDetailsFactory.java | 76 +++++++++++++++++++ .../connection/neo4j/Neo4jEnvironment.java | 57 ++++++++++++++ .../connection/neo4j/package-info.java | 20 +++++ .../main/resources/META-INF/spring.factories | 1 + ...nectionDetailsFactoryIntegrationTests.java | 51 +++++++++++++ .../neo4j/Neo4jEnvironmentTests.java | 59 ++++++++++++++ .../connection/neo4j/neo4j-compose.yaml | 8 ++ .../asciidoc/features/docker-compose.adoc | 3 + 9 files changed, 276 insertions(+) create mode 100644 spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jDockerComposeConnectionDetailsFactory.java create mode 100644 spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jEnvironment.java create mode 100644 spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/neo4j/package-info.java create mode 100644 spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jDockerComposeConnectionDetailsFactoryIntegrationTests.java create mode 100644 spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jEnvironmentTests.java create mode 100644 spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/neo4j/neo4j-compose.yaml diff --git a/spring-boot-project/spring-boot-docker-compose/build.gradle b/spring-boot-project/spring-boot-docker-compose/build.gradle index b0429632ffa..ec1665712de 100644 --- a/spring-boot-project/spring-boot-docker-compose/build.gradle +++ b/spring-boot-project/spring-boot-docker-compose/build.gradle @@ -18,6 +18,7 @@ dependencies { optional(project(":spring-boot-project:spring-boot-actuator-autoconfigure")) optional("io.r2dbc:r2dbc-spi") optional("org.mongodb:mongodb-driver-core") + optional("org.neo4j.driver:neo4j-java-driver") optional("org.springframework.data:spring-data-r2dbc") testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jDockerComposeConnectionDetailsFactory.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jDockerComposeConnectionDetailsFactory.java new file mode 100644 index 00000000000..33bd622e23a --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jDockerComposeConnectionDetailsFactory.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.neo4j; + +import java.net.URI; + +import org.neo4j.driver.AuthToken; + +import org.springframework.boot.autoconfigure.neo4j.Neo4jConnectionDetails; +import org.springframework.boot.docker.compose.core.RunningService; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource; + +/** + * {@link DockerComposeConnectionDetailsFactory} to create {@link Neo4jConnectionDetails} + * for a {@code Neo4j} service. + * + * @author Andy Wilkinson + */ +class Neo4jDockerComposeConnectionDetailsFactory extends DockerComposeConnectionDetailsFactory { + + Neo4jDockerComposeConnectionDetailsFactory() { + super("neo4j"); + } + + @Override + protected Neo4jConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { + return new Neo4jDockerComposeConnectionDetails(source.getRunningService()); + } + + /** + * {@link Neo4jConnectionDetails} backed by a {@code Neo4j} {@link RunningService}. + */ + static class Neo4jDockerComposeConnectionDetails extends DockerComposeConnectionDetails + implements Neo4jConnectionDetails { + + private static final int BOLT_PORT = 7687; + + private final AuthToken authToken; + + private final URI uri; + + Neo4jDockerComposeConnectionDetails(RunningService service) { + super(service); + Neo4jEnvironment neo4jEnvironment = new Neo4jEnvironment(service.env()); + this.authToken = neo4jEnvironment.getAuthToken(); + this.uri = URI.create("neo4j://%s:%d".formatted(service.host(), service.ports().get(BOLT_PORT))); + } + + @Override + public URI getUri() { + return this.uri; + } + + @Override + public AuthToken getAuthToken() { + return this.authToken; + } + + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jEnvironment.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jEnvironment.java new file mode 100644 index 00000000000..59e5a90e923 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jEnvironment.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.neo4j; + +import java.util.Map; + +import org.neo4j.driver.AuthToken; +import org.neo4j.driver.AuthTokens; + +/** + * Neo4j environment details. + * + * @author Andy Wilkinson + */ +class Neo4jEnvironment { + + private final AuthToken authToken; + + Neo4jEnvironment(Map env) { + this.authToken = parse(env.get("NEO4J_AUTH")); + } + + private AuthToken parse(String neo4jAuth) { + if (neo4jAuth == null) { + return null; + } + if ("none".equals(neo4jAuth)) { + return AuthTokens.none(); + } + if (neo4jAuth.startsWith("neo4j/")) { + return AuthTokens.basic("neo4j", neo4jAuth.substring(6)); + } + throw new IllegalStateException( + "Cannot extract auth token from NEO4J_AUTH environment variable with value '" + neo4jAuth + "'." + + " Value should be 'none' to disable authentication or start with 'neo4j/' to specify" + + " the neo4j user's password"); + } + + AuthToken getAuthToken() { + return this.authToken; + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/neo4j/package-info.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/neo4j/package-info.java new file mode 100644 index 00000000000..afea67c3cf5 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/neo4j/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Auto-configuration for docker compose Neo4j service connections. + */ +package org.springframework.boot.docker.compose.service.connection.neo4j; diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories index 80fe3d28309..f28a89272d9 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/spring.factories @@ -15,6 +15,7 @@ org.springframework.boot.docker.compose.service.connection.mariadb.MariaDbR2dbcD org.springframework.boot.docker.compose.service.connection.mongo.MongoDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.mysql.MySqlJdbcDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.mysql.MySqlR2dbcDockerComposeConnectionDetailsFactory,\ +org.springframework.boot.docker.compose.service.connection.neo4j.Neo4jDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.oracle.OracleJdbcDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.oracle.OracleR2dbcDockerComposeConnectionDetailsFactory,\ org.springframework.boot.docker.compose.service.connection.otlp.OpenTelemetryMetricsDockerComposeConnectionDetailsFactory,\ diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jDockerComposeConnectionDetailsFactoryIntegrationTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jDockerComposeConnectionDetailsFactoryIntegrationTests.java new file mode 100644 index 00000000000..ca95c13efa1 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jDockerComposeConnectionDetailsFactoryIntegrationTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.neo4j; + +import org.junit.jupiter.api.Test; +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Driver; +import org.neo4j.driver.GraphDatabase; + +import org.springframework.boot.autoconfigure.neo4j.Neo4jConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIntegrationTests; +import org.springframework.boot.testsupport.testcontainers.DockerImageNames; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; + +/** + * Integration tests for {@link Neo4jDockerComposeConnectionDetailsFactory}. + * + * @author Andy Wilkinson + */ +class Neo4jDockerComposeConnectionDetailsFactoryIntegrationTests extends AbstractDockerComposeIntegrationTests { + + Neo4jDockerComposeConnectionDetailsFactoryIntegrationTests() { + super("neo4j-compose.yaml", DockerImageNames.neo4j()); + } + + @Test + void runCreatesConnectionDetailsThatCanAccessNeo4j() { + Neo4jConnectionDetails connectionDetails = run(Neo4jConnectionDetails.class); + assertThat(connectionDetails.getAuthToken()).isEqualTo(AuthTokens.basic("neo4j", "secret")); + try (Driver driver = GraphDatabase.driver(connectionDetails.getUri(), connectionDetails.getAuthToken())) { + assertThatNoException().isThrownBy(driver::verifyConnectivity); + } + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jEnvironmentTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jEnvironmentTests.java new file mode 100644 index 00000000000..4cbb02d0b60 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/neo4j/Neo4jEnvironmentTests.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.docker.compose.service.connection.neo4j; + +import java.util.Collections; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.neo4j.driver.AuthTokens; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +/** + * Tests for {@link Neo4jEnvironment}. + * + * @author Andy Wilkinson + */ +class Neo4jEnvironmentTests { + + @Test + void whenNeo4jAuthIsNullThenAuthTokenIsNull() { + Neo4jEnvironment environment = new Neo4jEnvironment(Collections.emptyMap()); + assertThat(environment.getAuthToken()).isNull(); + } + + @Test + void whenNeo4jAuthIsNoneThenAuthTokenIsNone() { + Neo4jEnvironment environment = new Neo4jEnvironment(Map.of("NEO4J_AUTH", "none")); + assertThat(environment.getAuthToken()).isEqualTo(AuthTokens.none()); + } + + @Test + void whenNeo4jAuthIsNeo4jSlashPasswordThenAuthTokenIsBasic() { + Neo4jEnvironment environment = new Neo4jEnvironment(Map.of("NEO4J_AUTH", "neo4j/custom-password")); + assertThat(environment.getAuthToken()).isEqualTo(AuthTokens.basic("neo4j", "custom-password")); + } + + @Test + void whenNeo4jAuthIsNeitherNoneNorNeo4jSlashPasswordEnvironmentCreationThrows() { + assertThatIllegalStateException() + .isThrownBy(() -> new Neo4jEnvironment(Map.of("NEO4J_AUTH", "graphdb/custom-password"))); + } + +} diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/neo4j/neo4j-compose.yaml b/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/neo4j/neo4j-compose.yaml new file mode 100644 index 00000000000..313cce77927 --- /dev/null +++ b/spring-boot-project/spring-boot-docker-compose/src/test/resources/org/springframework/boot/docker/compose/service/connection/neo4j/neo4j-compose.yaml @@ -0,0 +1,8 @@ +services: + neo4j: + image: '{imageName}' + ports: + - '7687' + environment: + - 'NEO4J_AUTH=neo4j/secret' + diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/docker-compose.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/docker-compose.adoc index bebc060fa19..10df7be6c44 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/docker-compose.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/docker-compose.adoc @@ -76,6 +76,9 @@ The following service connections are currently supported: | `MongoConnectionDetails` | Containers named "mongo" +| `Neo4jConnectionDetails` +| Containers named "neo4j" + | `OtlpMetricsConnectionDetails` | Containers named "otel/opentelemetry-collector-contrib"