Skip to content

Commit ba2a981

Browse files
Add AWS AppConfig Freeform Configuration Provider support (#186)
* Add AppConfig centralized config provider * Update Readme to include AWS AppConfig * Add tests and Samples for AWS AppConfig Service * Update AwsTestProperty * Update AppConfig provider URL format in docs to include query parameters * Address Review Comment
1 parent 16894da commit ba2a981

File tree

8 files changed

+480
-3
lines changed

8 files changed

+480
-3
lines changed

ojdbc-provider-aws/README.md

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ Provider</a></dt>
1212
<dt><a href="#aws-secrets-manager-config-provider">AWS Secrets Manager Configuration
1313
Provider</a></dt>
1414
<dd>Provides connection properties managed by the Secrets Manager service</dd>
15-
<dt><a href="#aws-parameter-store-config-provider">AWS Parameter Store Configuration
16-
Provider</a></dt>
15+
<dt><a href="#aws-parameter-store-config-provider">AWS Parameter Store Configuration Provider</a></dt>
1716
<dd>Provides connection properties managed by the Systems Manager Parameter Store</dd>
17+
<dt><a href="#aws-appconfig-freeform-config-provider">AWS AppConfig Freeform Configuration Provider</a></dt>
18+
<dd>Provides connection properties managed by the AWS AppConfig Freeform Configuration service</dd>
1819
<dt><a href="#common-parameters-for-centralized-config-providers">Common Parameters for Centralized Config Providers</a></dt>
1920
<dd>Common parameters supported by the config providers</dd>
2021
<dt><a href="#caching-configuration">Caching configuration</a></dt>
@@ -206,6 +207,30 @@ jdbc:oracle:thin:@config-awsparameterstore://{parameter-name}
206207

207208
The JSON payload stored in the parameter should follow the same format as described in [AWS S3 Configuration Provider](#json-payload-format).
208209

210+
## AWS AppConfig Freeform Config Provider
211+
The Oracle DataSource uses the prefix `jdbc:oracle:thin:@config-awsappconfig` to identify that the freeform
212+
configuration parameters should be loaded using AWS AppConfig. Users need to specify the application identifier or name, along with the environment and configuration profile
213+
214+
A URL with the following format is valid:
215+
216+
<pre>
217+
jdbc:oracle:thin:@config-awsappconfig://{application-identifier}[?appconfig_environment={environment-id-or-name}&appconfig_profile={profile-id-or-name}]
218+
</pre>
219+
220+
All three values are required for the AppConfig Freeform Configuration Provider:
221+
222+
- `{application-identifier}`: the name or ID of your AppConfig application, as defined in [AWS AppConfig](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/appconfigdata/AppConfigDataClient.html). This must be provided directly in the URL.
223+
- `{environment-id-or-name}`: the name or ID of the environment within the application (e.g., dev, prod). This can be provided as a URL parameter (`appconfig_environment`), a system property (`aws.appconfig.environment`), or an environment variable (`AWS_APP_CONFIG_ENVIRONMENT`).
224+
- `{profile-id-or-name}`: the name or ID of the configuration profile that contains your settings. This can be provided as a URL parameter (`appconfig_profile`), a system property (`aws.appconfig.profile`), or an environment variable (`AWS_APP_CONFIG_PROFILE`).
225+
226+
Example using all parameters in the URL:
227+
<pre>
228+
jdbc:oracle:thin:@config-awsappconfig://app-name?appconfig_environment=your-environment&appconfig_profile=your-profile
229+
</pre>
230+
231+
Alternatively, you can set the environment and profile via system properties (`aws.appconfig.environment, aws.appconfig.profile`) or
232+
environment variables (`AWS_APP_CONFIG_ENVIRONMENT, AWS_APP_CONFIG_PROFILE`).
233+
209234
## Common Parameters for Centralized Config Providers
210235
AWS S3 Configuration Provider and AWS Secrets Manager Configuration Provider
211236
share the same sets of parameters for authentication configuration.

ojdbc-provider-aws/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@
7070
<groupId>software.amazon.awssdk</groupId>
7171
<artifactId>ssm</artifactId>
7272
</dependency>
73+
<dependency>
74+
<groupId>software.amazon.awssdk</groupId>
75+
<artifactId>appconfigdata</artifactId>
76+
</dependency>
7377
<!-- TEST DEPENDENCIES -->
7478
<dependency>
7579
<groupId>com.oracle.database.jdbc</groupId>
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/*
2+
** Copyright (c) 2025 Oracle and/or its affiliates.
3+
**
4+
** The Universal Permissive License (UPL), Version 1.0
5+
**
6+
** Subject to the condition set forth below, permission is hereby granted to any
7+
** person obtaining a copy of this software, associated documentation and/or data
8+
** (collectively the "Software"), free of charge and under any and all copyright
9+
** rights in the Software, and any and all patent rights owned or freely
10+
** licensable by each licensor hereunder covering either (i) the unmodified
11+
** Software as contributed to or provided by such licensor, or (ii) the Larger
12+
** Works (as defined below), to deal in both
13+
**
14+
** (a) the Software, and
15+
** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
16+
** one is included with the Software (each a "Larger Work" to which the Software
17+
** is contributed by such licensors),
18+
**
19+
** without restriction, including without limitation the rights to copy, create
20+
** derivative works of, display, perform, and distribute the Software and make,
21+
** use, sell, offer for sale, import, export, have made, and have sold the
22+
** Software and the Larger Work(s), and to sublicense the foregoing rights on
23+
** either these or other terms.
24+
**
25+
** This license is subject to the following condition:
26+
** The above copyright notice and either this complete permission notice or at
27+
** a minimum a reference to the UPL must be included in all copies or
28+
** substantial portions of the Software.
29+
**
30+
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31+
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32+
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
33+
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
34+
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
35+
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
36+
** SOFTWARE.
37+
*/
38+
39+
package oracle.jdbc.provider.aws.appconfig;
40+
41+
import oracle.jdbc.provider.aws.AwsResourceFactory;
42+
import oracle.jdbc.provider.factory.Resource;
43+
import oracle.jdbc.provider.factory.ResourceFactory;
44+
import oracle.jdbc.provider.parameter.Parameter;
45+
import oracle.jdbc.provider.parameter.ParameterSet;
46+
import software.amazon.awssdk.auth.credentials.AwsCredentials;
47+
import software.amazon.awssdk.regions.Region;
48+
import software.amazon.awssdk.services.appconfigdata.AppConfigDataClient;
49+
import software.amazon.awssdk.services.appconfigdata.model.*;
50+
51+
import java.io.ByteArrayInputStream;
52+
import java.io.InputStream;
53+
54+
import static oracle.jdbc.provider.aws.configuration.AwsConfigurationParameters.*;
55+
56+
/**
57+
* A factory for retrieving <b>Freeform Configurations</b> data from AWS
58+
* AppConfig using the AppConfig Data API. This factory establishes a
59+
* configuration session and fetches the latest configuration data based on
60+
* the provided application, environment, and configuration profile identifiers.
61+
*/
62+
public class AppConfigFactory extends AwsResourceFactory<InputStream> {
63+
64+
/**
65+
* Parameter for the AWS AppConfig application identifier.
66+
* This is a required parameter and can be either the application ID or the
67+
* application name (e.g., "my-app") as defined in AWS AppConfig.
68+
*/
69+
public static final Parameter<String> APP_CONFIG_APPLICATION = Parameter.create();
70+
71+
/**
72+
* Parameter for the AWS AppConfig environment identifier.
73+
* This is a required parameter and can be either the environment ID or the
74+
* environment name as defined in AWS AppConfig.
75+
*/
76+
public static final Parameter<String> APP_CONFIG_ENVIRONMENT = Parameter.create();
77+
78+
/**
79+
* Parameter for the AWS AppConfig configuration profile identifier.
80+
* This is a required parameter and can be either the configuration profile ID
81+
* or the configuration profile name as defined in AWS AppConfig.
82+
*/
83+
public static final Parameter<String> APP_CONFIG_PROFILE = Parameter.create();
84+
85+
// System property and environment variable keys for fallback values
86+
private static final String SYS_PROP_ENVIRONMENT = "aws.appconfig.environment";
87+
private static final String ENV_VAR_ENVIRONMENT = "AWS_APP_CONFIG_ENVIRONMENT";
88+
private static final String SYS_PROP_PROFILE = "aws.appconfig.profile";
89+
private static final String ENV_VAR_PROFILE = "AWS_APP_CONFIG_PROFILE";
90+
91+
92+
private static final ResourceFactory<InputStream> INSTANCE = new AppConfigFactory();
93+
94+
private AppConfigFactory() {}
95+
96+
/**
97+
* @return a singleton of {@code AppConfigFactory}
98+
*/
99+
public static ResourceFactory<InputStream> getInstance() {
100+
return INSTANCE;
101+
}
102+
103+
@Override
104+
public Resource<InputStream> request(AwsCredentials awsCredentials, ParameterSet parameterSet) {
105+
String applicationId = parameterSet.getRequired(APP_CONFIG_APPLICATION);
106+
String environmentId = getParameterWithFallback(APP_CONFIG_ENVIRONMENT, SYS_PROP_ENVIRONMENT, ENV_VAR_ENVIRONMENT, parameterSet);
107+
String configurationProfileId = getParameterWithFallback(APP_CONFIG_PROFILE, SYS_PROP_PROFILE, ENV_VAR_PROFILE, parameterSet);
108+
String region = parameterSet.getOptional(REGION);
109+
110+
try (AppConfigDataClient client = AppConfigDataClient.builder()
111+
.credentialsProvider(() -> awsCredentials)
112+
.applyMutation(builder -> {
113+
if (region != null) builder.region(Region.of(region));
114+
})
115+
.build()) {
116+
117+
final StartConfigurationSessionResponse sessionResponse = client.startConfigurationSession(
118+
StartConfigurationSessionRequest.builder()
119+
.applicationIdentifier(applicationId)
120+
.environmentIdentifier(environmentId)
121+
.configurationProfileIdentifier(configurationProfileId)
122+
.build());
123+
124+
final String token = sessionResponse.initialConfigurationToken();
125+
126+
final GetLatestConfigurationResponse configResponse = client.getLatestConfiguration(
127+
GetLatestConfigurationRequest.builder()
128+
.configurationToken(token)
129+
.build());
130+
return Resource.createPermanentResource(
131+
new ByteArrayInputStream(configResponse.configuration().asByteArray()),
132+
false);
133+
}
134+
}
135+
136+
/**
137+
* Retrieves a parameter value with fallback to system property and environment variable.
138+
* @param parameter The Parameter object to retrieve from ParameterSet.
139+
* @param sysPropKey The system property key to check.
140+
* @param envVarKey The environment variable key to check.
141+
* @param paramSet The ParameterSet to check first.
142+
* @return The parameter value, or throws an exception if not found.
143+
* @throws IllegalArgumentException if the parameter is not found in any source.
144+
*/
145+
private static String getParameterWithFallback(Parameter<String> parameter, String sysPropKey, String envVarKey, ParameterSet paramSet) {
146+
String value = paramSet.getOptional(parameter);
147+
if (value == null) {
148+
value = System.getProperty(sysPropKey);
149+
if (value == null) {
150+
value = System.getenv(envVarKey);
151+
if (value == null) {
152+
throw new IllegalArgumentException(
153+
String.format("Parameter '%s' is required and not found in ParameterSet, system property '%s', or environment variable '%s'",
154+
paramSet.getName(parameter), sysPropKey, envVarKey));
155+
}
156+
}
157+
}
158+
return value;
159+
}
160+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
** Copyright (c) 2025 Oracle and/or its affiliates.
3+
**
4+
** The Universal Permissive License (UPL), Version 1.0
5+
**
6+
** Subject to the condition set forth below, permission is hereby granted to any
7+
** person obtaining a copy of this software, associated documentation and/or data
8+
** (collectively the "Software"), free of charge and under any and all copyright
9+
** rights in the Software, and any and all patent rights owned or freely
10+
** licensable by each licensor hereunder covering either (i) the unmodified
11+
** Software as contributed to or provided by such licensor, or (ii) the Larger
12+
** Works (as defined below), to deal in both
13+
**
14+
** (a) the Software, and
15+
** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
16+
** one is included with the Software (each a "Larger Work" to which the Software
17+
** is contributed by such licensors),
18+
**
19+
** without restriction, including without limitation the rights to copy, create
20+
** derivative works of, display, perform, and distribute the Software and make,
21+
** use, sell, offer for sale, import, export, have made, and have sold the
22+
** Software and the Larger Work(s), and to sublicense the foregoing rights on
23+
** either these or other terms.
24+
**
25+
** This license is subject to the following condition:
26+
** The above copyright notice and either this complete permission notice or at
27+
** a minimum a reference to the UPL must be included in all copies or
28+
** substantial portions of the Software.
29+
**
30+
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31+
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32+
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
33+
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
34+
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
35+
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
36+
** SOFTWARE.
37+
*/
38+
39+
package oracle.jdbc.provider.aws.configuration;
40+
41+
import oracle.jdbc.driver.configuration.OracleConfigurationParsableProvider;
42+
import oracle.jdbc.provider.aws.appconfig.AppConfigFactory;
43+
import oracle.jdbc.provider.parameter.ParameterSet;
44+
import oracle.jdbc.provider.parameter.ParameterSetParser;
45+
import oracle.jdbc.util.OracleConfigurationCache;
46+
47+
import java.io.InputStream;
48+
import java.util.HashMap;
49+
import java.util.Map;
50+
51+
import static oracle.jdbc.provider.aws.appconfig.AppConfigFactory.*;
52+
import static oracle.jdbc.provider.aws.configuration.AwsConfigurationParameters.KEY;
53+
import static oracle.jdbc.provider.aws.configuration.AwsConfigurationParameters.REGION;
54+
55+
/**
56+
* A provider for JSON payload which contains configuration data from AWS
57+
* AppConfig
58+
* See {@link #getInputStream(String)} for the spec of the JSON payload.
59+
**/
60+
public class AwsAppConfigConfigurationProvider extends OracleConfigurationParsableProvider {
61+
62+
static final ParameterSetParser PARAMETER_SET_PARSER = AwsConfigurationParameters.configureBuilder(
63+
ParameterSetParser.builder()
64+
.addParameter("value", APP_CONFIG_APPLICATION)
65+
.addParameter("appconfig_environment", APP_CONFIG_ENVIRONMENT)
66+
.addParameter("appconfig_profile", APP_CONFIG_PROFILE)
67+
.addParameter("key", KEY)
68+
.addParameter("AWS_REGION", REGION))
69+
.build();
70+
71+
/**
72+
* {@inheritDoc}
73+
* <p>
74+
* Returns the JSON payload stored in AWS AppConfig.
75+
* </p>
76+
*
77+
* @param parameterName The application identifier or name for the
78+
* configuration
79+
* @return JSON payload as an InputStream
80+
*/
81+
@Override
82+
public InputStream getInputStream(String parameterName) {
83+
final String VALUE = "value";
84+
Map<String, String> opts = new HashMap<>(options);
85+
opts.put(VALUE, parameterName);
86+
87+
ParameterSet parameters = PARAMETER_SET_PARSER.parseNamedValues(opts);
88+
89+
return AppConfigFactory.getInstance()
90+
.request(parameters)
91+
.getContent();
92+
}
93+
94+
@Override
95+
public String getType() {
96+
return "awsappconfig";
97+
}
98+
99+
/**
100+
* {@inheritDoc}
101+
* @return cache of this provider which is used to store configuration
102+
*/
103+
@Override
104+
public OracleConfigurationCache getCache() {
105+
return CACHE;
106+
}
107+
108+
/**
109+
* {@inheritDoc}
110+
* @return the parser type
111+
*/
112+
@Override
113+
public String getParserType(String location) {
114+
return "json";
115+
}
116+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
oracle.jdbc.provider.aws.configuration.AwsS3ConfigurationProvider
22
oracle.jdbc.provider.aws.configuration.AwsSecretsManagerConfigurationProvider
3-
oracle.jdbc.provider.aws.configuration.AwsParameterStoreConfigurationProvider
3+
oracle.jdbc.provider.aws.configuration.AwsParameterStoreConfigurationProvider
4+
oracle.jdbc.provider.aws.configuration.AwsAppConfigConfigurationProvider

ojdbc-provider-aws/src/test/java/oracle/provider/aws/AwsTestProperty.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
public enum AwsTestProperty {
4141
AWS_S3_URL,
4242
AWS_SECRETS_MANAGER_URL,
43+
AWS_APP_CONFIG_URL,
4344
AWS_REGION,
4445
DB_CREDENTIALS_SECRET_NAME,
4546
TNSNAMES_SECRET_NAME,

0 commit comments

Comments
 (0)