From c7b0a7f3387e3041c3879b7847f19cb48cb767bf Mon Sep 17 00:00:00 2001 From: Jeroen Vervaeke Date: Wed, 3 Sep 2025 11:44:36 +0100 Subject: [PATCH] feat: implemented connect and listDeployments --- .github/workflows/CI.yml | 18 ++-- Cargo.toml | 6 +- README.md | 4 +- __test__/index.spec.ts | 27 ++++- index.d.ts | 63 +++++++++++- index.js | 6 +- src/lib.rs | 35 ++++++- src/models/list_deployments.rs | 173 +++++++++++++++++++++++++++++++++ src/models/mod.rs | 1 + 9 files changed, 313 insertions(+), 20 deletions(-) create mode 100644 src/models/list_deployments.rs create mode 100644 src/models/mod.rs diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 3caec1a..40bb68d 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -273,15 +273,15 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} license-and-audit: - runs-on: ubuntu-latest - steps: + runs-on: ubuntu-latest + steps: - uses: actions/checkout@v4 - name: Install Rust toolchain run: | rustup update stable rustup default stable rustup component add clippy rustfmt - + - name: Cache cargo registry uses: actions/cache@v4 with: @@ -292,7 +292,7 @@ jobs: key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} restore-keys: | ${{ runner.os }}-cargo- - + - name: Cache cargo tools uses: actions/cache@v4 with: @@ -300,21 +300,21 @@ jobs: key: ${{ runner.os }}-cargo-tools-deny-${{ env.CARGO_DENY_VERSION }}-audit-${{ env.CARGO_AUDIT_VERSION }} restore-keys: | ${{ runner.os }}-cargo-tools- - + - name: Install cargo-deny run: | if ! command -v cargo-deny &> /dev/null; then cargo install --locked --version ${{ env.CARGO_DENY_VERSION }} cargo-deny fi - + - name: Install cargo-audit run: | if ! command -v cargo-audit &> /dev/null; then cargo install --locked --version ${{ env.CARGO_AUDIT_VERSION }} cargo-audit fi - + - name: Run cargo deny run: cargo deny check - + - name: Run cargo audit - run: cargo audit \ No newline at end of file + run: cargo audit diff --git a/Cargo.toml b/Cargo.toml index 70a83b3..9a564ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,10 @@ version = "0.0.0-preview.0" crate-type = ["cdylib"] [dependencies] -atlas-local = { git = "https://github.com/mongodb/atlas-local-lib.git" } -napi = "3.0.0" +anyhow = "1.0.99" +atlas-local = { git = "https://github.com/mongodb/atlas-local-lib.git", rev = "36f56065e891bbe045beeb46489dd7d4142dbd41" } +bollard = "0.19.2" +napi = { version = "3.0.0", features = ["async", "anyhow"] } napi-derive = "3.0.0" [build-dependencies] diff --git a/README.md b/README.md index 10bfc4b..61f1268 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,9 @@ Before using this library, make sure you have: Here's a simple example to get you started: ```typescript -// TODO +const client = await Client.connect(); +const deployments = await client.listDeployments(); +console.log(deployments); ``` ## Development diff --git a/__test__/index.spec.ts b/__test__/index.spec.ts index 067ac9a..e7323be 100644 --- a/__test__/index.spec.ts +++ b/__test__/index.spec.ts @@ -1,8 +1,27 @@ import test from 'ava' -import { plus100 } from '../index' +import { Client } from '../index' -test('sync function from native code', (t) => { - const fixture = 42 - t.is(plus100(fixture), fixture + 100) +test('smoke test: list deployments', async (t) => { + let client: Client | null = null + + try { + // Create client + client = await Client.connect() + } catch (e: any) { + // If docker is not running we get this error + // any other error means failure + t.is(e?.message, 'connect to docker') + return + } + + if (client == null) { + t.fail('Client not created') + return + } + + // List deployments + // We don't care about the number, we're just testing that the method doesn't fail + await client.listDeployments() + t.pass() }) diff --git a/index.d.ts b/index.d.ts index 57ec103..762713c 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,3 +1,64 @@ /* auto-generated by NAPI-RS */ /* eslint-disable */ -export declare function plus100(input: number): number +export declare class Client { + static connect(): Client + listDeployments(): Promise> +} + +export declare const enum BindingType { + Loopback = 'Loopback', + AnyInterface = 'AnyInterface', + Specific = 'Specific', +} + +export interface CreationSource { + type: CreationSourceType + source: string +} + +export declare const enum CreationSourceType { + AtlasCLI = 'AtlasCLI', + Container = 'Container', + Other = 'Other', +} + +export interface Deployment { + containerId: string + name?: string + state: State + portBindings?: MongoDBPortBinding + mongodbType: MongodbType + mongodbVersion: string + creationSource?: CreationSource + localSeedLocation?: string + mongodbInitdbDatabase?: string + mongodbInitdbRootPasswordFile?: string + mongodbInitdbRootPassword?: string + mongodbInitdbRootUsernameFile?: string + mongodbInitdbRootUsername?: string + mongotLogFile?: string + runnerLogFile?: string + doNotTrack?: string + telemetryBaseUrl?: string +} + +export interface MongoDbPortBinding { + type: BindingType + ip: string + port: number +} + +export declare const enum MongodbType { + Community = 'Community', + Enterprise = 'Enterprise', +} + +export declare const enum State { + Created = 'Created', + Dead = 'Dead', + Exited = 'Exited', + Paused = 'Paused', + Removing = 'Removing', + Restarting = 'Restarting', + Running = 'Running', +} diff --git a/index.js b/index.js index bd72661..1702dd6 100644 --- a/index.js +++ b/index.js @@ -393,4 +393,8 @@ if (!nativeBinding) { } module.exports = nativeBinding -module.exports.plus100 = nativeBinding.plus100 +module.exports.Client = nativeBinding.Client +module.exports.BindingType = nativeBinding.BindingType +module.exports.CreationSourceType = nativeBinding.CreationSourceType +module.exports.MongodbType = nativeBinding.MongodbType +module.exports.State = nativeBinding.State diff --git a/src/lib.rs b/src/lib.rs index c01cf9e..6d669cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,39 @@ #![deny(clippy::all)] +use anyhow::{Context, Result}; +use atlas_local::Client as AtlasLocalClient; +use bollard::Docker; use napi_derive::napi; +use crate::models::list_deployments::Deployment; + +pub mod models; + +#[napi] +pub struct Client { + client: AtlasLocalClient, +} + #[napi] -pub fn plus_100(input: u32) -> u32 { - input + 100 +impl Client { + #[napi(factory)] + pub fn connect() -> Result { + let docker = Docker::connect_with_defaults().context("connect to docker")?; + + let atlas_local_client = AtlasLocalClient::new(docker); + + Ok(Client { + client: atlas_local_client, + }) + } + + #[napi] + pub async fn list_deployments(&self) -> Result> { + self + .client + .list_deployments() + .await + .context("list deployments") + .map(|deployments| deployments.into_iter().map(|d| d.into()).collect()) + } } diff --git a/src/models/list_deployments.rs b/src/models/list_deployments.rs new file mode 100644 index 0000000..19bb851 --- /dev/null +++ b/src/models/list_deployments.rs @@ -0,0 +1,173 @@ +use napi_derive::napi; + +#[napi(object)] +pub struct Deployment { + // Identifiers + pub container_id: String, + pub name: Option, + + // Docker specific + pub state: State, + pub port_bindings: Option, + + // MongoDB details (MongoD) + pub mongodb_type: MongodbType, + pub mongodb_version: String, + + // Creation source + pub creation_source: Option, + + // Initial database configuration + pub local_seed_location: Option, + pub mongodb_initdb_database: Option, + pub mongodb_initdb_root_password_file: Option, + pub mongodb_initdb_root_password: Option, + pub mongodb_initdb_root_username_file: Option, + pub mongodb_initdb_root_username: Option, + + // Logging + pub mongot_log_file: Option, + pub runner_log_file: Option, + + // Telemetry + pub do_not_track: Option, + pub telemetry_base_url: Option, +} + +#[napi(string_enum)] +pub enum State { + Created, + Dead, + Exited, + Paused, + Removing, + Restarting, + Running, +} + +#[napi(object)] +pub struct MongoDBPortBinding { + #[napi(js_name = "type")] + pub binding_type: BindingType, + pub ip: String, + pub port: u16, +} + +#[napi(string_enum)] +pub enum BindingType { + Loopback, // 127.0.0.1 + AnyInterface, // 0.0.0.0 + Specific, // Specific IP address +} + +#[napi(string_enum)] +pub enum MongodbType { + Community, + Enterprise, +} + +#[napi(object)] +pub struct CreationSource { + #[napi(js_name = "type")] + pub source_type: CreationSourceType, + pub source: String, +} + +#[napi(string_enum)] +pub enum CreationSourceType { + AtlasCLI, + Container, + Other, +} + +impl From for Deployment { + fn from(source: atlas_local::models::Deployment) -> Self { + Self { + container_id: source.container_id, + name: source.name, + state: source.state.into(), + port_bindings: source.port_bindings.map(MongoDBPortBinding::from), + mongodb_type: source.mongodb_type.into(), + mongodb_version: source.mongodb_version.to_string(), + creation_source: source.creation_source.map(CreationSource::from), + local_seed_location: source.local_seed_location, + mongodb_initdb_database: source.mongodb_initdb_database, + mongodb_initdb_root_password_file: source.mongodb_initdb_root_password_file, + mongodb_initdb_root_password: source.mongodb_initdb_root_password, + mongodb_initdb_root_username_file: source.mongodb_initdb_root_username_file, + mongodb_initdb_root_username: source.mongodb_initdb_root_username, + mongot_log_file: source.mongot_log_file, + runner_log_file: source.runner_log_file, + do_not_track: source.do_not_track, + telemetry_base_url: source.telemetry_base_url, + } + } +} + +impl From for State { + fn from(source: atlas_local::models::State) -> Self { + match source { + atlas_local::models::State::Created => State::Created, + atlas_local::models::State::Dead => State::Dead, + atlas_local::models::State::Exited => State::Exited, + atlas_local::models::State::Paused => State::Paused, + atlas_local::models::State::Removing => State::Removing, + atlas_local::models::State::Restarting => State::Restarting, + atlas_local::models::State::Running => State::Running, + } + } +} + +impl From for MongoDBPortBinding { + fn from(source: atlas_local::models::MongoDBPortBinding) -> Self { + use atlas_local::models::BindingType as SourceType; + + match source.binding_type { + SourceType::Loopback => MongoDBPortBinding { + binding_type: BindingType::Loopback, + ip: "127.0.0.1".to_string(), + port: source.port, + }, + SourceType::AnyInterface => MongoDBPortBinding { + binding_type: BindingType::AnyInterface, + ip: "0.0.0.0".to_string(), + port: source.port, + }, + SourceType::Specific(ip) => MongoDBPortBinding { + binding_type: BindingType::Specific, + ip: ip.to_string(), + port: source.port, + }, + } + } +} + +impl From for MongodbType { + fn from(source: atlas_local::models::MongodbType) -> Self { + match source { + atlas_local::models::MongodbType::Community => MongodbType::Community, + atlas_local::models::MongodbType::Enterprise => MongodbType::Enterprise, + } + } +} + +impl From for CreationSource { + fn from(source: atlas_local::models::CreationSource) -> Self { + use atlas_local::models::CreationSource as CreationSourceSource; + + match source { + CreationSourceSource::AtlasCLI => CreationSource { + source_type: CreationSourceType::AtlasCLI, + source: "ATLASCLI".to_string(), + }, + CreationSourceSource::Container => CreationSource { + source_type: CreationSourceType::Container, + source: "CONTAINER".to_string(), + }, + CreationSourceSource::Unknown(source) => CreationSource { + source_type: CreationSourceType::Other, + source, + }, + } + } +} diff --git a/src/models/mod.rs b/src/models/mod.rs new file mode 100644 index 0000000..0d7467e --- /dev/null +++ b/src/models/mod.rs @@ -0,0 +1 @@ +pub mod list_deployments;