Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/** @file dynamodb_client_options.h
* @brief LaunchDarkly Server-side DynamoDB Client Options C Binding.
*/
// NOLINTBEGIN modernize-use-using
#pragma once

#include <launchdarkly/bindings/c/export.h>

#ifdef __cplusplus
extern "C" {
// only need to export C interface if
// used by C++ source code
#endif

/**
* @brief LDServerDynamoDBClientOptionsBuilder configures the AWS DynamoDB
* client that a LaunchDarkly DynamoDB integration will use.
*
* All fields are optional. When left unset, the AWS SDK's default provider
* chain (environment variables, shared config, EC2/ECS instance metadata) is
* used to resolve the corresponding field.
*
* The builder is passed by handle to a DynamoDB source or store factory,
* which takes ownership and frees it. Callers only need to call
* @ref LDServerDynamoDBClientOptionsBuilder_Free directly if the builder is
* not passed to a factory.
*/
typedef struct _LDServerDynamoDBClientOptionsBuilder*
LDServerDynamoDBClientOptionsBuilder;

/**
* @brief Creates a new DynamoDB client options builder with all fields unset.
*
* @return A new builder handle.
*/
LD_EXPORT(LDServerDynamoDBClientOptionsBuilder)
LDServerDynamoDBClientOptionsBuilder_New(void);

/**
* @brief Sets the AWS region for the DynamoDB client.
*
* When unset, the AWS SDK resolves the region via the standard region
* provider chain (environment variables, shared config file, instance
* metadata).
*
* @param b Builder. Must not be NULL.
* @param region Region string (e.g. "us-east-1"). Must not be NULL.
*/
LD_EXPORT(void)
LDServerDynamoDBClientOptionsBuilder_Region(
LDServerDynamoDBClientOptionsBuilder b,
char const* region);

/**
* @brief Sets a custom endpoint for the DynamoDB client.
*
* Useful when pointing at DynamoDB Local or LocalStack for testing (e.g.
* "http://localhost:8000"). When unset, the AWS SDK uses the standard
* DynamoDB endpoint for the resolved region.
*
* @param b Builder. Must not be NULL.
* @param endpoint Endpoint URL. Must not be NULL.
*/
LD_EXPORT(void)
LDServerDynamoDBClientOptionsBuilder_Endpoint(
LDServerDynamoDBClientOptionsBuilder b,
char const* endpoint);

/**
* @brief Sets the AWS access key ID for the DynamoDB client.
*
* When none of AccessKeyId, SecretAccessKey, or SessionToken are set, the
* AWS SDK's default credential provider chain is used (environment
* variables, shared credentials file, EC2/ECS roles).
*
* @param b Builder. Must not be NULL.
* @param access_key_id AWS access key ID. Must not be NULL.
*/
LD_EXPORT(void)
LDServerDynamoDBClientOptionsBuilder_AccessKeyId(
LDServerDynamoDBClientOptionsBuilder b,
char const* access_key_id);

/**
* @brief Sets the AWS secret access key for the DynamoDB client.
*
* @param b Builder. Must not be NULL.
* @param secret_access_key AWS secret access key. Must not be NULL.
*/
LD_EXPORT(void)
LDServerDynamoDBClientOptionsBuilder_SecretAccessKey(
LDServerDynamoDBClientOptionsBuilder b,
char const* secret_access_key);

/**
* @brief Sets the AWS session token for the DynamoDB client (for temporary
* credentials).
*
* @param b Builder. Must not be NULL.
* @param session_token AWS session token. Must not be NULL.
*/
LD_EXPORT(void)
LDServerDynamoDBClientOptionsBuilder_SessionToken(
LDServerDynamoDBClientOptionsBuilder b,
char const* session_token);

/**
* @brief Frees a DynamoDB client options builder. Do not call if the builder
* was consumed by a DynamoDB source or store factory.
*
* @param b Builder to free.
*/
LD_EXPORT(void)
LDServerDynamoDBClientOptionsBuilder_Free(
LDServerDynamoDBClientOptionsBuilder b);

#ifdef __cplusplus
}
#endif

// NOLINTEND modernize-use-using
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/** @file dynamodb_source.h
* @brief LaunchDarkly Server-side DynamoDB Source C Binding.
*/
// NOLINTBEGIN modernize-use-using
#pragma once

#include <launchdarkly/server_side/bindings/c/integrations/dynamodb/dynamodb_client_options.h>

#include <launchdarkly/bindings/c/export.h>

#ifdef __cplusplus
extern "C" {
// only need to export C interface if
// used by C++ source code
#endif

/**
* @brief LDServerLazyLoadDynamoDBSource represents a data source for the
* Server-Side SDK backed by Amazon DynamoDB. It is meant to be used in place
* of the standard LaunchDarkly Streaming or Polling data sources.
*
* Call @ref LDServerLazyLoadDynamoDBSource_New to obtain a new instance.
* This instance can be passed into the SDK's DataSystem configuration via
* the LazyLoad builder.
*
* The DynamoDB table must already exist and follow the LaunchDarkly schema:
* a String partition key named `namespace` and a String sort key named
* `key`. The LaunchDarkly Relay Proxy populates the table with this schema;
* this source only reads from it.
*
* Example:
* @code
* // Optional: configure the AWS DynamoDB client. Pass NULL for defaults.
* LDServerDynamoDBClientOptionsBuilder options =
* LDServerDynamoDBClientOptionsBuilder_New();
* LDServerDynamoDBClientOptionsBuilder_Region(options, "us-east-1");
*
* // Stack allocate the result struct, which will hold the result pointer or
* // an error message.
* struct LDServerLazyLoadDynamoDBResult result;
*
* if (!LDServerLazyLoadDynamoDBSource_New("my-table", "testprefix", options,
* &result)) {
* // On failure, you may print the error message (result.error_message),
* // then exit or return.
* }
*
* LDServerLazyLoadBuilder lazy_builder = LDServerLazyLoadBuilder_New();
* LDServerLazyLoadBuilder_SourcePtr(
* lazy_builder, (LDServerLazyLoadSourcePtr)result.source);
*
* LDServerConfigBuilder cfg_builder = LDServerConfigBuilder_New("sdk-123");
* LDServerConfigBuilder_DataSystem_LazyLoad(cfg_builder, lazy_builder);
* @endcode
*
* This implementation is backed by the AWS SDK for C++.
*/
typedef struct _LDServerLazyLoadDynamoDBSource* LDServerLazyLoadDynamoDBSource;

/* Defines the size of the error message buffer in
* LDServerLazyLoadDynamoDBResult.
*/
#ifndef LDSERVER_LAZYLOAD_DYNAMODBSOURCE_ERROR_MESSAGE_SIZE
#define LDSERVER_LAZYLOAD_DYNAMODBSOURCE_ERROR_MESSAGE_SIZE 256
#endif

/**
* @brief Stores the result of calling @ref LDServerLazyLoadDynamoDBSource_New.
*
* On successful creation, source will contain a pointer which may be passed
* into the LaunchDarkly SDK's configuration.
*
* On failure, error_message contains a NULL-terminated string describing the
* error.
*
* The message may be truncated if it was originally longer than
* error_message's buffer size.
*/
struct LDServerLazyLoadDynamoDBResult {
LDServerLazyLoadDynamoDBSource source;
char error_message[LDSERVER_LAZYLOAD_DYNAMODBSOURCE_ERROR_MESSAGE_SIZE];
};

/**
* @brief Creates a new DynamoDB data source suitable for usage in the SDK's
* Lazy Load data system.
*
* In this system, the SDK will query DynamoDB for flag/segment data as
* required, with an in-memory cache to reduce the number of queries.
*
* Data is never written back to DynamoDB by the SDK; the LaunchDarkly Relay
* Proxy populates the table.
*
* @param table_name Name of the DynamoDB table to read from. The table must
* already exist; this function does not create it. Must not be NULL.
*
* @param prefix Prefix to use when reading SDK data from DynamoDB. This
* allows multiple SDK environments to coexist in the same table. Must not
* be NULL.
*
* @param options Optional AWS DynamoDB client configuration. When non-NULL,
* the builder is consumed and freed by this function; do not call
* @ref LDServerDynamoDBClientOptionsBuilder_Free on it afterward. When NULL,
* the AWS SDK's default provider chain is used for region, endpoint, and
* credentials.
*
* @param out_result Pointer to struct where the source pointer or an error
* message should be stored. Must not be NULL.
*
* @return True if the source was created successfully; out_result->source
* will contain the pointer. The caller must either free the pointer with
* @ref LDServerLazyLoadDynamoDBSource_Free, OR pass it into the SDK's
* configuration methods which will take ownership (in which case do not
* call @ref LDServerLazyLoadDynamoDBSource_Free.)
*/
LD_EXPORT(bool)
LDServerLazyLoadDynamoDBSource_New(
char const* table_name,
char const* prefix,
LDServerDynamoDBClientOptionsBuilder options,
struct LDServerLazyLoadDynamoDBResult* out_result);

/**
* @brief Frees a DynamoDB data source pointer. Only necessary to call if not
* passing ownership to SDK configuration.
*/
LD_EXPORT(void)
LDServerLazyLoadDynamoDBSource_Free(LDServerLazyLoadDynamoDBSource source);

#ifdef __cplusplus
}
#endif

// NOLINTEND modernize-use-using
2 changes: 2 additions & 0 deletions libs/server-sdk-dynamodb-source/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ target_sources(${LIBNAME}
dynamodb_big_segment_store.cpp
aws_sdk_guard.cpp
client_factory.cpp
bindings/dynamodb/client_options.cpp
bindings/dynamodb/dynamodb_source.cpp
)


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#include <launchdarkly/server_side/bindings/c/integrations/dynamodb/dynamodb_client_options.h>

#include <launchdarkly/server_side/integrations/dynamodb/options.hpp>

#include <launchdarkly/detail/c_binding_helpers.hpp>

using namespace launchdarkly::server_side::integrations;

#define TO_OPTIONS(ptr) (reinterpret_cast<DynamoDBClientOptions*>(ptr))
#define FROM_OPTIONS(ptr) \
(reinterpret_cast<LDServerDynamoDBClientOptionsBuilder>(ptr))

LD_EXPORT(LDServerDynamoDBClientOptionsBuilder)
LDServerDynamoDBClientOptionsBuilder_New(void) {
return FROM_OPTIONS(new DynamoDBClientOptions{});
}

LD_EXPORT(void)
LDServerDynamoDBClientOptionsBuilder_Region(
LDServerDynamoDBClientOptionsBuilder b,
char const* region) {
LD_ASSERT_NOT_NULL(b);
LD_ASSERT_NOT_NULL(region);
TO_OPTIONS(b)->region = region;
}

LD_EXPORT(void)
LDServerDynamoDBClientOptionsBuilder_Endpoint(
LDServerDynamoDBClientOptionsBuilder b,
char const* endpoint) {
LD_ASSERT_NOT_NULL(b);
LD_ASSERT_NOT_NULL(endpoint);
TO_OPTIONS(b)->endpoint = endpoint;
}

LD_EXPORT(void)
LDServerDynamoDBClientOptionsBuilder_AccessKeyId(
LDServerDynamoDBClientOptionsBuilder b,
char const* access_key_id) {
LD_ASSERT_NOT_NULL(b);
LD_ASSERT_NOT_NULL(access_key_id);
TO_OPTIONS(b)->aws_access_key_id = access_key_id;
}

LD_EXPORT(void)
LDServerDynamoDBClientOptionsBuilder_SecretAccessKey(
LDServerDynamoDBClientOptionsBuilder b,
char const* secret_access_key) {
LD_ASSERT_NOT_NULL(b);
LD_ASSERT_NOT_NULL(secret_access_key);
TO_OPTIONS(b)->aws_secret_access_key = secret_access_key;
}

LD_EXPORT(void)
LDServerDynamoDBClientOptionsBuilder_SessionToken(
LDServerDynamoDBClientOptionsBuilder b,
char const* session_token) {
LD_ASSERT_NOT_NULL(b);
LD_ASSERT_NOT_NULL(session_token);
TO_OPTIONS(b)->aws_session_token = session_token;
}

LD_EXPORT(void)
LDServerDynamoDBClientOptionsBuilder_Free(
LDServerDynamoDBClientOptionsBuilder b) {
delete TO_OPTIONS(b);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#include <launchdarkly/server_side/bindings/c/integrations/dynamodb/dynamodb_source.h>

#include <launchdarkly/server_side/integrations/dynamodb/dynamodb_source.hpp>
#include <launchdarkly/server_side/integrations/dynamodb/options.hpp>

#include <launchdarkly/detail/c_binding_helpers.hpp>

#include <cstring>
#include <utility>

using namespace launchdarkly::server_side::integrations;

LD_EXPORT(bool)
LDServerLazyLoadDynamoDBSource_New(char const* table_name,
char const* prefix,
LDServerDynamoDBClientOptionsBuilder options,
LDServerLazyLoadDynamoDBResult* out_result) {
LD_ASSERT_NOT_NULL(table_name);
LD_ASSERT_NOT_NULL(prefix);
LD_ASSERT_NOT_NULL(out_result);

// Explicitely zero out the error_message buffer in case the error is
// shorter than the buffer.
memset(out_result->error_message, 0,
sizeof(LDServerLazyLoadDynamoDBResult::error_message));

// Ensure the source pointer isn't garbage.
out_result->source = nullptr;

DynamoDBClientOptions opts{};
if (options != nullptr) {
auto* opts_ptr = reinterpret_cast<DynamoDBClientOptions*>(options);
opts = *opts_ptr;
LDServerDynamoDBClientOptionsBuilder_Free(options);
}

auto maybe_source =
DynamoDBDataSource::Create(table_name, prefix, std::move(opts));
if (!maybe_source) {
// Avoid heap allocating another string to pass back to the caller;
// instead, we copy into the buffer and ensure a terminator is present.
// This does mean the message may be truncated.
std::size_t const len = maybe_source.error().copy(
out_result->error_message, sizeof(out_result->error_message) - 1);
out_result->error_message[len] = '\0';
return false;
}

// The pointer is no longer managed and must either be freed by the caller,
// or passed into the SDK which will take ownership.
out_result->source = reinterpret_cast<LDServerLazyLoadDynamoDBSource>(
maybe_source->release());
return true;
}

LD_EXPORT(void)
LDServerLazyLoadDynamoDBSource_Free(LDServerLazyLoadDynamoDBSource source) {
delete reinterpret_cast<DynamoDBDataSource*>(source);
}
Loading