Skip to content

Commit 94c69ee

Browse files
authored
concord-tasks: add createApiKey action (#1194)
1 parent 8465f87 commit 94c69ee

File tree

4 files changed

+200
-35
lines changed

4 files changed

+200
-35
lines changed

it/runtime-v2/src/test/java/com/walmartlabs/concord/it/runtime/v2/ConcordTaskIT.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@
2828
import org.junit.jupiter.api.Test;
2929
import org.junit.jupiter.api.extension.RegisterExtension;
3030

31+
import java.util.List;
32+
import java.util.UUID;
33+
3134
import static com.walmartlabs.concord.it.common.ITUtils.randomString;
32-
import static org.junit.jupiter.api.Assertions.assertEquals;
33-
import static org.junit.jupiter.api.Assertions.assertNotNull;
35+
import static org.junit.jupiter.api.Assertions.*;
3436

3537
public class ConcordTaskIT extends AbstractTest {
3638

@@ -213,4 +215,32 @@ public void testDryRunForChildProcess() throws Exception {
213215
// ---
214216
proc.assertLog(".*Done!.*");
215217
}
218+
219+
220+
@Test
221+
public void testCreateApiKey() throws Exception {
222+
String apiKeyName = "test_" + randomString();
223+
224+
Payload payload = new Payload()
225+
.archive(resource("concord/createApiKey"))
226+
.arg("apiKeyName", apiKeyName);
227+
228+
// ---
229+
230+
ConcordProcess proc = concord.processes().start(payload);
231+
expectStatus(proc, ProcessEntry.StatusEnum.FINISHED);
232+
233+
// ---
234+
235+
proc.assertLog(".*result1=.*");
236+
proc.assertNoLog(".*result2=.*");
237+
proc.assertLog(".*error=.*");
238+
proc.assertLog(".*result3=.*");
239+
240+
// ---
241+
242+
ApiKeysApi apiKeysApi = new ApiKeysApi(concord.apiClient());
243+
List<ApiKeyEntry> apiKeys = apiKeysApi.listUserApiKeys(UUID.fromString("230c5c9c-d9a7-11e6-bcfd-bb681c07b26c"));// admin
244+
assertTrue(apiKeys.stream().anyMatch(k -> k.getName().equals(apiKeyName)));
245+
}
216246
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
configuration:
2+
runtime: "concord-v2"
3+
4+
flows:
5+
default:
6+
# create a new API key
7+
- task: concord
8+
in:
9+
action: createApiKey
10+
username: admin
11+
name: ${apiKeyName}
12+
out: result1
13+
- log: "result1=${result1}"
14+
15+
# try creating a new API key with the same name
16+
- try:
17+
- task: concord
18+
in:
19+
action: createApiKey
20+
username: admin
21+
name: ${apiKeyName}
22+
- log: "result2=${result2}"
23+
error:
24+
- log: "error=${lastError}"
25+
26+
# try creating a new API key with the same name but ignore existings
27+
- task: concord
28+
in:
29+
action: createApiKey
30+
username: admin
31+
name: ${apiKeyName}
32+
ignoreExisting: true
33+
out: result3
34+
- log: "result3=${result3}"

plugins/tasks/concord/src/main/java/com/walmartlabs/concord/client/ConcordTaskCommon.java

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import java.nio.file.Files;
3838
import java.nio.file.Path;
3939
import java.nio.file.Paths;
40+
import java.time.OffsetDateTime;
4041
import java.util.*;
4142
import java.util.concurrent.*;
4243
import java.util.function.Function;
@@ -84,23 +85,16 @@ public ConcordTaskCommon(String sessionToken, ApiClientFactory apiClientFactory,
8485

8586
public TaskResult execute(ConcordTaskParams in) throws Exception {
8687
Action action = in.action();
87-
switch (action) {
88-
case START: {
89-
return startChildProcess((StartParams) in);
90-
}
91-
case STARTEXTERNAL: {
92-
return startExternalProcess((StartExternalParams) in);
93-
}
94-
case FORK: {
95-
return fork((ForkParams) in);
96-
}
97-
case KILL: {
88+
return switch (action) {
89+
case START -> startChildProcess((StartParams) in);
90+
case STARTEXTERNAL -> startExternalProcess((StartExternalParams) in);
91+
case FORK -> fork((ForkParams) in);
92+
case KILL -> {
9893
kill((KillParams) in);
99-
return TaskResult.success();
94+
yield TaskResult.success();
10095
}
101-
default:
102-
throw new IllegalArgumentException("Unsupported action type: " + action);
103-
}
96+
case CREATEAPIKEY -> createApiKey((CreateApiKeyParams) in);
97+
};
10498
}
10599

106100
public List<ProcessEntry> listSubProcesses(ListSubProcesses in) throws Exception {
@@ -185,6 +179,55 @@ public void kill(KillParams in) throws Exception {
185179
}
186180
}
187181

182+
public TaskResult createApiKey(CreateApiKeyParams in) throws Exception {
183+
return withClient(in.baseUrl(), in.apiKey(), client -> {
184+
log.info("Creating a new API key in {}", client.getBaseUri());
185+
UUID userId = in.userId();
186+
if (userId == null) {
187+
String username = in.username();
188+
if (username == null) {
189+
throw new IllegalArgumentException("User ID or user name is required");
190+
}
191+
192+
UsersApi usersApi = new UsersApi(client);
193+
UserEntry user = usersApi.findByUsername(username);
194+
if (user == null) {
195+
throw new IllegalArgumentException("User '" + username + "' not found.");
196+
}
197+
userId = user.getId();
198+
}
199+
200+
String keyName = in.name();
201+
if (keyName != null) {
202+
ApiKeysApi api = new ApiKeysApi(client);
203+
List<ApiKeyEntry> existingKeys = api.listUserApiKeys(userId);
204+
Optional<ApiKeyEntry> maybeExistingKey = existingKeys.stream().filter(k -> k.getName().equals(keyName)).findFirst();
205+
if (maybeExistingKey.isPresent()) {
206+
if (in.ignoreExisting()) {
207+
log.info("API key '{}' already exists, nothing to do.", keyName);
208+
ApiKeyEntry existingKey = maybeExistingKey.get();
209+
return TaskResult.success()
210+
.value("id", existingKey.getId())
211+
.value("expiredAt", Optional.ofNullable(existingKey.getExpiredAt()).map(OffsetDateTime::toString).orElse(null));
212+
} else {
213+
throw new IllegalArgumentException("API key '" + keyName + "' already exists.");
214+
}
215+
}
216+
}
217+
218+
ApiKeysApi apiKeysApi = new ApiKeysApi(client);
219+
CreateApiKeyResponse response = apiKeysApi.createUserApiKey(new CreateApiKeyRequest()
220+
.name(keyName)
221+
.userId(userId)
222+
.userDomain(in.userDomain())
223+
.userType(in.userType()));
224+
225+
return TaskResult.success()
226+
.value("id", response.getId())
227+
.value("key", response.getKey());
228+
});
229+
}
230+
188231
public Map<String, Map<String, Object>> getOutVars(String baseUrl, String apiKey, List<UUID> ids, long timeout) {
189232
return waitForCompletion(ids, timeout, p -> {
190233
try {

plugins/tasks/concord/src/main/java/com/walmartlabs/concord/client/ConcordTaskParams.java

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
* =====
2121
*/
2222

23+
import com.walmartlabs.concord.client2.CreateApiKeyRequest;
2324
import com.walmartlabs.concord.runtime.v2.sdk.MapBackedVariables;
2425
import com.walmartlabs.concord.runtime.v2.sdk.Variables;
2526

@@ -36,23 +37,13 @@ public static ConcordTaskParams of(Variables input, Map<String, Object> defaults
3637
Variables variables = new MapBackedVariables(variablesMap);
3738

3839
ConcordTaskParams p = new ConcordTaskParams(variables);
39-
switch (p.action()) {
40-
case START: {
41-
return new StartParams(variables);
42-
}
43-
case STARTEXTERNAL: {
44-
return new StartExternalParams(variables);
45-
}
46-
case FORK: {
47-
return new ForkParams(variables);
48-
}
49-
case KILL: {
50-
return new KillParams(variables);
51-
}
52-
default: {
53-
throw new IllegalArgumentException("Unsupported action type: " + p.action());
54-
}
55-
}
40+
return switch (p.action()) {
41+
case START -> new StartParams(variables);
42+
case STARTEXTERNAL -> new StartExternalParams(variables);
43+
case FORK -> new ForkParams(variables);
44+
case KILL -> new KillParams(variables);
45+
case CREATEAPIKEY -> new CreateApiKeyParams(variables);
46+
};
5647
}
5748

5849
protected final Variables variables;
@@ -437,6 +428,72 @@ public boolean sync() {
437428
}
438429
}
439430

431+
public static class CreateApiKeyParams extends ConcordTaskParams {
432+
433+
private static final String BASE_URL_KEY = "baseUrl";
434+
private static final String API_KEY = "apiKey";
435+
private static final String USER_ID_KEY = "userId";
436+
private static final String USERNAME_KEY = "username";
437+
private static final String USER_DOMAIN_KEY = "userDomain";
438+
private static final String USER_TYPE_KEY = "userType";
439+
private static final String NAME_KEY = "name";
440+
private static final String IGNORE_EXISTING_KEY = "ignoreExisting";
441+
442+
CreateApiKeyParams(Variables variables) {
443+
super(variables);
444+
}
445+
446+
public String baseUrl() {
447+
return variables.getString(BASE_URL_KEY);
448+
}
449+
450+
public String apiKey() {
451+
return variables.getString(API_KEY);
452+
}
453+
454+
public UUID userId() {
455+
String value = variables.getString(USER_ID_KEY);
456+
if (value == null) {
457+
return null;
458+
}
459+
try {
460+
return UUID.fromString(value);
461+
} catch (IllegalArgumentException e) {
462+
throw new IllegalArgumentException("Expected a user ID, got: " + value);
463+
}
464+
}
465+
466+
public String username() {
467+
return variables.getString(USERNAME_KEY);
468+
}
469+
470+
public String userDomain() {
471+
return variables.getString(USER_DOMAIN_KEY);
472+
}
473+
474+
public CreateApiKeyRequest.UserTypeEnum userType() {
475+
String value = variables.getString(USER_TYPE_KEY);
476+
if (value == null) {
477+
return null;
478+
}
479+
try {
480+
return CreateApiKeyRequest.UserTypeEnum.valueOf(value.toUpperCase());
481+
} catch (IllegalArgumentException e) {
482+
String validTypes = Arrays.stream(CreateApiKeyRequest.UserTypeEnum.values())
483+
.map(CreateApiKeyRequest.UserTypeEnum::toString).collect(Collectors.joining(", "));
484+
throw new IllegalArgumentException("Invalid user type. Expected one of " + validTypes + ", got: " + value);
485+
}
486+
}
487+
488+
public String name() {
489+
return variables.getString(NAME_KEY);
490+
}
491+
492+
public boolean ignoreExisting() {
493+
return variables.getBoolean(IGNORE_EXISTING_KEY, false);
494+
}
495+
}
496+
440497
private static UUID toUUID(Object v) {
441498
if (v instanceof String) {
442499
return UUID.fromString(v.toString());
@@ -472,7 +529,8 @@ public enum Action {
472529
START,
473530
STARTEXTERNAL,
474531
FORK,
475-
KILL
532+
KILL,
533+
CREATEAPIKEY
476534
}
477535

478536
private static class DelegateVariables implements Variables {

0 commit comments

Comments
 (0)