Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ public class MyTest {
By default, the DB, the Server and the Agent are started using Docker containers.
See below for other options.

Server and Agent container image versions can be customized programmatically, or
overridden using environment variables (handy for testing against alternative
versions or custom images.

```
export TESTCONTAINERS_CONCORD_DB_IMAGE=myregistry/library/postgres:10
export TESTCONTAINERS_CONCORD_SERVER_IMAGE=myregistry/altowner/concord-server:custom-tag
export TESTCONTAINERS_CONCORD_AGENT_IMAGE=myregistry/myowner/concord-agent:custom-tag
$ ./mvnw clean install
```

See [test cases](./src/test/java/ca/ibodrov/concord/testcontainers/RuleTest.java) for
details.

Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
</developers>

<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
Expand Down
1 change: 0 additions & 1 deletion testcontainers-concord-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
@SuppressWarnings({"unchecked"})
public class Concord<T extends Concord<T>> implements AutoCloseable {

private static final String TESTCONTAINERS_CONCORD_DB_IMAGE = "TESTCONTAINERS_CONCORD_DB_IMAGE";
private static final String TESTCONTAINERS_CONCORD_SERVER_IMAGE = "TESTCONTAINERS_CONCORD_SERVER_IMAGE";
private static final String TESTCONTAINERS_CONCORD_AGENT_IMAGE = "TESTCONTAINERS_CONCORD_AGENT_IMAGE";

private boolean startAgent = true;
private boolean streamAgentLogs;
private boolean streamServerLogs;
Expand Down Expand Up @@ -173,7 +177,7 @@ public T mode(Mode mode) {
}

public String dbImage() {
return dbImage;
return Utils.getEnv(TESTCONTAINERS_CONCORD_DB_IMAGE, dbImage);
}

public T dbImage(String dbImage) {
Expand All @@ -182,7 +186,7 @@ public T dbImage(String dbImage) {
}

public String serverImage() {
return serverImage;
return Utils.getEnv(TESTCONTAINERS_CONCORD_SERVER_IMAGE, serverImage);
}

/**
Expand All @@ -194,7 +198,7 @@ public T serverImage(String image) {
}

public String agentImage() {
return agentImage;
return Utils.getEnv(TESTCONTAINERS_CONCORD_AGENT_IMAGE, agentImage);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
*/

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import com.walmartlabs.concord.common.Posix;
import org.slf4j.Logger;
Expand All @@ -39,9 +38,9 @@
import org.testcontainers.utility.MountableFile;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.*;
Expand Down Expand Up @@ -226,7 +225,7 @@ public void stop() {
private static Path prepareConfigurationFile(Path persistentWorkDir, Supplier<String> extraConfigurationSupplier) {
try {
Path dst = Files.createTempFile("server", ".dst");
String s = Resources.toString(DockerConcordEnvironment.class.getResource("docker/concord.conf"), Charsets.UTF_8);
String s = Resources.toString(DockerConcordEnvironment.class.getResource("docker/concord.conf"), StandardCharsets.UTF_8);
s = s.replace("%%agentToken%%", Utils.randomToken());
s = s.replace("%%persistentWorkDir%%", persistentWorkDir != null ? persistentWorkDir.toString() : "");
s = s.replace("%%extra%%", extraConfigurationSupplier != null ? extraConfigurationSupplier.get() : "");
Expand All @@ -251,7 +250,7 @@ private void fireAfterStart(ContainerType type, Container<?> container) {
this.containerListeners.forEach(l -> l.afterStart(type, container));
}

private static ImagePullPolicy pullPolicy(Concord opts) {
private static ImagePullPolicy pullPolicy(Concord<?> opts) {
ImagePullPolicy p = opts.pullPolicy();

if (p == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,7 @@
import java.io.IOException;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand Down Expand Up @@ -114,7 +111,7 @@ public void start() {
Path conf = prepareConfigurationFile();
System.setProperty("ollie.conf", conf.toAbsolutePath().toString());

this.server = ConcordServer.withAutoWiring().start();
this.server = ConcordServer.withModules().start();

waitForHttp("http://localhost:" + apiPort() + "/api/v1/server/ping", 60000);
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,26 @@ public Projects(ApiClient apiClient) {
}

public ProjectOperationResponse create(String orgName, String projectName) throws ApiException {
return create(orgName, projectName, true); // true to maintain backward compatibility
}

public ProjectOperationResponse create(String orgName, String projectName, boolean allowPayloads) throws ApiException {
ProjectEntry projectEntry = new ProjectEntry()
.name(projectName)
.orgName(orgName)
.acceptsRawPayload(Boolean.TRUE)
.rawPayloadMode(ProjectEntry.RawPayloadModeEnum.TEAM_MEMBERS)
.visibility(ProjectEntry.VisibilityEnum.PUBLIC);

return projectApi.createOrUpdateProject(orgName, projectEntry);
var project = projectApi.createOrUpdateProject(orgName, projectEntry);

if (allowPayloads) {
Copy link
Contributor

@ibodrov ibodrov Apr 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, why not set RawPayloadModeEnum.EVERYONE instead?

Copy link
Contributor Author

@benbroadaway benbroadaway Apr 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My rationale: when creating a project in <2.25.0, the default raw payload setting if you only include the "enabled" option, but no level, results in a project that allows payloads from the owner (or an admin). So I was trying to keep that effectively the same.

Unfortunately, doing that involves an extra change -> adding a team to the project, which was not a previous behavior of either this library or the Project API itself.

So something's going to change a little bit either way (EVERYONE vs TEAM). I think I just convinced myself EVERYONE is less of a change even though it's more permissive. If someone has a test that's sensitive to this change, they'd probably hit more unexpectedness from an assumed_team + access_level change than just the access level.

projectApi.updateProjectAccessLevel(orgName, projectName, new ResourceAccessEntry()
.orgName(orgName)
.teamName("default")
.level(ResourceAccessEntry.LevelEnum.OWNER));
}

return project;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,16 @@ public static Path getLocalMavenRepositoryPath() {
return Paths.get(localPath);
}

public static String getEnv(String name, String defValue) {
String envValue = System.getenv(name);

if (envValue == null || envValue.isBlank()) {
return defValue;
} else {
return envValue;
}
}

private Utils() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import com.walmartlabs.concord.client2.ProcessEntry;

import com.walmartlabs.concord.client2.ProcessListFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.Container;
import org.testcontainers.utility.MountableFile;

Expand All @@ -31,21 +33,26 @@
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.List;
import java.util.function.Consumer;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import static ca.ibodrov.concord.testcontainers.Utils.randomString;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class DockerTest {
class DockerTest {

private static final Logger log = LoggerFactory.getLogger(DockerTest.class);
private static Concord<?> concord;
private static final Path testFile = Paths.get("target/testDir/testFile.txt");

@BeforeAll
public static void setUp() {
static void setUp() {
Concord<?> c = new Concord<>()
.mode(Concord.Mode.DOCKER)
.containerListener(new ContainerListener() {
Expand Down Expand Up @@ -75,47 +82,96 @@ public void afterStart(ContainerType type, Container<?> container) {
c.start();

concord = c;

log.info("Concord IT server login: {}/#/login?useApiKey=true", concord.apiBaseUrl());
log.info("Concord IT admin token: {}", concord.environment().apiToken());
}

@AfterAll
public static void tearDown() {
static void tearDown() {
if (concord != null) {
concord.close();
concord = null;
}
}

@Test
public void testApiToken() {
void testApiToken() {
assertNotNull(concord.environment().apiToken());
}

@Test
public void testSimpleFlow() throws Exception {
void testSimpleFlow() throws Exception {
executeSimpleFlow(payload -> {}); // no project
}

@Test
void testSimpleFlowWithProject() throws Exception {
String orgName = "org_" + randomString();
concord.organizations().create(orgName);

String projectName = "project_" + randomString();
// validate backwards compatibility. default project creation would allow payloads
concord.projects().create(orgName, projectName);

executeSimpleFlow(payload -> payload.org(orgName).project(projectName));
}

@Test
void testSimpleFlowWithProjectAndExplicitPayloadSetting() throws Exception {
String orgName = "org_" + randomString();
concord.organizations().create(orgName);

String projectName = "project_" + randomString();
concord.projects().create(orgName, projectName, true);

executeSimpleFlow(payload -> payload.org(orgName).project(projectName));
}

@Test
void testPayloadInProjectWithoutEnablingPayloads() throws Exception {
String orgName = "org_" + randomString();
concord.organizations().create(orgName);

String projectName = "project_" + randomString();
concord.projects().create(orgName, projectName, false);

var ex = assertThrows(Exception.class, () -> executeSimpleFlow(payload -> payload.org(orgName).project(projectName)));
assertTrue(ex.getMessage().contains("The project is not accepting raw payloads"),
"Expected error for not accepting raw payloads");
}

private void executeSimpleFlow(Consumer<Payload> payloadConsumer) throws Exception {
String nameValue = "name_" + System.currentTimeMillis();

String yml = "" +
"flows: \n" +
" default:\n" +
" - log: Hello, ${name}!";
String yml = """
flows:
default:
- log: Hello, ${name}!
""";

var payload = new Payload()
.concordYml(yml)
.arg("name", nameValue);

payloadConsumer.accept(payload);

ConcordProcess p = concord.processes()
.start(new Payload()
.concordYml(yml)
.arg("name", nameValue));
.start(payload);

p.waitForStatus(ProcessEntry.StatusEnum.FINISHED);
p.assertLog(".*Hello, " + nameValue + ".*");
}

@Test
public void testTags() throws Exception {
void testTags() throws Exception {
String tag = "tag_" + System.currentTimeMillis();

String yml = "" +
"flows: \n" +
" default:\n" +
" - log: Hello, Concord!";
String yml = """
flows:
default:
- log: Hello, Concord!
""";

ConcordProcess p1 = concord.processes()
.start(new Payload()
Expand All @@ -135,11 +191,12 @@ public void testTags() throws Exception {
}

@Test
public void testSecrets() throws Exception {
String yml = "" +
"flows: \n" +
" default:\n" +
" - log: ${crypto.exportAsString('Default', 'testSecret', null)}";
void testSecrets() throws Exception {
String yml = """
flows:
default:
- log: ${crypto.exportAsString('Default', 'testSecret', null)}
""";

String mySecretValue = "Hello, I'm a secret value!";
concord.secrets().createSecret(NewSecretQuery.builder()
Expand All @@ -154,11 +211,12 @@ public void testSecrets() throws Exception {
}

@Test
public void testProcessLogStreaming() throws Exception {
String yml = "" +
"flows: \n" +
" default:\n" +
" - log: Hello, Concord!";
void testProcessLogStreaming() throws Exception {
String yml = """
flows:
default:
- log: Hello, Concord!
""";

ConcordProcess p = concord.processes()
.create()
Expand All @@ -171,11 +229,12 @@ public void testProcessLogStreaming() throws Exception {
}

@Test
public void testFilePush() throws Exception {
String yml = "" +
"flows: \n" +
" default:\n" +
" - log: \"${resource.asString('/tmp/testDir/testFile.txt')}\"";
void testFilePush() throws Exception {
String yml = """
flows:
default:
- log: "${resource.asString('/tmp/testDir/testFile.txt')}"
""";

ConcordProcess p = concord.processes()
.create()
Expand Down
6 changes: 6 additions & 0 deletions testcontainers-concord-junit5/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,11 @@
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</dependency>

<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Loading