Skip to content

Commit 008ac77

Browse files
committed
feat: Separate env-utils module
1 parent edf2370 commit 008ac77

12 files changed

Lines changed: 403 additions & 175 deletions

File tree

env-utils/pom.xml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
Copyright © 2026 DataSQRL (contact@datasqrl.com)
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
18+
-->
19+
<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">
20+
<modelVersion>4.0.0</modelVersion>
21+
22+
<parent>
23+
<groupId>com.datasqrl.flinkrunner</groupId>
24+
<artifactId>flink-sql-runner-parent</artifactId>
25+
<version>1.0-SNAPSHOT</version>
26+
</parent>
27+
28+
<artifactId>env-utils</artifactId>
29+
<name>Environment Utilities</name>
30+
31+
<dependencies>
32+
<!-- Add logging framework, to produce console output when running in the IDE. -->
33+
<!-- These dependencies are excluded from the application JAR by default. -->
34+
<dependency>
35+
<groupId>org.slf4j</groupId>
36+
<artifactId>slf4j-api</artifactId>
37+
<scope>provided</scope>
38+
</dependency>
39+
40+
<dependency>
41+
<groupId>com.fasterxml.jackson.core</groupId>
42+
<artifactId>jackson-databind</artifactId>
43+
</dependency>
44+
</dependencies>
45+
</project>

flink-sql-runner/src/main/java/com/datasqrl/flinkrunner/EnvUtils.java renamed to env-utils/src/main/java/com/datasqrl/flinkrunner/utils/EnvUtils.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,17 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package com.datasqrl.flinkrunner;
16+
package com.datasqrl.flinkrunner.utils;
1717

1818
import java.util.HashMap;
1919
import java.util.Map;
2020
import java.util.UUID;
21+
import lombok.AccessLevel;
22+
import lombok.NoArgsConstructor;
2123

2224
/** Utility class for environment variable operations. */
23-
final class EnvUtils {
24-
25+
@NoArgsConstructor(access = AccessLevel.PRIVATE)
26+
public final class EnvUtils {
2527
/**
2628
* Returns a map of environment variables with deployment-specific defaults.
2729
*
@@ -35,15 +37,18 @@ final class EnvUtils {
3537
*
3638
* @return an immutable map containing all environment variables with defaults applied
3739
*/
38-
static Map<String, String> getEnvWithDefaults() {
40+
public static Map<String, String> getEnvWithDeploymentDefaults() {
3941
var env = new HashMap<>(System.getenv());
40-
env.putIfAbsent("DEPLOYMENT_ID", UUID.randomUUID().toString());
41-
env.putIfAbsent("DEPLOYMENT_TIMESTAMP", String.valueOf(System.currentTimeMillis()));
42+
getDeploymentDefaults().forEach(env::putIfAbsent);
4243

4344
return Map.copyOf(env);
4445
}
4546

46-
private EnvUtils() {
47-
throw new UnsupportedOperationException();
47+
public static Map<String, String> getDeploymentDefaults() {
48+
return Map.of(
49+
"DEPLOYMENT_ID",
50+
UUID.randomUUID().toString(),
51+
"DEPLOYMENT_TIMESTAMP",
52+
String.valueOf(System.currentTimeMillis()));
4853
}
4954
}
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
/*
2+
* Copyright © 2026 DataSQRL (contact@datasqrl.com)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.datasqrl.flinkrunner.utils;
17+
18+
import com.fasterxml.jackson.core.JsonParser;
19+
import com.fasterxml.jackson.databind.DeserializationContext;
20+
import com.fasterxml.jackson.databind.JsonDeserializer;
21+
import com.fasterxml.jackson.databind.ObjectMapper;
22+
import com.fasterxml.jackson.databind.module.SimpleModule;
23+
import java.io.IOException;
24+
import java.util.HashMap;
25+
import java.util.HashSet;
26+
import java.util.Map;
27+
import java.util.regex.Matcher;
28+
import java.util.regex.Pattern;
29+
import lombok.extern.slf4j.Slf4j;
30+
31+
/**
32+
* Environment variable resolving functionality.
33+
*
34+
* <p>Resolver instances replace {@code ${VAR}} placeholders with values from a configured
35+
* environment map. Missing variables either fail resolution in strict mode or remain unresolved in
36+
* non-strict mode.
37+
*/
38+
@Slf4j
39+
public class EnvVarResolver {
40+
41+
private static final Pattern ENVIRONMENT_VARIABLE_PATTERN = Pattern.compile("\\$\\{(.*?)\\}");
42+
43+
private final Map<String, String> envVars;
44+
private final ObjectMapper objectMapper;
45+
private final boolean strict;
46+
47+
private EnvVarResolver(Map<String, String> envVars, boolean strict) {
48+
this.envVars = envVars;
49+
this.strict = strict;
50+
objectMapper = initObjectMapper();
51+
}
52+
53+
/**
54+
* Creates a strict resolver backed by the current process environment.
55+
*
56+
* @return a resolver backed by {@link System#getenv()}
57+
*/
58+
public static EnvVarResolver of() {
59+
return of(true);
60+
}
61+
62+
/**
63+
* Creates a resolver backed by the current process environment.
64+
*
65+
* @param strict whether missing variables should fail resolution
66+
* @return a resolver backed by {@link System#getenv()}
67+
*/
68+
public static EnvVarResolver of(boolean strict) {
69+
return of(System.getenv(), strict);
70+
}
71+
72+
/**
73+
* Creates a strict resolver backed by the supplied environment variables.
74+
*
75+
* @param envVars environment variables used for placeholder resolution
76+
* @return a resolver backed by the supplied environment variables
77+
*/
78+
public static EnvVarResolver of(Map<String, String> envVars) {
79+
return of(envVars, true);
80+
}
81+
82+
/**
83+
* Creates a resolver backed by the supplied environment variables.
84+
*
85+
* @param envVars environment variables used for placeholder resolution
86+
* @param strict whether missing variables should fail resolution
87+
* @return a resolver backed by the supplied environment variables
88+
*/
89+
public static EnvVarResolver of(Map<String, String> envVars, boolean strict) {
90+
return new EnvVarResolver(envVars, strict);
91+
}
92+
93+
/**
94+
* Creates a strict resolver backed by the current process environment plus deployment defaults.
95+
*
96+
* @return a resolver with deployment defaults applied
97+
*/
98+
public static EnvVarResolver withDeploymentDefaults() {
99+
return withDeploymentDefaults(true);
100+
}
101+
102+
/**
103+
* Creates a resolver backed by the current process environment plus deployment defaults.
104+
*
105+
* @param strict whether missing variables should fail resolution
106+
* @return a resolver with deployment defaults applied
107+
*/
108+
public static EnvVarResolver withDeploymentDefaults(boolean strict) {
109+
return new EnvVarResolver(EnvUtils.getEnvWithDeploymentDefaults(), strict);
110+
}
111+
112+
/**
113+
* Creates a strict resolver backed by the supplied environment variables plus deployment
114+
* defaults.
115+
*
116+
* @param envVars environment variables used for placeholder resolution
117+
* @return a resolver with deployment defaults applied
118+
*/
119+
public static EnvVarResolver withDeploymentDefaults(Map<String, String> envVars) {
120+
return withDeploymentDefaults(envVars, true);
121+
}
122+
123+
/**
124+
* Creates a resolver backed by the supplied environment variables plus deployment defaults.
125+
*
126+
* <p>Deployment defaults are only added when the supplied map does not already contain those
127+
* keys.
128+
*
129+
* @param envVars environment variables used for placeholder resolution
130+
* @param strict whether missing variables should fail resolution
131+
* @return a resolver with deployment defaults applied
132+
*/
133+
public static EnvVarResolver withDeploymentDefaults(Map<String, String> envVars, boolean strict) {
134+
var modifiedEnvVars = new HashMap<>(envVars);
135+
EnvUtils.getDeploymentDefaults().forEach(modifiedEnvVars::putIfAbsent);
136+
137+
return new EnvVarResolver(Map.copyOf(modifiedEnvVars), strict);
138+
}
139+
140+
/**
141+
* Resolves environment variables referenced in a given source string. Searches for environment
142+
* variable references based on {@link EnvVarResolver#ENVIRONMENT_VARIABLE_PATTERN}. If a blank
143+
* source string is passed, it will be returned as is.
144+
*
145+
* @param src given source string that may contain environment variable references
146+
* @return a new string with the resolved environment variables
147+
* @throws IllegalStateException if strict mode is enabled and any referenced environment variable
148+
* is not available
149+
*/
150+
public String resolve(String src) {
151+
if (src == null || src.isBlank()) {
152+
return src;
153+
}
154+
155+
var res = new StringBuilder();
156+
// First pass to replace environment variables
157+
var matcher = ENVIRONMENT_VARIABLE_PATTERN.matcher(src);
158+
var missingEnvVars = new HashSet<String>();
159+
while (matcher.find()) {
160+
var rawKey = matcher.group(1);
161+
String key;
162+
String defaultValue = null;
163+
164+
// Support bash-style default values: ${VAR:-default} or ${VAR:=default}
165+
int splitIdx = rawKey.indexOf(":-");
166+
if (splitIdx == -1) {
167+
splitIdx = rawKey.indexOf(":=");
168+
}
169+
170+
if (splitIdx >= 0) {
171+
key = rawKey.substring(0, splitIdx);
172+
defaultValue = rawKey.substring(splitIdx + 2);
173+
} else {
174+
key = rawKey;
175+
}
176+
177+
if (envVars.containsKey(key)) {
178+
var envValue = envVars.get(key);
179+
matcher.appendReplacement(res, Matcher.quoteReplacement(envValue));
180+
} else if (defaultValue != null) {
181+
matcher.appendReplacement(res, Matcher.quoteReplacement(defaultValue));
182+
} else {
183+
missingEnvVars.add(key);
184+
}
185+
}
186+
matcher.appendTail(res);
187+
188+
if (strict && !missingEnvVars.isEmpty()) {
189+
throw new IllegalStateException(
190+
String.format(
191+
"The following environment variables were referenced, but not found: %s",
192+
missingEnvVars));
193+
}
194+
195+
return res.toString();
196+
}
197+
198+
/**
199+
* Resolves environment variables referenced in a given JSON source string. Searches for
200+
* environment variable references in any string leaf nodes based on {@link
201+
* EnvVarResolver#ENVIRONMENT_VARIABLE_PATTERN}.
202+
*
203+
* @param jsonSrc given JSON source string that may contain environment variable references
204+
* @return JSON string with the resolved environment variables
205+
* @throws IOException if the JSON processing fails in any way
206+
*/
207+
public String resolveInJson(String jsonSrc) throws IOException {
208+
var res = objectMapper.readValue(jsonSrc, Map.class);
209+
210+
return objectMapper.writeValueAsString(res);
211+
}
212+
213+
/**
214+
* Creates an {@link ObjectMapper} configured to resolve environment variables in string values.
215+
*
216+
* @return an object mapper with environment-variable resolution enabled for string
217+
* deserialization
218+
*/
219+
public ObjectMapper initObjectMapper() {
220+
return initObjectMapper(new ObjectMapper());
221+
}
222+
223+
/**
224+
* Configures an {@link ObjectMapper} to resolve environment variables in string values.
225+
*
226+
* @param mapper mapper to configure
227+
* @return the supplied mapper with environment-variable resolution enabled for string
228+
* deserialization
229+
*/
230+
public ObjectMapper initObjectMapper(ObjectMapper mapper) {
231+
var module = new SimpleModule();
232+
module.addDeserializer(String.class, new JsonEnvVarDeserializer());
233+
mapper.registerModule(module);
234+
235+
return mapper;
236+
}
237+
238+
private class JsonEnvVarDeserializer extends JsonDeserializer<String> {
239+
240+
@Override
241+
public String deserialize(JsonParser parser, DeserializationContext ctx) throws IOException {
242+
var value = parser.getText();
243+
return resolve(value);
244+
}
245+
}
246+
}

0 commit comments

Comments
 (0)