diff --git a/.github/workflows/docker-rds-images.yml b/.github/workflows/docker-rds-images.yml index 7594235e..1ce58d58 100644 --- a/.github/workflows/docker-rds-images.yml +++ b/.github/workflows/docker-rds-images.yml @@ -243,7 +243,7 @@ jobs: type=raw,value=${{ matrix.target.version }}-dev-${{ steps.sha.outputs.short }},enable=${{ github.event_name == 'workflow_dispatch' }} - name: Trivy vulnerability scan - uses: aquasecurity/trivy-action@6c175e9c4083a92bbca2f9724c8a5e33bc2d97a5 # 0.30.0 + uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0 with: image-ref: ${{ env.IMAGE_BASE }}:${{ steps.meta.outputs.version }} format: table diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a4ff79cd..82543f73 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -157,7 +157,7 @@ jobs: type=raw,value=latest,enable={{is_default_branch}} - name: Trivy vulnerability scan - uses: aquasecurity/trivy-action@6c175e9c4083a92bbca2f9724c8a5e33bc2d97a5 # 0.30.0 + uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0 with: image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} format: table diff --git a/conformance-baseline.json b/conformance-baseline.json index 188a0cf6..efd17634 100644 --- a/conformance-baseline.json +++ b/conformance-baseline.json @@ -1,138 +1,138 @@ { - "variants_passed": 80074, + "variants_passed": 80073, "total_variants": 81489, "per_service": { - "cognito-idp": { - "passed": 4479, - "total": 4479 + "cloudformation": { + "passed": 3420, + "total": 3420 }, - "wafv2": { - "passed": 2141, - "total": 2141 + "athena": { + "passed": 2320, + "total": 2320 + }, + "apigatewayv2": { + "passed": 2769, + "total": 2769 }, "route53": { "passed": 2388, "total": 2388 }, + "ses": { + "passed": 2858, + "total": 2858 + }, "iam": { "passed": 5962, "total": 5962 }, - "elasticache": { - "passed": 2219, - "total": 2219 + "scheduler": { + "passed": 460, + "total": 491 }, - "logs": { - "passed": 3911, - "total": 3911 + "ecs": { + "passed": 1733, + "total": 2327 }, - "states": { - "passed": 1256, - "total": 1292 + "elasticloadbalancing": { + "passed": 1560, + "total": 1560 + }, + "cloudfront": { + "passed": 3576, + "total": 4103 + }, + "sqs": { + "passed": 549, + "total": 549 }, "rds": { "passed": 5376, "total": 5376 }, - "bedrock-runtime": { - "passed": 412, - "total": 412 - }, - "kms": { - "passed": 2196, - "total": 2196 - }, - "kinesis": { - "passed": 1611, - "total": 1611 + "states": { + "passed": 1256, + "total": 1292 }, - "cloudfront": { - "passed": 3576, - "total": 4103 + "sts": { + "passed": 438, + "total": 438 }, "sns": { "passed": 1027, "total": 1027 }, - "apigatewayv1": { - "passed": 3135, - "total": 3362 - }, - "sts": { - "passed": 438, - "total": 438 + "dynamodb": { + "passed": 2123, + "total": 2123 }, - "scheduler": { - "passed": 460, - "total": 491 + "bedrock": { + "passed": 3591, + "total": 3591 }, - "s3": { - "passed": 3620, - "total": 3620 + "logs": { + "passed": 3911, + "total": 3911 }, - "ecr": { - "passed": 1789, - "total": 1789 + "apigatewayv1": { + "passed": 3134, + "total": 3362 }, "secretsmanager": { "passed": 852, "total": 852 }, - "bedrock": { - "passed": 3591, - "total": 3591 + "kms": { + "passed": 2196, + "total": 2196 }, - "athena": { - "passed": 2320, - "total": 2320 + "wafv2": { + "passed": 2141, + "total": 2141 }, - "ssm": { - "passed": 5303, - "total": 5303 + "cognito-idp": { + "passed": 4479, + "total": 4479 }, "lambda": { "passed": 3347, "total": 3347 }, - "ecs": { - "passed": 1733, - "total": 2327 - }, - "cloudformation": { - "passed": 3420, - "total": 3420 + "s3": { + "passed": 3620, + "total": 3620 }, - "ses": { - "passed": 2858, - "total": 2858 + "bedrock-runtime": { + "passed": 412, + "total": 412 }, "events": { "passed": 2108, "total": 2108 }, - "apigatewayv2": { - "passed": 2769, - "total": 2769 - }, - "dynamodb": { - "passed": 2123, - "total": 2123 - }, "application-autoscaling": { "passed": 944, "total": 944 }, + "ecr": { + "passed": 1789, + "total": 1789 + }, + "elasticache": { + "passed": 2219, + "total": 2219 + }, + "ssm": { + "passed": 5303, + "total": 5303 + }, "acm": { "passed": 601, "total": 601 }, - "elasticloadbalancing": { - "passed": 1560, - "total": 1560 - }, - "sqs": { - "passed": 549, - "total": 549 + "kinesis": { + "passed": 1611, + "total": 1611 } } } diff --git a/crates/fakecloud-conformance/tests/rds.rs b/crates/fakecloud-conformance/tests/rds.rs index 994db753..9b80b0ed 100644 --- a/crates/fakecloud-conformance/tests/rds.rs +++ b/crates/fakecloud-conformance/tests/rds.rs @@ -91,6 +91,8 @@ async fn rds_describe_db_instances() { .await .unwrap(); + wait_for_db_available(&client, "conf-rds-db").await; + let response = client .describe_db_instances() .db_instance_identifier("conf-rds-db") @@ -540,7 +542,7 @@ async fn create_instance_with_deletion_protection( db_instance_identifier: &str, deletion_protection: bool, ) -> aws_sdk_rds::operation::create_db_instance::CreateDbInstanceOutput { - client + let response = client .create_db_instance() .db_instance_identifier(db_instance_identifier) .allocated_storage(20) @@ -553,7 +555,39 @@ async fn create_instance_with_deletion_protection( .db_name("appdb") .send() .await - .unwrap() + .unwrap(); + + // Async CreateDBInstance returns a `creating` placeholder; the + // background container start has to finish before any caller can + // exercise snapshot / replica / dump paths without hitting + // "Docker/Podman is required for RDS DB instances but is not + // available". Poll until the instance flips to `available`. + wait_for_db_available(client, db_instance_identifier).await; + + response +} + +async fn wait_for_db_available(client: &aws_sdk_rds::Client, db_instance_identifier: &str) { + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(240); + while std::time::Instant::now() < deadline { + let response = client + .describe_db_instances() + .db_instance_identifier(db_instance_identifier) + .send() + .await + .unwrap(); + if let Some(status) = response + .db_instances() + .first() + .and_then(|i| i.db_instance_status()) + { + if status == "available" { + return; + } + } + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + panic!("DB instance {db_instance_identifier} did not reach `available` within 240s"); } #[test_action("rds", "CreateDBSubnetGroup", checksum = "1b1b06a3")] @@ -873,6 +907,8 @@ async fn rds_delete_db_instance_with_final_snapshot() { .await .unwrap(); + wait_for_db_available(&client, "conf-rds-final").await; + // Delete with final snapshot let response = client .delete_db_instance() @@ -982,6 +1018,8 @@ async fn rds_describe_db_snapshots_pagination() { .await .unwrap(); + wait_for_db_available(&client, "conf-snap-paginate").await; + // Create 3 snapshots for i in 1..=3 { client diff --git a/crates/fakecloud-e2e/tests/rds_mysql_lambda.rs b/crates/fakecloud-e2e/tests/rds_mysql_lambda.rs index 466aae08..f458ea8b 100644 --- a/crates/fakecloud-e2e/tests/rds_mysql_lambda.rs +++ b/crates/fakecloud-e2e/tests/rds_mysql_lambda.rs @@ -4,6 +4,14 @@ //! images. Each test creates a Lambda, spins up the engine container, //! and exercises both the sync and async invocation paths through the //! libcurl-backed UDF + bridge endpoint round trip. +//! +//! Gated behind `FAKECLOUD_E2E_HEAVY_DBS=1` (same pattern as +//! `rds_heavy_engines.rs`): on a fresh PR build the prebuilt +//! `fakecloud-mysql` / `fakecloud-mariadb` images are not yet on +//! ghcr.io for the in-flight version, so the runtime falls back to a +//! local `docker build` that pushes the per-job E2E budget over +//! 30 minutes. CI lanes that bake the heavy images opt in via the +//! variable; the regular E2E lane skips. mod helpers; @@ -13,6 +21,12 @@ use aws_sdk_lambda::primitives::Blob; use helpers::TestServer; use mysql_async::prelude::*; +fn heavy_dbs_opted_in() -> bool { + std::env::var("FAKECLOUD_E2E_HEAVY_DBS") + .map(|v| v == "1") + .unwrap_or(false) +} + fn make_echo_zip() -> Vec { let buf = Vec::new(); let cursor = std::io::Cursor::new(buf); @@ -93,10 +107,20 @@ async fn run_lambda_round_trip(engine: &str, engine_version: &str, db_id: &str) #[tokio::test] async fn aws_lambda_bridge_mysql_round_trip() { + if !heavy_dbs_opted_in() { + eprintln!( + "skipping aws_lambda_bridge_mysql_round_trip — set FAKECLOUD_E2E_HEAVY_DBS=1 to enable" + ); + return; + } run_lambda_round_trip("mysql", "8.0", "mysql-lambda-db").await; } #[tokio::test] async fn aws_lambda_bridge_mariadb_round_trip() { + if !heavy_dbs_opted_in() { + eprintln!("skipping aws_lambda_bridge_mariadb_round_trip — set FAKECLOUD_E2E_HEAVY_DBS=1 to enable"); + return; + } run_lambda_round_trip("mariadb", "10.11", "mariadb-lambda-db").await; } diff --git a/crates/fakecloud-rds/src/service.rs b/crates/fakecloud-rds/src/service.rs index 0c417779..7f1894c5 100644 --- a/crates/fakecloud-rds/src/service.rs +++ b/crates/fakecloud-rds/src/service.rs @@ -2477,10 +2477,13 @@ fn validate_create_request( // dev-edition images (gvenzl/oracle-free 23, mssql-server 2022, // db2_community 11.5). Adding a new version here also requires // wiring the image tag in `RdsRuntime::ensure_postgres`. + // Major versions ("8.0", "10.11", ...) are accepted alongside the + // full `..` triplets — AWS RDS validates both + // forms and the runtime resolves the matching prebuilt image regardless. let supported_versions = match engine { - "postgres" => vec!["16.3", "15.5", "14.10", "13.13"], - "mysql" => vec!["8.0.35", "8.0.28", "5.7.44"], - "mariadb" => vec!["10.11.6", "10.6.16"], + "postgres" => vec!["16", "15", "14", "13", "16.3", "15.5", "14.10", "13.13"], + "mysql" => vec!["8.0", "8.0.35", "8.0.28", "5.7.44"], + "mariadb" => vec!["10.6", "10.11", "11.4", "11.4.5", "10.11.6", "10.6.16"], "oracle-ee" | "oracle-se2" | "oracle-ee-cdb" | "oracle-se2-cdb" => { vec!["23.0.0", "21.0.0", "19.0.0"] } @@ -3178,7 +3181,9 @@ fn default_parameter_group(engine: &str, engine_version: &str) -> String { format!("default.mysql{}", major) } "mariadb" => { - let major = if engine_version.starts_with("10.11") { + let major = if engine_version.starts_with("11.4") { + "11.4" + } else if engine_version.starts_with("10.11") { "10.11" } else { "10.6" diff --git a/crates/fakecloud-rds/src/state.rs b/crates/fakecloud-rds/src/state.rs index 933b0973..55882c90 100644 --- a/crates/fakecloud-rds/src/state.rs +++ b/crates/fakecloud-rds/src/state.rs @@ -391,6 +391,14 @@ pub fn default_engine_versions() -> Vec { status: "available".to_string(), }, // MariaDB versions + EngineVersionInfo { + engine: "mariadb".to_string(), + engine_version: "11.4.5".to_string(), + db_parameter_group_family: "mariadb11.4".to_string(), + db_engine_description: "MariaDB Community Edition".to_string(), + db_engine_version_description: "MariaDB 11.4.5".to_string(), + status: "available".to_string(), + }, EngineVersionInfo { engine: "mariadb".to_string(), engine_version: "10.11.6".to_string(), @@ -420,6 +428,7 @@ pub fn default_orderable_options() -> Vec { ("mysql", "8.0.35", "general-public-license"), ("mysql", "8.0.28", "general-public-license"), ("mysql", "5.7.44", "general-public-license"), + ("mariadb", "11.4.5", "general-public-license"), ("mariadb", "10.11.6", "general-public-license"), ("mariadb", "10.6.16", "general-public-license"), ]; @@ -454,6 +463,7 @@ pub fn default_parameter_groups( ("postgres13", "Default parameter group for postgres13"), ("mysql8.0", "Default parameter group for mysql8.0"), ("mysql5.7", "Default parameter group for mysql5.7"), + ("mariadb11.4", "Default parameter group for mariadb11.4"), ("mariadb10.11", "Default parameter group for mariadb10.11"), ("mariadb10.6", "Default parameter group for mariadb10.6"), // Heavy-engine families. The names match what @@ -611,8 +621,8 @@ mod tests { fn default_engine_versions_are_postgres_metadata() { let versions = default_engine_versions(); - assert_eq!(versions.len(), 9); // 4 postgres + 3 mysql + 2 mariadb - // Check first postgres version + assert_eq!(versions.len(), 10); // 4 postgres + 3 mysql + 3 mariadb + // Check first postgres version assert_eq!(versions[0].engine, "postgres"); assert_eq!(versions[0].engine_version, "16.3"); assert_eq!(versions[0].db_parameter_group_family, "postgres16"); @@ -623,7 +633,7 @@ mod tests { let versions = default_engine_versions(); let options = default_orderable_options(); - assert_eq!(options.len(), 63); // 9 versions * 7 instance classes + assert_eq!(options.len(), 70); // 10 versions * 7 instance classes // Verify all engines and versions have orderable options for version in &versions { assert!(options.iter().any(|opt| {