Skip to content

Commit

Permalink
Add SpiceDB test container
Browse files Browse the repository at this point in the history
  • Loading branch information
Fameing committed Jan 22, 2024
1 parent 2111a37 commit 02cc3ae
Show file tree
Hide file tree
Showing 14 changed files with 392 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -338,12 +338,14 @@ embedded:
=== link:embedded-solr/README.adoc[embedded-solr]

=== link:embedded-cockroachdb/README.adoc[embedded-cockroachdb]

=== link:embedded-git/README.adoc[embedded-git]

=== link:embedded-wiremock/README.adoc[embedded-wiremock]

=== link:embedded-mailhog/README.adoc[embedded-mailhog]

=== link:embedded-spicedb/README.adoc[embedded-spicedb]

== How to contribute

Expand Down
32 changes: 32 additions & 0 deletions embedded-spicedb/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
=== embedded-spicedb

==== Maven dependency

.pom.xml
[source,xml]
----
<dependency>
<groupId>com.playtika.testcontainers</groupId>
<artifactId>embedded-spicedb</artifactId>
<scope>test</scope>
</dependency>
----

==== Consumes (via `bootstrap.properties`)

* `embedded.spicedb.enabled` `(true|false, default is true)`
* `embedded.spicedb.dockerImage` `(default is 'authzed/spicedb:v1.28.0')`
** Image versions on https://hub.docker.com/r/authzed/spicedb/tags[dockerhub]
* `embedded.spicedb.presharedKey` `(default is 'somerandomkeyhere')`
* `embedded.toxiproxy.proxies.spicedb.enabled` Enables both creation of the container with ToxiProxy TCP proxy and a proxy to the `embedded-spicedb` container.


==== Produces

* `embedded.spicedb.host`
* `embedded.spicedb.port`
* `embedded.spicedb.token`
* `embedded.spicedb.toxiproxy.host`
* `embedded.spicedb.toxiproxy.port`
* `embedded.spicedb.networkAlias`
* Bean `ToxiproxyContainer.ContainerProxy spicedbContainerProxy`
46 changes: 46 additions & 0 deletions embedded-spicedb/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>testcontainers-spring-boot-parent</artifactId>
<groupId>com.playtika.testcontainers</groupId>
<version>3.1.2</version>
<relativePath>../testcontainers-spring-boot-parent</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>embedded-spicedb</artifactId>

<properties>
<grpc-protobuf.version>1.61.0</grpc-protobuf.version>
<authzed.version>0.6.0</authzed.version>
</properties>

<dependencies>
<dependency>
<groupId>com.playtika.testcontainers</groupId>
<artifactId>testcontainers-common</artifactId>
</dependency>
<dependency>
<groupId>com.playtika.testcontainers</groupId>
<artifactId>embedded-toxiproxy</artifactId>
</dependency>

<dependency>
<groupId>com.authzed.api</groupId>
<artifactId>authzed</artifactId>
<version>${authzed.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc-protobuf.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc-protobuf.version}</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.playtika.testcontainer.spicedb;

import com.playtika.testcontainer.common.spring.DockerPresenceBootstrapConfiguration;
import com.playtika.testcontainer.common.utils.ContainerUtils;
import com.playtika.testcontainer.toxiproxy.EmbeddedToxiProxyBootstrapConfiguration;
import com.playtika.testcontainer.toxiproxy.condition.ConditionalOnToxiProxyEnabled;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.ToxiproxyContainer;
import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy;
import org.testcontainers.containers.wait.strategy.WaitAllStrategy;
import org.testcontainers.containers.wait.strategy.WaitStrategy;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;

import static com.playtika.testcontainer.common.utils.ContainerUtils.configureCommonsAndStart;
import static com.playtika.testcontainer.spicedb.SpiceDBProperties.BEAN_NAME_EMBEDDED_SPICEDB;
import static com.playtika.testcontainer.spicedb.SpiceDBProperties.BEAN_NAME_EMBEDDED_SPICEDB_TOXI_PROXY;

@Slf4j
@Configuration
@ConditionalOnExpression("${embedded.containers.enabled:true}")
@AutoConfigureAfter({DockerPresenceBootstrapConfiguration.class, EmbeddedToxiProxyBootstrapConfiguration.class})
@ConditionalOnProperty(name = "embedded.spicedb.enabled", matchIfMissing = true)
@EnableConfigurationProperties(SpiceDBProperties.class)
public class EmbeddedSpiceDBBootstrapConfiguration {

private static final String NATS_NETWORK_ALIAS = "spicedb.testcontainer.docker";

@Bean(name = BEAN_NAME_EMBEDDED_SPICEDB_TOXI_PROXY)
@ConditionalOnToxiProxyEnabled(module = "spicedb")
ToxiproxyContainer.ContainerProxy spicedbContainerProxy(ToxiproxyContainer toxiproxyContainer,
@Qualifier(BEAN_NAME_EMBEDDED_SPICEDB) GenericContainer<?> spicedbContainer,
SpiceDBProperties properties,
ConfigurableEnvironment environment) {
ToxiproxyContainer.ContainerProxy proxy = toxiproxyContainer.getProxy(spicedbContainer, properties.getPort());

Map<String, Object> map = new LinkedHashMap<>();
map.put("embedded.spicedb.toxiproxy.host", proxy.getContainerIpAddress());
map.put("embedded.spicedb.toxiproxy.port", proxy.getProxyPort());
map.put("embedded.spicedb.toxiproxy.proxyName", proxy.getName());

MapPropertySource propertySource = new MapPropertySource("embeddedSpicedbToxiproxyInfo", map);
environment.getPropertySources().addFirst(propertySource);
log.info("Started Spicedb ToxiProxy connection details {}", map);

return proxy;
}

@Bean(name = BEAN_NAME_EMBEDDED_SPICEDB, destroyMethod = "stop")
public GenericContainer<?> spicedbContainer(ConfigurableEnvironment environment,
SpiceDBProperties properties,
Optional<Network> network) {
WaitStrategy waitStrategy = new WaitAllStrategy()
.withStrategy(new HostPortWaitStrategy())
.withStartupTimeout(properties.getTimeoutDuration());

GenericContainer<?> spicedbContainer = new GenericContainer<>(ContainerUtils.getDockerImageName(properties))
.withExposedPorts(properties.getPort())
.withCommand("serve", "--grpc-preshared-key", properties.getPresharedKey(), "--skip-release-check")
.waitingFor(waitStrategy)
.withNetworkAliases(NATS_NETWORK_ALIAS);

network.ifPresent(spicedbContainer::withNetwork);

spicedbContainer = configureCommonsAndStart(spicedbContainer, properties, log);

registerNatsEnvironment(spicedbContainer, environment, properties);
return spicedbContainer;
}

private void registerNatsEnvironment(GenericContainer<?> natsContainer,
ConfigurableEnvironment environment,
SpiceDBProperties properties) {
Integer clientMappedPort = natsContainer.getMappedPort(properties.getPort());
String host = natsContainer.getHost();

LinkedHashMap<String, Object> map = new LinkedHashMap<>();

map.put("embedded.spicedb.host", host);
map.put("embedded.spicedb.port", clientMappedPort);
map.put("embedded.spicedb.token", properties.getPresharedKey());
map.put("embedded.spicedb.networkAlias", NATS_NETWORK_ALIAS);

log.info("Started SpiceDb server. Connection details {}", map);

MapPropertySource propertySource = new MapPropertySource("embeddedSpicedbInfo", map);
environment.getPropertySources().addFirst(propertySource);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.playtika.testcontainer.spicedb;

import com.playtika.testcontainer.common.properties.CommonContainerProperties;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data
@EqualsAndHashCode(callSuper = true)
@ConfigurationProperties("embedded.spicedb")
public class SpiceDBProperties extends CommonContainerProperties {
static final String BEAN_NAME_EMBEDDED_SPICEDB = "embeddedSpiceDB";
static final String BEAN_NAME_EMBEDDED_SPICEDB_TOXI_PROXY = "embeddedSpiceDbToxiProxy";

int port = 50051;
String presharedKey = "somerandomkeyhere";

@Override
public String getDefaultDockerImage() {
// Please don`t remove this comment.
// renovate: datasource=docker
return "authzed/spicedb:v1.28.0";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"groups": [
],
"properties": [
{
"name": "embedded.spicedb.enabled",
"type": "java.lang.Boolean",
"defaultValue": "true"
}
],
"hints": [
{
"name": "embedded.spicedb.enabled",
"values": [
{
"value": "true",
"description": "Enables configuration of SpiceDB server on startup."
},
{
"value": "false",
"description": "Disabled configuration of SpiceDB server on startup."
}
]
}
]
}
2 changes: 2 additions & 0 deletions embedded-spicedb/src/main/resources/META-INF/spring.factories
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.playtika.testcontainer.spicedb.EmbeddedSpiceDBBootstrapConfiguration
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.playtika.testcontainer.spicedb;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Configuration;

@Slf4j
@SpringBootTest(
classes = BaseSpiceDbTest.TestConfiguration.class
)
public abstract class BaseSpiceDbTest {

@Autowired
ConfigurableListableBeanFactory beanFactory;

@EnableAutoConfiguration
@Configuration
static class TestConfiguration {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.playtika.testcontainer.spicedb;

import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.testcontainers.containers.Container;

import static org.assertj.core.api.Assertions.assertThat;

class DisableSpiceDBTest {

private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(
EmbeddedSpiceDBBootstrapConfiguration.class));

@Test
void contextLoads() {
contextRunner
.withPropertyValues(
"embedded.spicedb.enabled=false"
)
.run((context) -> assertThat(context)
.hasNotFailed()
.doesNotHaveBean(Container.class)
.doesNotHaveBean("spiceDbDependencyPostProcessor"));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.playtika.testcontainer.spicedb;

import com.authzed.api.v1.SchemaServiceGrpc;
import com.authzed.api.v1.SchemaServiceOuterClass;
import com.authzed.grpcutil.BearerToken;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;

import static org.assertj.core.api.Assertions.assertThat;

@Slf4j
class EmbeddedSpiceDbBootstrapConfigurationTest extends BaseSpiceDbTest {

@Autowired
ConfigurableListableBeanFactory beanFactory;

@Autowired
ConfigurableEnvironment environment;


@Value("${embedded.spicedb.host}")
String host;
@Value("${embedded.spicedb.port}")
int port;
@Value("${embedded.spicedb.token}")
String token;

@Test
void shouldConnect() throws InterruptedException {
ManagedChannel channel = ManagedChannelBuilder
.forAddress(host, port)
.usePlaintext()
.build();

SchemaServiceGrpc.SchemaServiceBlockingStub schemaService = SchemaServiceGrpc.newBlockingStub(channel)
.withCallCredentials(new BearerToken(token));

String schema = """
definition blog/user {}
definition blog/post {
relation reader: blog/user
relation writer: blog/user
permission read = reader + writer
permission write = writer
}
""";

SchemaServiceOuterClass.WriteSchemaRequest request = SchemaServiceOuterClass.WriteSchemaRequest
.newBuilder()
.setSchema(schema)
.build();

SchemaServiceOuterClass.WriteSchemaResponse response;
try {
response = schemaService.writeSchema(request);
response.getWrittenAt().getToken();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}

@Test
void propertiesAreAvailable() {
assertThat(environment.getProperty("embedded.spicedb.port")).isNotEmpty();
assertThat(environment.getProperty("embedded.spicedb.host")).isNotEmpty();
assertThat(environment.getProperty("embedded.spicedb.token")).isNotEmpty();
assertThat(environment.getProperty("embedded.spicedb.networkAlias")).isNotEmpty();
}

@EnableAutoConfiguration
@Configuration
static class TestConfiguration {
}
}
1 change: 1 addition & 0 deletions embedded-spicedb/src/test/resources/bootstrap.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
embedded.spicedb.attach-container-log=false
Loading

0 comments on commit 02cc3ae

Please sign in to comment.