Skip to content

Commit

Permalink
Add support for AppConfigDataClient in AWS SDK (#60)
Browse files Browse the repository at this point in the history
  • Loading branch information
lavenderses authored Jun 1, 2024
1 parent 5f1c8a8 commit c708115
Show file tree
Hide file tree
Showing 18 changed files with 1,024 additions and 20 deletions.
1 change: 1 addition & 0 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dependencies {
compileOnly(libs.openfeature)
implementation(libs.aws.appconfigdata)
implementation(libs.jackson.databind)
implementation(libs.jakarta.annotation.api)

// spotbugsPlugins("com.h3xstream.findsecbugs:findsecbugs-plugin:1.12.0")
// spotbugs("com.github.spotbugs:spotbugs:4.8.0")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.github.lavenderses.aws_app_config_openfeature_provider.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NonNull;
import org.jetbrains.annotations.NotNull;

/**
* A class to mask secret string to avoid it appeared in logging or something.
* This works completely exactly except {@code toString} method. For example, comparison or equality.
*/
@Getter
@AllArgsConstructor
@Builder(toBuilder = true)
public final class Credential implements Comparable<Credential> {

@NotNull
@NonNull
private final String rawValue;

@Override
public String toString() {
return "Masked credentials(***)";
}

@Override
public boolean equals(Object obj) {
if (obj instanceof Credential credential) {
return rawValue.equals(credential.rawValue);
} else {
return false;
}
}

@Override
public int compareTo(@NotNull Credential o) {
return rawValue.compareTo(o.rawValue);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package io.github.lavenderses.aws_app_config_openfeature_provider.proxy;

import io.github.lavenderses.aws_app_config_openfeature_provider.proxy.config.AwsAppConfigProxyConfig;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand All @@ -23,10 +21,6 @@ public abstract class AbstractAwsAppConfigProxy implements AwsAppConfigProxy {

private static final Logger log = LoggerFactory.getLogger(AbstractAwsAppConfigProxy.class);

@NotNull
@NonNull
protected final AwsAppConfigProxyConfig awsAppConfigProxyConfig;

/**
* @param key feature flag key in OpenFeature world
* @param response SDK response object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
* To hide this difference, this interface is introduced (to connect whatever kind of the AWS AppConfig instance from
* Provider implementation).
*/
@FunctionalInterface
public interface AwsAppConfigProxy {
public interface AwsAppConfigProxy extends AutoCloseable {

/**
* Get the feature flag value as JSON schema (see {@link AppConfigValue} for the schema).<br/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import io.github.lavenderses.aws_app_config_openfeature_provider.AwsAppConfigClientOptions;
import io.github.lavenderses.aws_app_config_openfeature_provider.proxy.agent.AwsAppConfigAgentProxy;
import io.github.lavenderses.aws_app_config_openfeature_provider.proxy.appconfig_data_client.AwsAppConfigDataClientProxy;
import io.github.lavenderses.aws_app_config_openfeature_provider.proxy.config.AwsAppConfigAgentProxyConfig;
import io.github.lavenderses.aws_app_config_openfeature_provider.proxy.config.AwsAppConfigDataClientProxyConfig;
import io.github.lavenderses.aws_app_config_openfeature_provider.proxy.config.AwsAppConfigProxyConfig;
import org.jetbrains.annotations.NotNull;

Expand All @@ -16,9 +18,14 @@ public static AwsAppConfigProxy build(

if (config instanceof AwsAppConfigAgentProxyConfig) {
return new AwsAppConfigAgentProxy(
/* optionsi = */ options,
/* option = */ options,
/* config = */ (AwsAppConfigAgentProxyConfig) config
);
} else if (config instanceof AwsAppConfigDataClientProxyConfig) {
return new AwsAppConfigDataClientProxy(
/* options = */ options,
/* config = */ (AwsAppConfigDataClientProxyConfig) config
);
} else {
throw new IllegalArgumentException(String.format("unknown type of config: %s", config));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,7 @@ public final class AwsAppConfigAgentProxy extends AbstractAwsAppConfigProxy {
@NotNull final HttpClient httpClient,
@NotNull final HttpResponse.BodyHandler<String> handler
) {
super(
/* awsAppConfigProxyConfig = */ requireNonNull(awsAppConfigProxyConfig, "awsAppConfigProxyConfig")
);
super();

this.options = requireNonNull(options, "aws");
config = requireNonNull(awsAppConfigProxyConfig, "awsAppConfigProxyConfig");
Expand All @@ -65,16 +63,19 @@ public AwsAppConfigAgentProxy(
@NotNull final AwsAppConfigClientOptions options,
@NotNull final AwsAppConfigAgentProxyConfig awsAppConfigProxyConfig
) {
super(
/* awsAppConfigProxyConfig = */ requireNonNull(awsAppConfigProxyConfig, "awsAppConfigProxyConfig")
);
super();

this.options = requireNonNull(options, "aws");
config = awsAppConfigProxyConfig;
config = requireNonNull(awsAppConfigProxyConfig, "awsAppConfigProxyConfig");
httpClient = setupHttpClient();
handler = HttpResponse.BodyHandlers.ofString();
}

@Override
public void close() {
// nothing to close
}

/**
* Get the feature flag value as JSON schema (see {@link AppConfigValue} for the schema).<br/>
* Note that return value is not validated, this is just raw value from AWS AppConfig.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package io.github.lavenderses.aws_app_config_openfeature_provider.proxy.appconfig_data_client;

import com.fasterxml.jackson.databind.JsonNode;
import io.github.lavenderses.aws_app_config_openfeature_provider.AwsAppConfigClientOptions;
import io.github.lavenderses.aws_app_config_openfeature_provider.app_config_model.AppConfigValue;
import io.github.lavenderses.aws_app_config_openfeature_provider.proxy.AbstractAwsAppConfigProxy;
import io.github.lavenderses.aws_app_config_openfeature_provider.proxy.config.AwsAppConfigDataClientProxyConfig;
import jakarta.annotation.PreDestroy;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
import software.amazon.awssdk.services.appconfigdata.AppConfigDataClient;

import static java.util.Objects.isNull;
import static java.util.Objects.requireNonNull;

/**
* Provider implementation proxy for accessing AWS AppConfig instance via AWS AppConfig client from SDK.
* <br/>
* You can use this with the client instance you prepared. Or just pass the configuration class, then this class creates
* and manages it.
*/
public final class AwsAppConfigDataClientProxy extends AbstractAwsAppConfigProxy {

private final CachedFeatureFlagManager cachedFeatureFlagManager;

@VisibleForTesting
AwsAppConfigDataClientProxy(
@NotNull final CachedFeatureFlagManager cachedFeatureFlagManager
) {
super();

this.cachedFeatureFlagManager = requireNonNull(cachedFeatureFlagManager, "cachedFeatureFlagManager");
}

/**
* Constructor for configuring {@link AppConfigDataClient} with your own configuration.
* <br/>
* When you use this constructor, this provider implementation library is responsible for closing
* {@link AppConfigDataClient} resource. You don't have to worry about.
*
* @param config a configuration to {@link AppConfigDataClient}
*/
public AwsAppConfigDataClientProxy(
@NotNull final AwsAppConfigClientOptions options,
@NotNull final AwsAppConfigDataClientProxyConfig config
) {
super();

cachedFeatureFlagManager = new CachedFeatureFlagManager(
/* options = */ options,
/* config = */ config,
/* startSession = */ true
);
}

@PreDestroy
@Override
public void close() throws Exception {
cachedFeatureFlagManager.close();
}

/**
* Get the feature flag value as JSON schema (see {@link AppConfigValue} for the schema).<br/>
* Note that return value is not validated, this is just raw value from AWS AppConfig.
*
* @param key feature flag key
* @return JSON schema response from AWS AppConfigInstance
*/
@Language("json")
@Nullable
@Override
public String getRawFlagObject(@NotNull final String key) {
final JsonNode featureFlagJsonValue = cachedFeatureFlagManager.getCachedFeatureFlagByKeyFrom(
/* key = */ requireNonNull(key, "key")
);

if (isNull(featureFlagJsonValue)) {
return null;
} else {
return featureFlagJsonValue.toString();
}
}
}
Loading

0 comments on commit c708115

Please sign in to comment.