From f203d36685269bb4e4f280fbf95fffb060d4cb3a Mon Sep 17 00:00:00 2001 From: Reyhan Koyun Date: Thu, 16 Oct 2025 11:46:35 -0700 Subject: [PATCH 1/6] Add integration tests workflow and enable parallel test execution - Add integration-tests.yml workflow with OIDC authentication and pull_request_target - Support team member detection and safe-to-test label for external contributors - Remove #[ignore] annotations from all 6 integration tests - Add dynamic port allocation using atomic counter to prevent port conflicts - Add proper randomness to secret naming (thread ID + nanoseconds + random number) - Add fastrand dependency for random number generation - Update test-local.sh to remove --ignored flag and update comments - Enable both sequential (--test-threads=1) and parallel test execution --- .github/workflows/integration-tests.yml | 63 +++++++++++++++++++++ Cargo.lock | 1 + integration-tests/Cargo.toml | 1 + integration-tests/tests/common.rs | 27 ++++++--- integration-tests/tests/secret_retrieval.rs | 6 -- test-local.sh | 6 +- 6 files changed, 87 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/integration-tests.yml diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml new file mode 100644 index 0000000..b2352e2 --- /dev/null +++ b/.github/workflows/integration-tests.yml @@ -0,0 +1,63 @@ +name: Integration Tests + +on: + workflow_dispatch: + push: + branches: ["main"] + pull_request_target: + types: [opened, synchronize, labeled] + +env: + CARGO_TERM_COLOR: always + +jobs: + integration-tests: + runs-on: ubuntu-latest + + # Run if: + # 1. Manual trigger or push to main, OR + # 2. PR from team member (same repo), OR + # 3. PR from external contributor with "safe-to-test" label + if: | + github.event_name != 'pull_request_target' || + github.event.pull_request.head.repo.full_name == github.repository || + contains(github.event.pull_request.labels.*.name, 'safe-to-test') + + permissions: + id-token: write + contents: read + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.sha }} + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }} + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.ROLE_ARN }} + role-session-name: secrets-manager-agent-ci-${{ github.run_id }} + aws-region: us-east-1 + + - name: Build agent binary + run: cargo build + + - name: Run integration tests + run: | + cd integration-tests + cargo test -- --test-threads=1 diff --git a/Cargo.lock b/Cargo.lock index 560f27c..a89c686 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1777,6 +1777,7 @@ dependencies = [ "aws-config", "aws-sdk-secretsmanager", "derive_builder", + "fastrand", "reqwest", "serde_json", "tokio", diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 3a5cf25..54e6594 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -13,4 +13,5 @@ aws-config = "1" aws-sdk-secretsmanager = "1" url = "2" derive_builder = "0.20" +fastrand = "2" diff --git a/integration-tests/tests/common.rs b/integration-tests/tests/common.rs index 4e3d78a..9227cef 100644 --- a/integration-tests/tests/common.rs +++ b/integration-tests/tests/common.rs @@ -5,11 +5,14 @@ use std::env; use std::fmt; use std::path::PathBuf; use std::process::Stdio; +use std::sync::atomic::{AtomicU16, Ordering}; use std::time::Duration; use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::process::Command as TokioCommand; use url::Url; +static PORT_COUNTER: AtomicU16 = AtomicU16::new(2775); + #[derive(Debug, Clone, Copy)] pub enum SecretType { Basic, @@ -78,7 +81,8 @@ pub struct AgentProcess { impl AgentProcess { pub async fn start() -> AgentProcess { - Self::start_with_config(2775, 5).await + let port = PORT_COUNTER.fetch_add(1, Ordering::SeqCst); + Self::start_with_config(port, 5).await } pub async fn start_with_config(port: u16, ttl_seconds: u16) -> AgentProcess { @@ -192,13 +196,20 @@ impl TestSecrets { } pub async fn setup() -> Self { - let test_prefix = format!( - "aws-sm-agent-test-{}", - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_secs() - ); + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + let mut hasher = DefaultHasher::new(); + std::thread::current().id().hash(&mut hasher); + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + .hash(&mut hasher); + fastrand::u64(..).hash(&mut hasher); + let hash = hasher.finish(); + + let test_prefix = format!("aws-sm-agent-test-{}", hash); let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; let client = aws_sdk_secretsmanager::Client::new(&config); diff --git a/integration-tests/tests/secret_retrieval.rs b/integration-tests/tests/secret_retrieval.rs index 89b62af..4758aaf 100644 --- a/integration-tests/tests/secret_retrieval.rs +++ b/integration-tests/tests/secret_retrieval.rs @@ -3,7 +3,6 @@ mod common; use common::*; #[tokio::test] -#[ignore = "integration test - requires AWS credentials"] async fn test_secret_retrieval_by_name() { let secrets = TestSecrets::setup().await; let secret_name = secrets.secret_name(SecretType::Basic); @@ -23,7 +22,6 @@ async fn test_secret_retrieval_by_name() { } #[tokio::test] -#[ignore = "integration test - requires AWS credentials"] async fn test_secret_retrieval_by_arn() { let secrets = TestSecrets::setup().await; let secret_name = secrets.secret_name(SecretType::Basic); @@ -52,7 +50,6 @@ async fn test_secret_retrieval_by_arn() { } #[tokio::test] -#[ignore = "integration test - requires AWS credentials"] async fn test_binary_secret_retrieval() { let secrets = TestSecrets::setup().await; let secret_name = secrets.secret_name(SecretType::Binary); @@ -72,7 +69,6 @@ async fn test_binary_secret_retrieval() { } #[tokio::test] -#[ignore = "integration test - requires AWS credentials"] async fn test_version_stage_retrieval() { let secrets = TestSecrets::setup().await; let secret_name = secrets.secret_name(SecretType::Versioned); @@ -124,7 +120,6 @@ async fn test_version_stage_retrieval() { } #[tokio::test] -#[ignore = "integration test - requires AWS credentials"] async fn test_version_id_retrieval() { let secrets = TestSecrets::setup().await; let secret_name = secrets.secret_name(SecretType::Versioned); @@ -174,7 +169,6 @@ async fn test_version_id_retrieval() { } #[tokio::test] -#[ignore = "integration test - requires AWS credentials"] async fn test_large_secret_retrieval() { let secrets = TestSecrets::setup().await; let secret_name = secrets.secret_name(SecretType::Large); diff --git a/test-local.sh b/test-local.sh index 0feb85d..1973c2b 100755 --- a/test-local.sh +++ b/test-local.sh @@ -15,9 +15,9 @@ cargo build echo "Running integration tests..." cd integration-tests -# Run integration tests (including ignored ones) -# Tests now handle their own setup and cleanup -cargo test -- --test-threads=1 --ignored +# Run integration tests sequentially (matches CI behavior) +# Tests handle their own setup and cleanup +cargo test -- --test-threads=1 cd .. From 7d0b42f6ba6e296e2a2b51fe1cf226a3ca9ac42c Mon Sep 17 00:00:00 2001 From: Reyhan Koyun Date: Thu, 16 Oct 2025 11:54:41 -0700 Subject: [PATCH 2/6] Update safe-to-test label to match AWS conventions Use 'safe to test' (with spaces) to align with other AWS repositories --- .github/workflows/integration-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index b2352e2..c544e15 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -21,7 +21,7 @@ jobs: if: | github.event_name != 'pull_request_target' || github.event.pull_request.head.repo.full_name == github.repository || - contains(github.event.pull_request.labels.*.name, 'safe-to-test') + contains(github.event.pull_request.labels.*.name, 'safe to test') permissions: id-token: write From 6e684a7ad3fe42ab5b578c0b4c718261edb81f6d Mon Sep 17 00:00:00 2001 From: Reyhan Koyun Date: Thu, 16 Oct 2025 13:49:31 -0700 Subject: [PATCH 3/6] Simplify workflow condition to trust-based model - COLLABORATOR: automatic test execution - Others: require 'safe to test' label for manual approval - Remove repo source checks - only author trust level matters --- .github/workflows/integration-tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index c544e15..1088395 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -16,11 +16,11 @@ jobs: # Run if: # 1. Manual trigger or push to main, OR - # 2. PR from team member (same repo), OR - # 3. PR from external contributor with "safe-to-test" label + # 2. PR from trusted author (COLLABORATOR), OR + # 3. PR from untrusted author with "safe to test" label if: | github.event_name != 'pull_request_target' || - github.event.pull_request.head.repo.full_name == github.repository || + github.event.pull_request.author_association == 'COLLABORATOR' || contains(github.event.pull_request.labels.*.name, 'safe to test') permissions: From 3a9a4f31ff2d745401e6a33c24059eef66e46f74 Mon Sep 17 00:00:00 2001 From: Reyhan Koyun Date: Thu, 16 Oct 2025 15:20:23 -0700 Subject: [PATCH 4/6] Simplify integration tests for sequential execution --- .github/workflows/integration-tests.yml | 6 +++--- .github/workflows/rust.yml | 4 ++-- Cargo.lock | 1 - integration-tests/Cargo.toml | 1 - integration-tests/tests/common.rs | 20 ++++---------------- 5 files changed, 9 insertions(+), 23 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 1088395..ec801b1 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -21,14 +21,14 @@ jobs: if: | github.event_name != 'pull_request_target' || github.event.pull_request.author_association == 'COLLABORATOR' || - contains(github.event.pull_request.labels.*.name, 'safe to test') + contains(github.event.pull_request.labels.*.name, 'safe-to-test') permissions: id-token: write contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.sha }} @@ -48,7 +48,7 @@ jobs: key: ${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }} - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v4 + uses: aws-actions/configure-aws-credentials@v5 with: role-to-assume: ${{ secrets.ROLE_ARN }} role-session-name: secrets-manager-agent-ci-${{ github.run_id }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 48e5171..6dd6d1d 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -48,9 +48,9 @@ jobs: - name: Build run: cargo build --verbose - name: Run tests - run: cargo test --verbose --all-features --no-fail-fast + run: cargo test --verbose --all-features --no-fail-fast --workspace --exclude integration-tests - name: Code Coverage - run: cargo llvm-cov --all-features --workspace --codecov --output-path ./codecov.json + run: cargo llvm-cov --all-features --workspace --exclude integration-tests --codecov --output-path ./codecov.json - name: Publish Code Coverage uses: codecov/codecov-action@v5 with: diff --git a/Cargo.lock b/Cargo.lock index a89c686..560f27c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1777,7 +1777,6 @@ dependencies = [ "aws-config", "aws-sdk-secretsmanager", "derive_builder", - "fastrand", "reqwest", "serde_json", "tokio", diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 54e6594..3a5cf25 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -13,5 +13,4 @@ aws-config = "1" aws-sdk-secretsmanager = "1" url = "2" derive_builder = "0.20" -fastrand = "2" diff --git a/integration-tests/tests/common.rs b/integration-tests/tests/common.rs index 9227cef..e531c20 100644 --- a/integration-tests/tests/common.rs +++ b/integration-tests/tests/common.rs @@ -5,14 +5,11 @@ use std::env; use std::fmt; use std::path::PathBuf; use std::process::Stdio; -use std::sync::atomic::{AtomicU16, Ordering}; use std::time::Duration; use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::process::Command as TokioCommand; use url::Url; -static PORT_COUNTER: AtomicU16 = AtomicU16::new(2775); - #[derive(Debug, Clone, Copy)] pub enum SecretType { Basic, @@ -81,8 +78,7 @@ pub struct AgentProcess { impl AgentProcess { pub async fn start() -> AgentProcess { - let port = PORT_COUNTER.fetch_add(1, Ordering::SeqCst); - Self::start_with_config(port, 5).await + Self::start_with_config(2775, 5).await } pub async fn start_with_config(port: u16, ttl_seconds: u16) -> AgentProcess { @@ -196,20 +192,12 @@ impl TestSecrets { } pub async fn setup() -> Self { - use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; - - let mut hasher = DefaultHasher::new(); - std::thread::current().id().hash(&mut hasher); - std::time::SystemTime::now() + let timestamp = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() - .as_nanos() - .hash(&mut hasher); - fastrand::u64(..).hash(&mut hasher); - let hash = hasher.finish(); + .as_nanos(); - let test_prefix = format!("aws-sm-agent-test-{}", hash); + let test_prefix = format!("aws-sm-agent-test-{}", timestamp); let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await; let client = aws_sdk_secretsmanager::Client::new(&config); From 41dc96b6a9fff38a173e23389259f7cc20f23ff0 Mon Sep 17 00:00:00 2001 From: Simon Marty Date: Thu, 16 Oct 2025 16:08:53 -0700 Subject: [PATCH 5/6] Remove the cache Signed-off-by: Simon Marty --- .github/workflows/integration-tests.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index ec801b1..96594af 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -38,15 +38,6 @@ jobs: toolchain: stable override: true - - name: Cache cargo registry - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }} - - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v5 with: From c7e7088a3882ab71b571bb3ef876dbfaade4bc99 Mon Sep 17 00:00:00 2001 From: Reyhan Koyun Date: Fri, 17 Oct 2025 10:19:08 -0700 Subject: [PATCH 6/6] Add reopened and ready_for_review triggers to integration tests workflow - Add reopened trigger (part of default set) - Add ready_for_review trigger for draft PRs marked ready - Add Checkout step name for consistency --- .github/workflows/integration-tests.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 96594af..1609da3 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -5,7 +5,7 @@ on: push: branches: ["main"] pull_request_target: - types: [opened, synchronize, labeled] + types: [opened, synchronize, reopened, ready_for_review, labeled] env: CARGO_TERM_COLOR: always @@ -28,7 +28,8 @@ jobs: contents: read steps: - - uses: actions/checkout@v5 + - name: Checkout + uses: actions/checkout@v5 with: ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.sha }}