Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Dapr configuration support to Testcontainers #1148

Merged
merged 8 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2024 The Dapr 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
* http://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 io.dapr.testcontainers;

/**
* Represents a Dapr component.
*/
public class Configuration {
private final String name;
private final TracingConfigurationSettings tracing;

//@TODO: add httpPipeline
//@TODO: add secrets
//@TODO: add components
//@TODO: add accessControl

/**
* Creates a new configuration.
* @param name Configuration name.
* @param tracing TracingConfigParameters tracing configuration parameters.
*/
public Configuration(String name, TracingConfigurationSettings tracing) {
this.name = name;
this.tracing = tracing;
}

public String getName() {
return name;
}

public TracingConfigurationSettings getTracing() {
return tracing;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.dapr.testcontainers;

// This is a marker interface, so we could get
// a list of all the configuration settings implementations
public interface ConfigurationSettings {
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,49 +13,54 @@

package io.dapr.testcontainers;

import io.dapr.testcontainers.converter.ComponentYamlConverter;
import io.dapr.testcontainers.converter.ConfigurationYamlConverter;
import io.dapr.testcontainers.converter.SubscriptionYamlConverter;
import io.dapr.testcontainers.converter.YamlConverter;
import io.dapr.testcontainers.converter.YamlMapperFactory;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.containers.wait.strategy.WaitStrategy;
import org.testcontainers.images.builder.Transferable;
import org.testcontainers.utility.DockerImageName;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.representer.Representer;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class DaprContainer extends GenericContainer<DaprContainer> {

private static final int DAPRD_DEFAULT_HTTP_PORT = 3500;
private static final int DAPRD_DEFAULT_GRPC_PORT = 50001;
private static final DaprProtocol DAPR_PROTOCOL = DaprProtocol.HTTP;
private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("daprio/daprd");
private static final Yaml YAML_MAPPER = YamlMapperFactory.create();
private static final YamlConverter<Component> COMPONENT_CONVERTER = new ComponentYamlConverter(YAML_MAPPER);
private static final YamlConverter<Subscription> SUBSCRIPTION_CONVERTER = new SubscriptionYamlConverter(YAML_MAPPER);
private static final YamlConverter<Configuration> CONFIGURATION_CONVERTER = new ConfigurationYamlConverter(
YAML_MAPPER);
private static final WaitStrategy WAIT_STRATEGY = Wait.forHttp("/v1.0/healthz/outbound")
.forPort(DAPRD_DEFAULT_HTTP_PORT)
.forStatusCodeMatching(statusCode -> statusCode >= 200 && statusCode <= 399);

private final Set<Component> components = new HashSet<>();
private final Set<Subscription> subscriptions = new HashSet<>();
private DaprProtocol protocol = DaprProtocol.HTTP;
private String appName;
private Integer appPort = null;
private DaprLogLevel daprLogLevel = DaprLogLevel.INFO;
private String appChannelAddress = "localhost";
private String placementService = "placement";
private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("daprio/daprd");
private static final Yaml yaml = getYamlMapper();
private DaprPlacementContainer placementContainer;
private String placementDockerImageName = "daprio/placement";

private Configuration configuration;
private DaprPlacementContainer placementContainer;
private String appName;
private Integer appPort;
private boolean shouldReusePlacement;

/**
Expand All @@ -78,6 +83,10 @@ public DaprContainer(String image) {
this(DockerImageName.parse(image));
}

public Configuration getConfiguration() {
return configuration;
}

public Set<Component> getComponents() {
return components;
}
Expand All @@ -91,6 +100,16 @@ public DaprContainer withAppPort(Integer port) {
return this;
}

public DaprContainer withAppChannelAddress(String appChannelAddress) {
this.appChannelAddress = appChannelAddress;
return this;
}

public DaprContainer withConfiguration(Configuration configuration) {
this.configuration = configuration;
return this;
}

public DaprContainer withPlacementService(String placementService) {
this.placementService = placementService;
return this;
Expand All @@ -111,6 +130,21 @@ public DaprContainer withSubscription(Subscription subscription) {
return this;
}

public DaprContainer withPlacementImage(String placementDockerImageName) {
this.placementDockerImageName = placementDockerImageName;
return this;
}

public DaprContainer withReusablePlacement(boolean reuse) {
this.shouldReusePlacement = reuse;
return this;
}

public DaprContainer withPlacementContainer(DaprPlacementContainer placementContainer) {
this.placementContainer = placementContainer;
return this;
}

public DaprContainer withComponent(Component component) {
components.add(component);
return this;
Expand All @@ -123,7 +157,7 @@ public DaprContainer withComponent(Component component) {
*/
public DaprContainer withComponent(Path path) {
try {
Map<String, Object> component = this.yaml.loadAs(Files.newInputStream(path), Map.class);
Map<String, Object> component = this.YAML_MAPPER.loadAs(Files.newInputStream(path), Map.class);

String type = (String) component.get("type");
Map<String, Object> metadata = (Map<String, Object>) component.get("metadata");
Expand Down Expand Up @@ -165,60 +199,6 @@ public String getGrpcEndpoint() {
return ":" + getMappedPort(DAPRD_DEFAULT_GRPC_PORT);
}

public DaprContainer withAppChannelAddress(String appChannelAddress) {
this.appChannelAddress = appChannelAddress;
return this;
}

/**
* Get a map of Dapr component details.
* @param component A Dapr Component.
* @return Map of component details.
*/
public Map<String, Object> componentToMap(Component component) {
Map<String, Object> componentProps = new HashMap<>();
componentProps.put("apiVersion", "dapr.io/v1alpha1");
componentProps.put("kind", "Component");

Map<String, String> componentMetadata = new LinkedHashMap<>();
componentMetadata.put("name", component.getName());
componentProps.put("metadata", componentMetadata);

Map<String, Object> componentSpec = new HashMap<>();
componentSpec.put("type", component.getType());
componentSpec.put("version", component.getVersion());

if (!component.getMetadata().isEmpty()) {
componentSpec.put("metadata", component.getMetadata());
}

componentProps.put("spec", componentSpec);
return Collections.unmodifiableMap(componentProps);
}

/**
* Get a map of Dapr subscription details.
* @param subscription A Dapr Subscription.
* @return Map of subscription details.
*/
public Map<String, Object> subscriptionToMap(Subscription subscription) {
Map<String, Object> subscriptionProps = new HashMap<>();
subscriptionProps.put("apiVersion", "dapr.io/v1alpha1");
subscriptionProps.put("kind", "Subscription");

Map<String, String> subscriptionMetadata = new LinkedHashMap<>();
subscriptionMetadata.put("name", subscription.getName());
subscriptionProps.put("metadata", subscriptionMetadata);

Map<String, Object> subscriptionSpec = new HashMap<>();
subscriptionSpec.put("pubsubname", subscription.getPubsubName());
subscriptionSpec.put("topic", subscription.getTopic());
subscriptionSpec.put("route", subscription.getRoute());

subscriptionProps.put("spec", subscriptionSpec);
return Collections.unmodifiableMap(subscriptionProps);
}

@Override
protected void configure() {
super.configure();
Expand All @@ -241,7 +221,7 @@ protected void configure() {
cmds.add(appName);
cmds.add("--dapr-listen-addresses=0.0.0.0");
cmds.add("--app-protocol");
cmds.add(protocol.getName());
cmds.add(DAPR_PROTOCOL.getName());
cmds.add("-placement-host-address");
cmds.add(placementService + ":50005");

Expand All @@ -257,10 +237,15 @@ protected void configure() {

cmds.add("--log-level");
cmds.add(daprLogLevel.toString());
cmds.add("-components-path");
cmds.add("--resources-path");
cmds.add("/dapr-resources");
withCommand(cmds.toArray(new String[]{}));

if (configuration != null) {
String configurationYaml = CONFIGURATION_CONVERTER.convert(configuration);
withCopyToContainer(Transferable.of(configurationYaml), "/dapr-resources/" + configuration.getName() + ".yaml");
}

if (components.isEmpty()) {
components.add(new Component("kvstore", "state.in-memory", "v1", Collections.emptyMap()));
components.add(new Component("pubsub", "pubsub.in-memory", "v1", Collections.emptyMap()));
Expand All @@ -271,28 +256,18 @@ protected void configure() {
}

for (Component component : components) {
String componentYaml = componentToYaml(component);
String componentYaml = COMPONENT_CONVERTER.convert(component);
withCopyToContainer(Transferable.of(componentYaml), "/dapr-resources/" + component.getName() + ".yaml");
}

for (Subscription subscription : subscriptions) {
String subscriptionYaml = subscriptionToYaml(subscription);
String subscriptionYaml = SUBSCRIPTION_CONVERTER.convert(subscription);
withCopyToContainer(Transferable.of(subscriptionYaml), "/dapr-resources/" + subscription.getName() + ".yaml");
}

dependsOn(placementContainer);
}

public String subscriptionToYaml(Subscription subscription) {
Map<String, Object> subscriptionMap = subscriptionToMap(subscription);
return yaml.dumpAsMap(subscriptionMap);
}

public String componentToYaml(Component component) {
Map<String, Object> componentMap = componentToMap(component);
return yaml.dumpAsMap(componentMap);
}

public String getAppName() {
return appName;
}
Expand All @@ -313,21 +288,6 @@ public static DockerImageName getDefaultImageName() {
return DEFAULT_IMAGE_NAME;
}

public DaprContainer withPlacementImage(String placementDockerImageName) {
this.placementDockerImageName = placementDockerImageName;
return this;
}

public DaprContainer withReusablePlacement(boolean reuse) {
this.shouldReusePlacement = reuse;
return this;
}

public DaprContainer withPlacementContainer(DaprPlacementContainer placementContainer) {
this.placementContainer = placementContainer;
return this;
}

// Required by spotbugs plugin
@Override
public boolean equals(Object o) {
Expand All @@ -338,13 +298,4 @@ public boolean equals(Object o) {
public int hashCode() {
return super.hashCode();
}

private static Yaml getYamlMapper() {
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
options.setPrettyFlow(true);
Representer representer = new Representer(options);
representer.addClassTag(MetadataEntry.class, Tag.MAP);
return new Yaml(representer);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.dapr.testcontainers;

/**
* Configuration settings for Otel tracing.
*/
public class OtelTracingConfigurationSettings implements ConfigurationSettings {
private final String endpointAddress;
private final Boolean isSecure;
private final String protocol;

/**
* Creates a new configuration.
* @param endpointAddress tracing endpoint address
* @param isSecure if the endpoint is secure
* @param protocol tracing protocol
*/
public OtelTracingConfigurationSettings(String endpointAddress, Boolean isSecure, String protocol) {
this.endpointAddress = endpointAddress;
this.isSecure = isSecure;
this.protocol = protocol;
}

public String getEndpointAddress() {
return endpointAddress;
}

public Boolean getSecure() {
return isSecure;
}

public String getProtocol() {
return protocol;
}
}
Loading
Loading