diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 7980a87..10dcf8e 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -96,28 +96,28 @@ async fn initialize(with_metadata: bool, context: Context) -> ), supported_environment_variables: vec![ metadata::EnvironmentVariableDefinition { - name: "CONNECTION_URI".to_string(), - description: "The dynamodb connection URI".to_string(), + name: "HASURA_DYNAMODB_AWS_ACCESS_KEY_ID".to_string(), + description: "The AWS DynamoDB access key ID".to_string(), default_value: Some("dynamodbql://read_only_user:readonlyuser@35.236.11.122:5432/v3-docs-sample-app".to_string()), - required: false, + required: true, }, metadata::EnvironmentVariableDefinition { - name: "CLIENT_CERT".to_string(), - description: "The SSL client certificate (Optional)".to_string(), + name: "HASURA_DYNAMODB_AWS_SECRET_ACCESS_KEY".to_string(), + description: "The AWS DynamoDB secret access key".to_string(), default_value: Some(String::new()), - required: false + required: true }, + // metadata::EnvironmentVariableDefinition { + // name: "HASURA_DYNAMODB_AWS_PROVIDER_NAME".to_string(), + // description: "The AWS DynamoDB provider name".to_string(), + // default_value: Some(String::new()), + // required: true, + // }, metadata::EnvironmentVariableDefinition { - name: "CLIENT_KEY".to_string(), - description: "The SSL client key (Optional)".to_string(), + name: "HASURA_DYNAMODB_AWS_REGION".to_string(), + description: "The AWS DynamoDB region".to_string(), default_value: Some(String::new()), - required: false, - }, - metadata::EnvironmentVariableDefinition { - name: "ROOT_CERT".to_string(), - description: "The SSL root certificate (Optional)".to_string(), - default_value: Some(String::new()), - required: false, + required: true, }, ], commands: metadata::Commands { diff --git a/crates/configuration/src/configuration.rs b/crates/configuration/src/configuration.rs index 1405ea5..22a5fe2 100644 --- a/crates/configuration/src/configuration.rs +++ b/crates/configuration/src/configuration.rs @@ -19,8 +19,9 @@ pub const DEFAULT_CONNECTION_URI_VARIABLE: &str = "CONNECTION_URI"; #[derive(Debug)] pub struct Configuration { pub metadata: metadata::Metadata, - // pub service_key: String, - // pub project_id: String, - // pub dataset_id: String, + pub access_key_id: String, + pub secret_access_key: String, + // pub provider_name: String, + pub region: String, // pub mutations_version: Option, } \ No newline at end of file diff --git a/crates/configuration/src/connection_settings.rs b/crates/configuration/src/connection_settings.rs index 4d7c9d6..13b0f36 100644 --- a/crates/configuration/src/connection_settings.rs +++ b/crates/configuration/src/connection_settings.rs @@ -1,25 +1,39 @@ //! Database connection settings. -use crate::values::{Secret, ServiceKey}; +use crate::values::{Secret, AccessKeyId, SecretAccessKey, ProviderName, Region}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -pub const DEFAULT_CONNECTION_URI_PLACEHOLDER: &str = "HASURA_DYNAMODB_CONNECTION_URI_PLACEHOLDER"; +pub const DEFAULT_ACCESS_KEY_ID_VARIABLE: &str = "HASURA_DYNAMODB_AWS_ACCESS_KEY_ID"; +pub const DEFAULT_SECRET_ACCESS_KEY_VARIABLE: &str = "HASURA_DYNAMODB_AWS_SECRET_ACCESS_KEY"; +pub const DEFAULT_PROVIDER_NAME: &str = "HASURA_DYNAMODB_AWS_PROVIDER_NAME"; +pub const DEFAULT_REGION_VARIABLE: &str = "HASURA_DYNAMODB_AWS_REGION"; /// Database connection settings. #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] pub struct DatabaseConnectionSettings { - /// Connection string for a Postgres-compatible database. - pub connection_placeholder: ServiceKey, + pub access_key_id: AccessKeyId, + pub secret_access_key: SecretAccessKey, + // pub provider_name: ProviderName, + pub region: Region, } impl DatabaseConnectionSettings { pub fn empty() -> Self { Self { - connection_placeholder: ServiceKey(Secret::FromEnvironment { - variable: DEFAULT_CONNECTION_URI_PLACEHOLDER.into(), - }) + access_key_id: AccessKeyId(Secret::FromEnvironment { + variable: DEFAULT_ACCESS_KEY_ID_VARIABLE.into(), + }), + secret_access_key: SecretAccessKey(Secret::FromEnvironment { + variable: DEFAULT_SECRET_ACCESS_KEY_VARIABLE.into(), + }), + // provider_name: ProviderName(Secret::FromEnvironment { + // variable: DEFAULT_PROVIDER_NAME.into(), + // }), + region: Region(Secret::FromEnvironment { + variable: DEFAULT_REGION_VARIABLE.into(), + }), } } } diff --git a/crates/configuration/src/lib.rs b/crates/configuration/src/lib.rs index 60f1e59..611c6f6 100644 --- a/crates/configuration/src/lib.rs +++ b/crates/configuration/src/lib.rs @@ -18,5 +18,5 @@ pub use version1::{ // PoolSettings, ParsedConfiguration, }; - +pub use values::connection_info::{AccessKeyId, Region, SecretAccessKey, ProviderName}; pub use to_runtime_configuration::make_runtime_configuration; diff --git a/crates/configuration/src/to_runtime_configuration.rs b/crates/configuration/src/to_runtime_configuration.rs index 7acd2c4..6aa14f3 100644 --- a/crates/configuration/src/to_runtime_configuration.rs +++ b/crates/configuration/src/to_runtime_configuration.rs @@ -6,7 +6,7 @@ use std::collections::BTreeMap; use super::version1::ParsedConfiguration; use crate::environment::Environment; use crate::error::MakeRuntimeConfigurationError; -use crate::values::{Secret, ServiceKey}; +use crate::values::{Secret, AccessKeyId, Region, SecretAccessKey}; use query_engine_metadata::{self, metadata}; // use crate::VersionTag; @@ -16,9 +16,42 @@ pub fn make_runtime_configuration( parsed_config: ParsedConfiguration, environment: impl Environment, ) -> Result { - let service_key = match parsed_config.connection_settings.connection_placeholder { - ServiceKey(Secret::Plain(key)) => Ok(key), - ServiceKey(Secret::FromEnvironment { variable }) => { + let access_key_id = match parsed_config.connection_settings.access_key_id { + AccessKeyId(Secret::Plain(key)) => Ok(key), + AccessKeyId(Secret::FromEnvironment { variable }) => { + environment.read(&variable).map_err(|error| { + MakeRuntimeConfigurationError::MissingEnvironmentVariable { + file_path: super::version1::CONFIGURATION_FILENAME.into(), + message: error.to_string(), + } + }) + } + }?; + let secret_access_key = match parsed_config.connection_settings.secret_access_key { + SecretAccessKey(Secret::Plain(key)) => Ok(key), + SecretAccessKey(Secret::FromEnvironment { variable }) => { + environment.read(&variable).map_err(|error| { + MakeRuntimeConfigurationError::MissingEnvironmentVariable { + file_path: super::version1::CONFIGURATION_FILENAME.into(), + message: error.to_string(), + } + }) + } + }?; + // let provider_name = match parsed_config.connection_settings.provider_name { + // ProviderName(Secret::Plain(key)) => Ok(key), + // ProviderName(Secret::FromEnvironment { variable }) => { + // environment.read(&variable).map_err(|error| { + // MakeRuntimeConfigurationError::MissingEnvironmentVariable { + // file_path: super::version1::CONFIGURATION_FILENAME.into(), + // message: error.to_string(), + // } + // }) + // } + // }?; + let region = match parsed_config.connection_settings.region { + Region(Secret::Plain(key)) => Ok(key), + Region(Secret::FromEnvironment { variable }) => { environment.read(&variable).map_err(|error| { MakeRuntimeConfigurationError::MissingEnvironmentVariable { file_path: super::version1::CONFIGURATION_FILENAME.into(), @@ -29,10 +62,11 @@ pub fn make_runtime_configuration( }?; Ok(crate::Configuration { metadata: convert_metadata(parsed_config.metadata), + access_key_id, + secret_access_key, + // provider_name, + region, // pool_settings: parsed_config.pool_settings, - // service_key, - // project_id, - // dataset_id, // mutations_version: convert_mutations_version(parsed_config.mutations_version), }) } diff --git a/crates/configuration/src/values/connection_info.rs b/crates/configuration/src/values/connection_info.rs index cf06ec3..907cfad 100644 --- a/crates/configuration/src/values/connection_info.rs +++ b/crates/configuration/src/values/connection_info.rs @@ -4,15 +4,60 @@ use serde::{Deserialize, Serialize}; use super::Secret; #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] -pub struct ServiceKey(pub Secret); +pub struct AccessKeyId(pub Secret); -impl From for ServiceKey { +impl From for AccessKeyId { fn from(value: String) -> Self { Self(value.into()) } } -impl From<&str> for ServiceKey { +impl From<&str> for AccessKeyId { + fn from(value: &str) -> Self { + Self::from(value.to_string()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] +pub struct SecretAccessKey(pub Secret); + +impl From for SecretAccessKey { + fn from(value: String) -> Self { + Self(value.into()) + } +} + +impl From<&str> for SecretAccessKey { + fn from(value: &str) -> Self { + Self::from(value.to_string()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] +pub struct ProviderName(pub Secret); + +impl From for ProviderName { + fn from(value: String) -> Self { + Self(value.into()) + } +} + +impl From<&str> for ProviderName { + fn from(value: &str) -> Self { + Self::from(value.to_string()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] +pub struct Region(pub Secret); + +impl From for Region { + fn from(value: String) -> Self { + Self(value.into()) + } +} + +impl From<&str> for Region { fn from(value: &str) -> Self { Self::from(value.to_string()) } diff --git a/crates/configuration/src/values/mod.rs b/crates/configuration/src/values/mod.rs index 2c900dd..f7cdcad 100644 --- a/crates/configuration/src/values/mod.rs +++ b/crates/configuration/src/values/mod.rs @@ -1,7 +1,7 @@ mod secret; mod pool_settings; -mod connection_info; +pub mod connection_info; pub use secret::Secret; pub use pool_settings::PoolSettings; -pub use connection_info::ServiceKey; \ No newline at end of file +pub use connection_info::{AccessKeyId, SecretAccessKey, ProviderName, Region}; \ No newline at end of file diff --git a/crates/configuration/src/version1.rs b/crates/configuration/src/version1.rs index fe7e8a6..a476f00 100644 --- a/crates/configuration/src/version1.rs +++ b/crates/configuration/src/version1.rs @@ -1,12 +1,13 @@ //! Internal Configuration and state for our connector. -use crate::{connection_settings}; +use crate::{connection_settings, AccessKeyId, ProviderName, SecretAccessKey}; use crate::environment::Environment; use crate::error::WriteParsedConfigurationError; use crate::values::{PoolSettings, Secret}; use super::error::ParseConfigurationError; -use aws_config::Region; +use aws_config::meta::region::RegionProviderChain; +// use aws_config::Region; // use aws_smithy_http::endpoint::Endpoint; use aws_sdk_dynamodb::operation::list_tables; use aws_sdk_dynamodb::types::{GlobalSecondaryIndex, KeyType, ProjectionType}; @@ -78,17 +79,49 @@ pub async fn introspect( args: &ParsedConfiguration, environment: impl Environment, ) -> anyhow::Result { - let key_placeholder = args.connection_settings.connection_placeholder.clone(); + let access_key_id = match &args.connection_settings.access_key_id { + AccessKeyId(Secret::Plain(value)) => Cow::Borrowed(value), + AccessKeyId(Secret::FromEnvironment { variable }) => Cow::Owned(environment.read(variable)?), + }; + let secret_access_key = match &args.connection_settings.secret_access_key { + SecretAccessKey(Secret::Plain(value)) => Cow::Borrowed(value), + SecretAccessKey(Secret::FromEnvironment { variable }) => Cow::Owned(environment.read(variable)?), + }; + // let provider_name = match &args.connection_settings.provider_name { + // ProviderName(Secret::Plain(value)) => Cow::Borrowed(value), + // ProviderName(Secret::FromEnvironment { variable }) => Cow::Owned(environment.read(variable)?), + // }; + let region = match &args.connection_settings.region { + crate::Region(Secret::Plain(value)) => Cow::Borrowed(value), + crate::Region(Secret::FromEnvironment { variable }) => Cow::Owned(environment.read(variable)?), + }; + // let access_key_id = args.connection_settings.access_key_id.clone(); + // let secret_access_key = args.connection_settings.secret_access_key.clone(); + // let session_token = args.connection_settings.session_token.clone(); + // let region = args.connection_settings.region.clone(); // let config = aws_config::load_from_env().await; - let config = aws_config::defaults(aws_config::BehaviorVersion::latest()) - .test_credentials() - .region(Region::new("us-west-2")) - // DynamoDB run locally uses port 8000 by default. - .endpoint_url("http://localhost:8085") - .load() - .await; - let dynamodb_local_config = aws_sdk_dynamodb::config::Builder::from(&config).build(); - let client = aws_sdk_dynamodb::Client::from_conf(dynamodb_local_config); + let credentials = aws_sdk_dynamodb::config::Credentials::new( + access_key_id.to_string(), + secret_access_key.to_string(), + None, // Optional session token + None, // Expiration (None for non-expiring) + "my-provider", // Provider name + ); + // let config = aws_config::defaults(aws_config::BehaviorVersion::latest()) + // .test_credentials() + // .region(aws_config::Region::new("us-west-2")) + // // DynamoDB run locally uses port 8000 by default. + // .endpoint_url("http://localhost:8085") + // .load() + // .await; + + // Configure AWS SDK with explicit credentials + let config = Config::builder() + .region(aws_config::Region::new(region.to_string())) + .credentials_provider(credentials) + .build(); + // let dynamodb_local_config = aws_sdk_dynamodb::config::Builder::from(&config).build(); + let client = aws_sdk_dynamodb::Client::from_conf(config); // let endpoint = Endpoint::immutable("http://localhost:8054".parse().unwrap()); // let client = aws_sdk_dynamodb::Client::from_conf( // Builder::from(&config) @@ -279,7 +312,11 @@ pub async fn introspect( Ok(ParsedConfiguration { version: 1, connection_settings: connection_settings::DatabaseConnectionSettings { - connection_placeholder: args.connection_settings.connection_placeholder.clone(), + access_key_id: args.connection_settings.access_key_id.clone(), + secret_access_key: args.connection_settings.secret_access_key.clone(), + // provider_name: args.connection_settings.provider_name.clone(), + region: args.connection_settings.region.clone(), + // connection_placeholder: args.connection_settings.connection_placeholder.clone(), }, metadata: metadata::Metadata { tables: TablesInfo(tables_info), diff --git a/crates/ndc-dynamodb/src/health.rs b/crates/ndc-dynamodb/src/health.rs index 16016e9..8b8c3f5 100644 --- a/crates/ndc-dynamodb/src/health.rs +++ b/crates/ndc-dynamodb/src/health.rs @@ -3,7 +3,7 @@ use ndc_sdk::connector::ErrorResponse; // use gcp_bigquery_client::model::query_request::QueryRequest; -use aws_sdk_dynamodb::Client; +use aws_sdk_dynamodb::{config::http, Client}; /// Check the health of the connector. /// @@ -13,11 +13,8 @@ use aws_sdk_dynamodb::Client; pub async fn health_check( client: &Client, ) -> Result<(), ErrorResponse> { - // TODO: need to parse this from service account key or allow user to provide it - // let project_id = "hasura-development"; - - // // Query - // let mut rs = bigquery_client + // Query + // let mut rs = client // .job() // .query( // project_id, @@ -26,7 +23,24 @@ pub async fn health_check( // .await // .unwrap(); - // silly check + let tables_result = client.list_tables().send().await; + let tables = tables_result.map_err(|op| { + ndc_dynamodb_configuration::error::ParseConfigurationError::IoErrorButStringified(format!( + "Failed to list tables:", + // op.error_message.unwrap() + )) + }); //TODO: handle error + + match tables { + Ok(_res) => { + Ok(()) + } + Err(_e) => { + Err(ErrorResponse::new_internal_with_details(serde_json::Value::Null)) + } + } + + // // silly check // let mut count = 0; // while rs.next_row() { @@ -35,5 +49,5 @@ pub async fn health_check( // assert_eq!(count, 1); - Ok(()) + // Ok(()) } diff --git a/crates/ndc-dynamodb/src/state.rs b/crates/ndc-dynamodb/src/state.rs index d6f9050..4b9c996 100644 --- a/crates/ndc-dynamodb/src/state.rs +++ b/crates/ndc-dynamodb/src/state.rs @@ -3,6 +3,7 @@ //! This is initialized on startup. use aws_config::Region; +use aws_sdk_dynamodb::Config; use thiserror::Error; use tracing::{info_span, Instrument}; @@ -34,15 +35,32 @@ pub async fn create_state( .instrument(info_span!("Setup metrics")) .await?; - let config = aws_config::defaults(aws_config::BehaviorVersion::latest()) - .test_credentials() - .region(Region::new("us-west-2")) - // DynamoDB run locally uses port 8000 by default. - .endpoint_url("http://localhost:8085") - .load() - .await; - let dynamodb_local_config = aws_sdk_dynamodb::config::Builder::from(&config).build(); - let client = aws_sdk_dynamodb::Client::from_conf(dynamodb_local_config); + let access_key_id = configuration.access_key_id.clone(); + let secret_access_key = configuration.secret_access_key.clone(); + let region = configuration.region.clone(); + + let credentials = aws_sdk_dynamodb::config::Credentials::new( + access_key_id.to_string(), + secret_access_key.to_string(), + None, // Optional session token + None, // Expiration (None for non-expiring) + "my-provider", // Provider name + ); + + let config = Config::builder() + .region(aws_config::Region::new(region.to_string())) + .credentials_provider(credentials) + .build(); + + // let config = aws_config::defaults(aws_config::BehaviorVersion::latest()) + // .test_credentials() + // .region(Region::new("us-west-2")) + // // DynamoDB run locally uses port 8000 by default. + // .endpoint_url("http://localhost:8085") + // .load() + // .await; + // let dynamodb_local_config = aws_sdk_dynamodb::config::Builder::from(&config).build(); + let client = aws_sdk_dynamodb::Client::from_conf(config); // let service_account_key = // yup_oauth2::parse_service_account_key(configuration.service_key.clone()).unwrap();