This repository has been archived by the owner on Aug 25, 2024. It is now read-only.
forked from LangStream/langstream
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
be541f1
commit 146df93
Showing
10 changed files
with
399 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
136 changes: 136 additions & 0 deletions
136
...rc/main/java/com/datastax/oss/sga/impl/common/ApplicationInstancePlaceholderResolver.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
package com.datastax.oss.sga.impl.common; | ||
|
||
import com.datastax.oss.sga.api.model.AgentConfiguration; | ||
import com.datastax.oss.sga.api.model.ApplicationInstance; | ||
import com.datastax.oss.sga.api.model.Instance; | ||
import com.datastax.oss.sga.api.model.Module; | ||
import com.datastax.oss.sga.api.model.Pipeline; | ||
import com.datastax.oss.sga.api.model.Resource; | ||
import com.datastax.oss.sga.api.model.StreamingCluster; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.samskivert.mustache.Mustache; | ||
import java.io.IOException; | ||
import java.util.HashMap; | ||
import java.util.LinkedHashMap; | ||
import java.util.Map; | ||
import lombok.SneakyThrows; | ||
|
||
public class ApplicationInstancePlaceholderResolver { | ||
|
||
private static final ObjectMapper mapper = new ObjectMapper(); | ||
|
||
private ApplicationInstancePlaceholderResolver() { | ||
} | ||
|
||
@SneakyThrows | ||
public static ApplicationInstance resolvePlaceholders(ApplicationInstance instance) { | ||
instance = deepCopy(instance); | ||
final Map<String, Object> context = createContext(instance); | ||
|
||
|
||
instance.setInstance(resolveInstance(instance, context)); | ||
instance.setResources(resolveResources(instance, context)); | ||
instance.setModules(resolveModules(instance, context)); | ||
return instance; | ||
} | ||
|
||
static Map<String, Object> createContext(ApplicationInstance application) throws IOException { | ||
Map<String, Object> context = new HashMap<>(); | ||
final Instance instance = application.getInstance(); | ||
if (instance != null) { | ||
context.put("cluster", instance.streamingCluster()); | ||
context.put("globals", instance.globals()); | ||
} | ||
|
||
Map<String, Map<String, Object>> secrets = new HashMap<>(); | ||
if (application.getSecrets() != null && application.getSecrets().secrets() != null) { | ||
application.getSecrets().secrets().forEach((k, v) -> secrets.put(k, v.data())); | ||
} | ||
context.put("secrets", secrets); | ||
context = deepCopy(context); | ||
return context; | ||
} | ||
|
||
private static Map<String, Module> resolveModules(ApplicationInstance instance, Map<String, Object> context) { | ||
Map<String, Module> newModules = new LinkedHashMap<>(); | ||
for (Map.Entry<String, Module> moduleEntry : instance.getModules().entrySet()) { | ||
final Module module = moduleEntry.getValue(); | ||
for (Map.Entry<String, Pipeline> pipelineEntry : module.getPipelines().entrySet()) { | ||
final Pipeline pipeline = pipelineEntry.getValue(); | ||
Map<String, AgentConfiguration> newAgents = new LinkedHashMap<>(); | ||
for (Map.Entry<String, AgentConfiguration> stringAgentConfigurationEntry : pipeline.getAgents() | ||
.entrySet()) { | ||
final AgentConfiguration value = stringAgentConfigurationEntry.getValue(); | ||
value.setConfiguration(resolveMap(context, value.getConfiguration())); | ||
newAgents.put(stringAgentConfigurationEntry.getKey(), value); | ||
} | ||
pipeline.setAgents(newAgents); | ||
} | ||
newModules.put(moduleEntry.getKey(), module); | ||
} | ||
return newModules; | ||
} | ||
|
||
private static Instance resolveInstance(ApplicationInstance applicationInstance, Map<String, Object> context) { | ||
final StreamingCluster newCluster; | ||
final Instance instance = applicationInstance.getInstance(); | ||
if (instance == null) { | ||
return null; | ||
} | ||
final StreamingCluster cluster = instance.streamingCluster(); | ||
if (cluster != null) { | ||
newCluster = new StreamingCluster(cluster.type(), resolveMap(context, cluster.configuration())); | ||
} else { | ||
newCluster = null; | ||
} | ||
return new Instance( | ||
newCluster, | ||
resolveMap(context, instance.globals()) | ||
); | ||
} | ||
|
||
private static Map<String, Resource> resolveResources(ApplicationInstance instance, | ||
Map<String, Object> context) { | ||
Map<String, Resource> newResources = new HashMap<>(); | ||
for (Map.Entry<String, Resource> resourceEntry : instance.getResources().entrySet()) { | ||
final Resource resource = resourceEntry.getValue(); | ||
newResources.put(resourceEntry.getKey(), | ||
new Resource( | ||
resource.id(), | ||
resource.name(), | ||
resource.type(), | ||
resolveMap(context, resource.configuration()) | ||
) | ||
); | ||
} | ||
return newResources; | ||
} | ||
|
||
static Map<String, Object> resolveMap(Map<String, Object> context, Map<String, Object> config) { | ||
|
||
Map<String, Object> resolvedConfig = new HashMap<>(); | ||
if (config == null) { | ||
return resolvedConfig; | ||
} | ||
for (Map.Entry<String, Object> stringObjectEntry : config.entrySet()) { | ||
resolvedConfig.put(stringObjectEntry.getKey(), resolveValue(context, stringObjectEntry.getValue() + "")); | ||
} | ||
return resolvedConfig; | ||
} | ||
|
||
static String resolveValue(Map<String, Object> context, String template) { | ||
return Mustache.compiler() | ||
.compile(template) | ||
.execute(context); | ||
} | ||
|
||
private static ApplicationInstance deepCopy(ApplicationInstance instance) throws IOException { | ||
return mapper.readValue(mapper.writeValueAsBytes(instance), ApplicationInstance.class); | ||
} | ||
|
||
private static Map<String, Object> deepCopy(Map<String, Object> context) throws IOException { | ||
return mapper.readValue(mapper.writeValueAsBytes(context), Map.class); | ||
} | ||
|
||
|
||
} |
12 changes: 12 additions & 0 deletions
12
core/src/main/java/com/datastax/oss/sga/impl/common/PlaceholderEvaluator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.datastax.oss.sga.impl.common; | ||
|
||
import com.datastax.oss.sga.api.model.ApplicationInstance; | ||
|
||
public class PlaceholderEvaluator { | ||
|
||
public static ApplicationInstance evaluate(ApplicationInstance applicationInstance) { | ||
new ApplicationInstance(); | ||
return applicationInstance; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
135 changes: 135 additions & 0 deletions
135
...est/java/com/datastax/oss/sga/impl/common/ApplicationInstancePlaceholderResolverTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package com.datastax.oss.sga.impl.common; | ||
|
||
import com.datastax.oss.sga.api.model.ApplicationInstance; | ||
import com.datastax.oss.sga.api.model.Resource; | ||
import com.datastax.oss.sga.impl.parser.ModelBuilder; | ||
import com.samskivert.mustache.MustacheException; | ||
import java.util.Map; | ||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.Test; | ||
|
||
class ApplicationInstancePlaceholderResolverTest { | ||
|
||
@Test | ||
void testAvailablePlaceholders() throws Exception { | ||
|
||
ApplicationInstance applicationInstance = ModelBuilder | ||
.buildApplicationInstance(Map.of( | ||
"secrets.yaml", """ | ||
secrets: | ||
- name: "OpenAI Azure credentials" | ||
id: "openai-credentials" | ||
data: | ||
accessKey: "my-access-key" | ||
""", | ||
"instance.yaml", """ | ||
instance: | ||
streamingCluster: | ||
type: pulsar | ||
configuration: | ||
webServiceUrl: http://mypulsar.localhost:8080 | ||
globals: | ||
another-url: another-value | ||
open-api-url: http://myurl.localhost:8080/endpoint | ||
""")); | ||
|
||
final Map<String, Object> context = ApplicationInstancePlaceholderResolver.createContext(applicationInstance); | ||
Assertions.assertEquals("my-access-key", | ||
ApplicationInstancePlaceholderResolver.resolveValue(context, | ||
"{{secrets.openai-credentials.accessKey}}")); | ||
Assertions.assertEquals("http://mypulsar.localhost:8080", | ||
ApplicationInstancePlaceholderResolver.resolveValue(context, | ||
"{{cluster.configuration.webServiceUrl}}")); | ||
Assertions.assertEquals("http://myurl.localhost:8080/endpoint", | ||
ApplicationInstancePlaceholderResolver.resolveValue(context, "{{globals.open-api-url}}")); | ||
} | ||
|
||
@Test | ||
void testResolveSecretsInConfiguration() throws Exception { | ||
ApplicationInstance applicationInstance = ModelBuilder | ||
.buildApplicationInstance(Map.of("configuration.yaml", | ||
""" | ||
configuration: | ||
resources: | ||
- type: "openai-azure-config" | ||
name: "OpenAI Azure configuration" | ||
id: "openai-azure" | ||
configuration: | ||
credentials: "{{secrets.openai-credentials.accessKey}}" | ||
url: "{{globals.open-api-url}}" | ||
""", | ||
"secrets.yaml", """ | ||
secrets: | ||
- name: "OpenAI Azure credentials" | ||
id: "openai-credentials" | ||
data: | ||
accessKey: "my-access-key" | ||
""", | ||
"instance.yaml", """ | ||
instance: | ||
globals: | ||
another-url: another-value | ||
open-api-url: http://myurl.localhost:8080/endpoint | ||
""")); | ||
|
||
final ApplicationInstance resolved = | ||
ApplicationInstancePlaceholderResolver.resolvePlaceholders(applicationInstance); | ||
final Resource resource = resolved.getResources().get("openai-azure"); | ||
Assertions.assertEquals("my-access-key", resource.configuration().get("credentials")); | ||
Assertions.assertEquals("http://myurl.localhost:8080/endpoint", resource.configuration().get("url")); | ||
} | ||
|
||
@Test | ||
void testResolveInAgentConfiguration() throws Exception { | ||
ApplicationInstance applicationInstance = ModelBuilder | ||
.buildApplicationInstance(Map.of("module1.yaml", | ||
""" | ||
module: "module-1" | ||
id: "pipeline-1" | ||
topics: | ||
- name: "input-topic" | ||
pipeline: | ||
- name: "sink1" | ||
id: "sink1" | ||
type: "generic-pulsar-sink" | ||
input: "input-topic" | ||
configuration: | ||
sinkType: "some-sink-type-on-your-cluster" | ||
access-key: "{{ secrets.ak.value }}" | ||
""", | ||
"secrets.yaml", """ | ||
secrets: | ||
- name: "OpenAI Azure credentials" | ||
id: "ak" | ||
data: | ||
value: "my-access-key" | ||
""")); | ||
|
||
final ApplicationInstance resolved = | ||
ApplicationInstancePlaceholderResolver.resolvePlaceholders(applicationInstance); | ||
Assertions.assertEquals("my-access-key", | ||
resolved.getModule("module-1").getPipelines().values().iterator().next().getAgents().get("sink1") | ||
.getConfiguration() | ||
.get("access-key")); | ||
} | ||
|
||
@Test | ||
void testErrorOnNotFound() throws Exception { | ||
ApplicationInstance applicationInstance = ModelBuilder | ||
.buildApplicationInstance(Map.of("configuration.yaml", | ||
""" | ||
configuration: | ||
resources: | ||
- type: "openai-azure-config" | ||
name: "OpenAI Azure configuration" | ||
id: "openai-azure" | ||
configuration: | ||
credentials: "{{secrets.openai-credentials.invalid}}" | ||
""")); | ||
Assertions.assertThrows(MustacheException.Context.class, () -> { | ||
ApplicationInstancePlaceholderResolver.resolvePlaceholders(applicationInstance); | ||
}); | ||
} | ||
} |
Oops, something went wrong.