From 79629c0355c199c750ddba9f265d01a51a05f11b Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Wed, 10 Jun 2026 17:51:56 -0700 Subject: [PATCH 1/8] fix(test): align config itest with MinSavingsUSD field rename PR #1206 split RecommendationFilter.MinSavings into MinSavingsUSD and MinSavingsPct but only updated the unit test file. The integration-tagged store_postgres_recommendations_test.go still referenced the removed MinSavings field, breaking golangci-lint typecheck on main. --- internal/config/store_postgres_recommendations_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/config/store_postgres_recommendations_test.go b/internal/config/store_postgres_recommendations_test.go index 626f52c61..eb0c57f6e 100644 --- a/internal/config/store_postgres_recommendations_test.go +++ b/internal/config/store_postgres_recommendations_test.go @@ -173,8 +173,8 @@ func TestPostgresStore_ListStoredRecommendations_FilterPushdown(t *testing.T) { require.NoError(t, err) assert.Len(t, got, 1) - // Filter by min savings. - got, err = store.ListStoredRecommendations(ctx, config.RecommendationFilter{MinSavings: 25}) + // Filter by min savings (dollar floor, pushed down to SQL). + got, err = store.ListStoredRecommendations(ctx, config.RecommendationFilter{MinSavingsUSD: 25}) require.NoError(t, err) assert.Len(t, got, 1) assert.Equal(t, "rds", got[0].Service) From de238781a777c13195477608ae08db0e8a7864f9 Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Wed, 10 Jun 2026 17:52:03 -0700 Subject: [PATCH 2/8] fix(test): restore compilation of server integration tests internal/server/integration_test.go referenced scheduler.CollectResult without importing the scheduler package, failing the integration build on main. Fixing the import surfaced an unused ctx variable in TestApplicationLifecycle, which is also removed. --- internal/server/integration_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/server/integration_test.go b/internal/server/integration_test.go index 22afe878e..47e2eabab 100644 --- a/internal/server/integration_test.go +++ b/internal/server/integration_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/LeanerCloud/CUDly/internal/scheduler" "github.com/LeanerCloud/CUDly/internal/testutil" ) @@ -107,8 +108,6 @@ func TestApplicationLifecycle(t *testing.T) { t.Skip("Skipping integration test in short mode") } - ctx := testutil.TestContext(t) - // Set up test environment testutil.SetEnv(t, "VERSION", "integration-test") testutil.SetEnv(t, "CONFIG_TABLE", "test-config") From 1a17089ac03ee4a7d9907aa2aa88e69046b35240 Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Wed, 10 Jun 2026 17:52:04 -0700 Subject: [PATCH 3/8] fix(migrations): plain-column matview unique indexes for concurrent refresh Migration 000074 recreated the unique indexes of monthly_savings_summary, daily_savings_trend and provider_savings_summary as COALESCE expression indexes. REFRESH MATERIALIZED VIEW CONCURRENTLY requires a unique index using only column names, so refresh_savings_materialized_views() failed with SQLSTATE 55000 on every call, in production and in the analytics integration tests. Migration 000076 recreates the three indexes on plain columns with NULLS NOT DISTINCT (PostgreSQL 15+; all environments run 16.x), preserving the dedup intent of the COALESCE for NULL cloud_account_id while satisfying the CONCURRENTLY prerequisite. --- ...view_unique_indexes_plain_columns.down.sql | 23 ++++++++++++++ ...atview_unique_indexes_plain_columns.up.sql | 31 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 internal/database/postgres/migrations/000076_matview_unique_indexes_plain_columns.down.sql create mode 100644 internal/database/postgres/migrations/000076_matview_unique_indexes_plain_columns.up.sql diff --git a/internal/database/postgres/migrations/000076_matview_unique_indexes_plain_columns.down.sql b/internal/database/postgres/migrations/000076_matview_unique_indexes_plain_columns.down.sql new file mode 100644 index 000000000..da7114a88 --- /dev/null +++ b/internal/database/postgres/migrations/000076_matview_unique_indexes_plain_columns.down.sql @@ -0,0 +1,23 @@ +-- 000076 down: restore the COALESCE expression unique indexes from 000074. +-- Note: with these expression indexes in place REFRESH MATERIALIZED VIEW +-- CONCURRENTLY fails (SQLSTATE 55000); this only reverts to the prior state. + +DROP INDEX IF EXISTS idx_monthly_savings_summary_unique; +CREATE UNIQUE INDEX idx_monthly_savings_summary_unique + ON monthly_savings_summary( + month, account_id, + COALESCE(cloud_account_id, '00000000-0000-0000-0000-000000000000'::uuid), + provider, service); + +DROP INDEX IF EXISTS idx_daily_savings_trend_unique; +CREATE UNIQUE INDEX idx_daily_savings_trend_unique + ON daily_savings_trend( + day, account_id, + COALESCE(cloud_account_id, '00000000-0000-0000-0000-000000000000'::uuid), + provider); + +DROP INDEX IF EXISTS idx_provider_savings_summary_unique; +CREATE UNIQUE INDEX idx_provider_savings_summary_unique + ON provider_savings_summary( + provider, account_id, + COALESCE(cloud_account_id, '00000000-0000-0000-0000-000000000000'::uuid)); diff --git a/internal/database/postgres/migrations/000076_matview_unique_indexes_plain_columns.up.sql b/internal/database/postgres/migrations/000076_matview_unique_indexes_plain_columns.up.sql new file mode 100644 index 000000000..63a7e5f6e --- /dev/null +++ b/internal/database/postgres/migrations/000076_matview_unique_indexes_plain_columns.up.sql @@ -0,0 +1,31 @@ +-- 000076: recreate the materialized-view unique indexes on plain columns so +-- REFRESH MATERIALIZED VIEW CONCURRENTLY works again. +-- +-- Migration 000074 recreated idx_monthly_savings_summary_unique, +-- idx_daily_savings_trend_unique and idx_provider_savings_summary_unique as +-- COALESCE(cloud_account_id, ...) expression indexes. PostgreSQL requires the +-- unique index backing REFRESH ... CONCURRENTLY to use only column names (no +-- expressions, no WHERE clause), so refresh_savings_materialized_views() has +-- failed with SQLSTATE 55000 ("cannot refresh materialized view ... +-- concurrently") ever since. +-- +-- The COALESCE existed to collapse NULL cloud_account_id values into a single +-- key; NULLS NOT DISTINCT (PostgreSQL 15+) preserves that dedup intent while +-- satisfying the CONCURRENTLY prerequisite. Row uniqueness at this grain is +-- already guaranteed by each view's GROUP BY, so the index swap does not +-- change view contents. + +DROP INDEX IF EXISTS idx_monthly_savings_summary_unique; +CREATE UNIQUE INDEX idx_monthly_savings_summary_unique + ON monthly_savings_summary (month, account_id, cloud_account_id, provider, service) + NULLS NOT DISTINCT; + +DROP INDEX IF EXISTS idx_daily_savings_trend_unique; +CREATE UNIQUE INDEX idx_daily_savings_trend_unique + ON daily_savings_trend (day, account_id, cloud_account_id, provider) + NULLS NOT DISTINCT; + +DROP INDEX IF EXISTS idx_provider_savings_summary_unique; +CREATE UNIQUE INDEX idx_provider_savings_summary_unique + ON provider_savings_summary (provider, account_id, cloud_account_id) + NULLS NOT DISTINCT; From 7e30c597c59a4afc95d9cd696a4e65e048e0469e Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Wed, 10 Jun 2026 17:52:11 -0700 Subject: [PATCH 4/8] fix(test): expect AVG run-rate aggregation in analytics breakdowns QueryByProvider deliberately averages run-rate snapshots over time (migration 000074 / PR #1127); the tests still asserted the pre-000074 SUM semantics (300 = 200 + 100) and passed the *float64 AvgCoverage pointer straight into InDelta, which fails with "Parameters must be numerical". Expect AVG(200, 100) = 150 and dereference the pointer after a nil check. --- internal/analytics/postgres_analytics_db_test.go | 7 +++++-- internal/analytics/postgres_analytics_integration_test.go | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/internal/analytics/postgres_analytics_db_test.go b/internal/analytics/postgres_analytics_db_test.go index 3ce6aba8d..c76da6ca1 100644 --- a/internal/analytics/postgres_analytics_db_test.go +++ b/internal/analytics/postgres_analytics_db_test.go @@ -335,8 +335,11 @@ func TestPostgresAnalyticsStore_QueryByProvider_DB(t *testing.T) { for _, b := range breakdowns { if b.Provider == "aws" && b.Service == "rds" { found = true - assert.InDelta(t, 300.00, b.TotalSavings, 0.01) // 200 + 100 - assert.InDelta(t, 77.5, b.AvgCoverage, 0.01) // (80 + 75) / 2 + // Snapshots are run-rates: aggregation over time uses AVG, + // not SUM (see migration 000074 / PR #1127). + assert.InDelta(t, 150.00, b.TotalSavings, 0.01) // AVG(200, 100) + require.NotNil(t, b.AvgCoverage) + assert.InDelta(t, 77.5, *b.AvgCoverage, 0.01) // (80 + 75) / 2 } } assert.True(t, found, "aws/rds breakdown not found") diff --git a/internal/analytics/postgres_analytics_integration_test.go b/internal/analytics/postgres_analytics_integration_test.go index 82b5aa0ca..865f6e241 100644 --- a/internal/analytics/postgres_analytics_integration_test.go +++ b/internal/analytics/postgres_analytics_integration_test.go @@ -343,8 +343,11 @@ func TestPostgresAnalyticsStore_QueryByProvider(t *testing.T) { for _, b := range breakdowns { if b.Provider == "aws" && b.Service == "rds" { found = true - assert.InDelta(t, 300.00, b.TotalSavings, 0.01) // 200 + 100 - assert.InDelta(t, 77.5, b.AvgCoverage, 0.01) // (80 + 75) / 2 + // Snapshots are run-rates: aggregation over time uses AVG, + // not SUM (see migration 000074 / PR #1127). + assert.InDelta(t, 150.00, b.TotalSavings, 0.01) // AVG(200, 100) + require.NotNil(t, b.AvgCoverage) + assert.InDelta(t, 77.5, *b.AvgCoverage, 0.01) // (80 + 75) / 2 } } assert.True(t, found, "aws/rds breakdown not found") From d4032929eb6ec40ba6630cc9f191f6adb0a478bd Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Wed, 10 Jun 2026 17:52:11 -0700 Subject: [PATCH 5/8] fix(test): pin migration tests by version, not steps from head Most migration regression tests pinned the schema with "run to head, then roll back N steps", which silently drifts every time a newer migration lands: rolling back one step undoes whatever migration is newest instead of the one under test. All of these tests have been failing on main since the suite moved past their anchors. Add migrations.MigrateToVersion (wraps golang-migrate's Migrate) and convert the tests to pin the exact version below the migration under test, cycling that migration's own up/down before running the full chain to head. Also repaired while converting: - 000053: confdeltype is the "char" type (OID 18), which pgx cannot scan into *string in binary format; cast to text. The negative-space guard expected the recommendations FK to be SET NULL, but it has been ON DELETE CASCADE since its creation in 000030. - 000057/backfill: later group migrations (000064 Purchaser backfill) legitimately add groups to admins, so assert exact mappings right after 000057 / 000056 and only invariants at head. - 000060: purchase_history.id is a UUID; the seeded value used non-hex characters and could never insert. - savings_snapshots_pk: golang-migrate keeps a single schema_migrations row, so counting rows above version 26 always returned 1 and the rollback loop never reached 000026. --- ...053_executions_account_fk_restrict_test.go | 46 +++++++++------- .../000057_drop_user_role_to_groups_test.go | 19 ++++--- .../000060_cleanup_universal_plans_test.go | 27 +++++----- .../000064_relocate_purchaser_group_test.go | 9 ++-- .../backfill_admin_group_ids_test.go | 35 +++++++----- .../database/postgres/migrations/migrate.go | 37 +++++++++++++ .../migrations/savings_snapshots_pk_test.go | 30 +++-------- .../migrations/split_savingsplans_test.go | 54 +++++++++++++------ 8 files changed, 161 insertions(+), 96 deletions(-) diff --git a/internal/database/postgres/migrations/000053_executions_account_fk_restrict_test.go b/internal/database/postgres/migrations/000053_executions_account_fk_restrict_test.go index c51875de4..6a696129c 100644 --- a/internal/database/postgres/migrations/000053_executions_account_fk_restrict_test.go +++ b/internal/database/postgres/migrations/000053_executions_account_fk_restrict_test.go @@ -26,12 +26,15 @@ func TestMigration_ExecutionsAccountFKRestrict(t *testing.T) { defer container.Cleanup(ctx) pool := container.DB.Pool() - require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) + // Pin the schema at 000053 so the assertions exercise this migration's + // direct effects; later migrations change unrelated parts of the same + // tables (e.g. purchase_executions.execution_id becomes a UUID). + require.NoError(t, migrations.MigrateToVersion(ctx, pool, migrationsPath, 53)) // Verify the FK is RESTRICT after the migration. var deleteAction string err = pool.QueryRow(ctx, ` - SELECT confdeltype + SELECT confdeltype::text FROM pg_constraint WHERE conname = 'purchase_executions_cloud_account_id_fkey' `).Scan(&deleteAction) @@ -40,18 +43,17 @@ func TestMigration_ExecutionsAccountFKRestrict(t *testing.T) { "expected confdeltype 'r' (RESTRICT) after 000053, got %q", deleteAction) // Negative-space guard: migration 000053 must only tighten the executions - // FK. The recommendations FK should stay ON DELETE SET NULL — orphaning a - // historical recommendation is fine (it's advisory data), unlike a pending - // execution (which represents money about to be spent). + // FK. The recommendations FK has been ON DELETE CASCADE since its creation + // in 000030 and must be left untouched. var recsDeleteAction string err = pool.QueryRow(ctx, ` - SELECT confdeltype + SELECT confdeltype::text FROM pg_constraint WHERE conname = 'recommendations_cloud_account_id_fkey' `).Scan(&recsDeleteAction) require.NoError(t, err) - assert.Equal(t, "n", recsDeleteAction, - "recommendations FK must stay SET NULL after 000053, got %q", recsDeleteAction) + assert.Equal(t, "c", recsDeleteAction, + "recommendations FK must stay CASCADE after 000053, got %q", recsDeleteAction) // Behavioural test: insert an account + a pending execution that // references it, then attempt to delete the account. Postgres must @@ -66,7 +68,7 @@ func TestMigration_ExecutionsAccountFKRestrict(t *testing.T) { INSERT INTO purchase_executions (execution_id, status, step_number, scheduled_date, cloud_account_id) VALUES - ('exec-fk-test', 'pending', 1, NOW(), 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa') + ('eeeeeeee-eeee-eeee-eeee-eeeeeeee0001', 'pending', 1, NOW(), 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa') `) require.NoError(t, err) @@ -77,16 +79,17 @@ func TestMigration_ExecutionsAccountFKRestrict(t *testing.T) { strings.Contains(err.Error(), "23503") || strings.Contains(err.Error(), "foreign key"), "expected FK violation, got %v", err) - // Cancel the execution, then the delete should succeed. + // RESTRICT applies to every referencing row regardless of status, so the + // account delete only succeeds once the execution row itself is removed + // (the API layer's Cancel-All-And-Delete flow does exactly that). _, err = pool.Exec(ctx, ` - UPDATE purchase_executions - SET status = 'cancelled' - WHERE execution_id = 'exec-fk-test' + DELETE FROM purchase_executions + WHERE execution_id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeee0001' `) require.NoError(t, err) _, err = pool.Exec(ctx, `DELETE FROM cloud_accounts WHERE id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'`) - require.NoError(t, err, "DELETE should succeed once pending executions are cancelled") + require.NoError(t, err, "DELETE should succeed once referencing executions are removed") } // TestMigration_ExecutionsAccountFKRestrict_Rollback asserts that the @@ -102,12 +105,15 @@ func TestMigration_ExecutionsAccountFKRestrict_Rollback(t *testing.T) { defer container.Cleanup(ctx) pool := container.DB.Pool() - require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) + // Pin the schema at 000053, then roll back exactly that migration. A + // fixed step count from head would exercise whichever migration happens + // to be newest instead of 000053's down. + require.NoError(t, migrations.MigrateToVersion(ctx, pool, migrationsPath, 53)) require.NoError(t, migrations.RollbackMigrations(ctx, pool, migrationsPath, 1)) var deleteAction string err = pool.QueryRow(ctx, ` - SELECT confdeltype + SELECT confdeltype::text FROM pg_constraint WHERE conname = 'purchase_executions_cloud_account_id_fkey' `).Scan(&deleteAction) @@ -116,14 +122,14 @@ func TestMigration_ExecutionsAccountFKRestrict_Rollback(t *testing.T) { "expected confdeltype 'n' (SET NULL) after rollback, got %q", deleteAction) // Negative-space guard: rolling back 000053 must not touch the - // recommendations FK — it has always been SET NULL and should stay so. + // recommendations FK — it has been CASCADE since 000030 and should stay so. var recsDeleteAction string err = pool.QueryRow(ctx, ` - SELECT confdeltype + SELECT confdeltype::text FROM pg_constraint WHERE conname = 'recommendations_cloud_account_id_fkey' `).Scan(&recsDeleteAction) require.NoError(t, err) - assert.Equal(t, "n", recsDeleteAction, - "recommendations FK must stay SET NULL after rollback, got %q", recsDeleteAction) + assert.Equal(t, "c", recsDeleteAction, + "recommendations FK must stay CASCADE after rollback, got %q", recsDeleteAction) } diff --git a/internal/database/postgres/migrations/000057_drop_user_role_to_groups_test.go b/internal/database/postgres/migrations/000057_drop_user_role_to_groups_test.go index 44af19ef6..99d31da32 100644 --- a/internal/database/postgres/migrations/000057_drop_user_role_to_groups_test.go +++ b/internal/database/postgres/migrations/000057_drop_user_role_to_groups_test.go @@ -47,10 +47,10 @@ func TestMigration_DropUserRoleToGroups(t *testing.T) { defer container.Cleanup(ctx) pool := container.DB.Pool() - // Apply to head, then roll back 000057 so the DB sits at v56 where the - // `role` column still exists and we can seed role-bearing rows. - require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) - require.NoError(t, migrations.RollbackMigrations(ctx, pool, migrationsPath, 1)) + // Pin the DB at v56, where the `role` column still exists and we can + // seed role-bearing rows. (Pinning by version stays correct as newer + // migrations land; the old "head minus one step" approach did not.) + require.NoError(t, migrations.MigrateToVersion(ctx, pool, migrationsPath, 56)) // Seed one user per legacy role, each with empty group_ids (the pre-#907 // state where authorization came from the role, not groups). Also seed a @@ -74,8 +74,9 @@ func TestMigration_DropUserRoleToGroups(t *testing.T) { require.NoError(t, err) seed("unknown@test.example", "legacy-custom") - // Re-apply 000057. - require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) + // Apply exactly 000057 so the mapping assertions below are not affected + // by later group migrations (000064 adds admins to Purchaser, etc.). + require.NoError(t, migrations.MigrateToVersion(ctx, pool, migrationsPath, 57)) // Role -> group mapping must preserve access. assert.Equal(t, []string{defaultAdminGroupIDTest}, @@ -111,6 +112,9 @@ func TestMigration_DropUserRoleToGroups(t *testing.T) { ARRAY['`+readOnlyUsersGroupIDTest+`']::uuid[], NOW(), NOW()) `) require.NoError(t, err, "a user with at least one group must insert successfully") + + // The rest of the chain must still apply cleanly over the migrated data. + require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) } // assertColumnAbsent fails if the named column still exists on the table. @@ -138,7 +142,8 @@ func TestMigration_DropUserRoleToGroups_Down(t *testing.T) { defer container.Cleanup(ctx) pool := container.DB.Pool() - require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) + // Pin at v57 so the rollback below exercises exactly 000057's down. + require.NoError(t, migrations.MigrateToVersion(ctx, pool, migrationsPath, 57)) // Seed group-only users (post-#907 shape) before rolling back. _, err = pool.Exec(ctx, ` diff --git a/internal/database/postgres/migrations/000060_cleanup_universal_plans_test.go b/internal/database/postgres/migrations/000060_cleanup_universal_plans_test.go index e54f1b9ab..b807d95f9 100644 --- a/internal/database/postgres/migrations/000060_cleanup_universal_plans_test.go +++ b/internal/database/postgres/migrations/000060_cleanup_universal_plans_test.go @@ -13,7 +13,7 @@ import ( "github.com/stretchr/testify/require" ) -// TestMigration_CleanupUniversalPlans asserts that migration 000057 deletes +// TestMigration_CleanupUniversalPlans asserts that migration 000060 deletes // every purchase_plans row that has no plan_accounts entry (universal plan) // while leaving correctly scoped plans intact. // @@ -41,10 +41,10 @@ func TestMigration_CleanupUniversalPlans(t *testing.T) { defer container.Cleanup(ctx) pool := container.DB.Pool() - // Run all migrations to head (includes 000057), then roll back one so we - // sit at 000056, seed fixtures, and re-run to apply 000057 in isolation. - require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) - require.NoError(t, migrations.RollbackMigrations(ctx, pool, migrationsPath, 1)) + // Pin the DB at v59, seed fixtures, then apply 000060 in isolation. + // (Pinning by version stays correct as newer migrations land; the old + // "head minus one step" approach did not.) + require.NoError(t, migrations.MigrateToVersion(ctx, pool, migrationsPath, 59)) // Seed: two cloud accounts (needed for plan_accounts FK). _, err = pool.Exec(ctx, ` @@ -95,7 +95,7 @@ func TestMigration_CleanupUniversalPlans(t *testing.T) { (id, account_id, purchase_id, timestamp, provider, service, region, resource_type, term, payment, plan_id, plan_name) VALUES ( - 'hhhhhhhh-hhhh-hhhh-hhhh-000000000001', + 'ffffffff-ffff-ffff-ffff-000000000001', '111111111111', 'ri-abc123', NOW(), 'aws', 'rds', 'us-east-1', 'm5.large', 36, 'all-upfront', @@ -105,8 +105,8 @@ func TestMigration_CleanupUniversalPlans(t *testing.T) { `) require.NoError(t, err) - // Apply migration 000057. - require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) + // Apply migration 000060. + require.NoError(t, migrations.MigrateToVersion(ctx, pool, migrationsPath, 60)) // Universal plans must be gone. var universalCount int @@ -118,7 +118,7 @@ func TestMigration_CleanupUniversalPlans(t *testing.T) { ) `).Scan(&universalCount) require.NoError(t, err) - assert.Equal(t, 0, universalCount, "both universal plans must be deleted by migration 000057") + assert.Equal(t, 0, universalCount, "both universal plans must be deleted by migration 000060") // Scoped plan must survive. var scopedCount int @@ -127,7 +127,7 @@ func TestMigration_CleanupUniversalPlans(t *testing.T) { WHERE id = '22222222-2222-2222-2222-000000000001' `).Scan(&scopedCount) require.NoError(t, err) - assert.Equal(t, 1, scopedCount, "scoped plan (with plan_accounts) must survive migration 000057") + assert.Equal(t, 1, scopedCount, "scoped plan (with plan_accounts) must survive migration 000060") // Execution linked to deleted universal plan must have plan_id NULLed. var execPlanID *string @@ -142,15 +142,14 @@ func TestMigration_CleanupUniversalPlans(t *testing.T) { var histPlanID *string err = pool.QueryRow(ctx, ` SELECT plan_id::TEXT FROM purchase_history - WHERE id = 'hhhhhhhh-hhhh-hhhh-hhhh-000000000001' + WHERE id = 'ffffffff-ffff-ffff-ffff-000000000001' `).Scan(&histPlanID) require.NoError(t, err) assert.Nil(t, histPlanID, "history.plan_id must be NULLed when universal plan is deleted (ON DELETE SET NULL)") - // Idempotency: re-running the migration path must be a no-op. + // Idempotency: roll back 000060 (its down is a no-op by design), then + // migrate to head, which re-applies 000060 over already-cleaned data. require.NoError(t, migrations.RollbackMigrations(ctx, pool, migrationsPath, 1)) - // Re-seed only the scoped plan to simulate a clean DB at version 56. - // The universal plans are intentionally absent (already cleaned up). require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) var postIdempotentCount int diff --git a/internal/database/postgres/migrations/000064_relocate_purchaser_group_test.go b/internal/database/postgres/migrations/000064_relocate_purchaser_group_test.go index 40901e3f7..1b9c1db92 100644 --- a/internal/database/postgres/migrations/000064_relocate_purchaser_group_test.go +++ b/internal/database/postgres/migrations/000064_relocate_purchaser_group_test.go @@ -75,10 +75,11 @@ func TestMigration_RelocatePurchaserGroup(t *testing.T) { defer container.Cleanup(ctx) pool := container.DB.Pool() - // Apply all migrations, then roll back to before 000064 so we - // can seed an admin and verify the backfill fires on re-apply. - require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) - require.NoError(t, migrations.RollbackMigrations(ctx, pool, migrationsPath, 1)) + // Pin the DB just below 000064 so we can seed an admin and + // verify the backfill fires when 000064 applies. (Pinning by + // version stays correct as newer migrations land; the old + // "head minus one step" approach did not.) + require.NoError(t, migrations.MigrateToVersion(ctx, pool, migrationsPath, 63)) // Seed an admin user in the Administrators group but NOT in Purchaser. const adminEmail = "admin-purchaser-test@test.example" diff --git a/internal/database/postgres/migrations/backfill_admin_group_ids_test.go b/internal/database/postgres/migrations/backfill_admin_group_ids_test.go index 5d39fd620..01de642af 100644 --- a/internal/database/postgres/migrations/backfill_admin_group_ids_test.go +++ b/internal/database/postgres/migrations/backfill_admin_group_ids_test.go @@ -33,12 +33,11 @@ func TestMigration_BackfillAdminGroupIDs(t *testing.T) { defer container.Cleanup(ctx) pool := container.DB.Pool() - // Up to head, then roll back to version 55 so the next RunMigrations - // re-applies 000056. Two steps because 000057 (drop role -> groups) now - // sits above 000056 at head; rolling back 000057 first also restores the - // `role` column this test seeds below. - require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) - require.NoError(t, migrations.RollbackMigrations(ctx, pool, migrationsPath, 2)) + // Pin the DB at version 55 so 000056 (the SQL backfill under test) is + // not applied yet and the `role` column this test seeds below still + // exists. (Pinning by version stays correct as newer migrations land; + // the old fixed-step rollback from head did not.) + require.NoError(t, migrations.MigrateToVersion(ctx, pool, migrationsPath, 55)) // Simulate a restored/manually-seeded admin whose group_ids drifted to // empty (the bug pattern from issue #351). @@ -51,21 +50,31 @@ func TestMigration_BackfillAdminGroupIDs(t *testing.T) { drifted := queryAdminGroupIDs(t, ctx, pool, adminEmail) require.Empty(t, drifted, "test setup: admin should start with empty group_ids") - // Re-run migrations with NO admin email. m.Up() re-applies 000056; the - // Go-level assignAdminGroupAndWarn does NOT run (empty email), so any - // repair is attributable solely to the SQL migration. - require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) + // Apply exactly 000056. MigrateToVersion runs no Go-level repair + // (assignAdminGroupAndWarn), so any fix is attributable solely to the + // SQL migration. + require.NoError(t, migrations.MigrateToVersion(ctx, pool, migrationsPath, 56)) got := queryAdminGroupIDs(t, ctx, pool, adminEmail) assert.Equal(t, []string{defaultAdminGroupIDTest}, got, "migration 000056 must backfill the Administrators group onto a drifted admin row even without an admin email") - // Idempotent: rolling back the head migration (000057) and re-applying it + // Idempotent: cycling 000057 down and back up, then migrating to head, // must not duplicate or drop the Administrators group on the already // backfilled admin row. + require.NoError(t, migrations.MigrateToVersion(ctx, pool, migrationsPath, 57)) require.NoError(t, migrations.RollbackMigrations(ctx, pool, migrationsPath, 1)) require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) got = queryAdminGroupIDs(t, ctx, pool, adminEmail) - assert.Equal(t, []string{defaultAdminGroupIDTest}, got, - "re-applying the head migration must not duplicate the Administrators group entry") + // Later migrations legitimately add more groups to admins (000064 adds + // Purchaser), so assert on the Administrators entry rather than the + // exact set: present exactly once, never duplicated or dropped. + adminEntries := 0 + for _, id := range got { + if id == defaultAdminGroupIDTest { + adminEntries++ + } + } + assert.Equal(t, 1, adminEntries, + "re-applying the migration chain must keep exactly one Administrators group entry, got %v", got) } diff --git a/internal/database/postgres/migrations/migrate.go b/internal/database/postgres/migrations/migrate.go index c1276bde6..ea2cf3728 100644 --- a/internal/database/postgres/migrations/migrate.go +++ b/internal/database/postgres/migrations/migrate.go @@ -455,6 +455,43 @@ func RollbackMigrations(ctx context.Context, pool *pgxpool.Pool, migrationsPath return nil } +// MigrateToVersion migrates the schema up or down to exactly the given +// version. Unlike RunMigrations it applies no post-migration Go logic +// (admin seeding etc.), and unlike RollbackMigrations it targets a version +// rather than a step count. Migration tests use it to pin the database at +// the version just below the migration under test; fixed step counts from +// head silently drift every time a newer migration lands. +func MigrateToVersion(ctx context.Context, pool *pgxpool.Pool, migrationsPath string, version uint) error { + dsn := buildMigrateDSN(pool.Config(), "") + + m, err := migrate.New( + fmt.Sprintf("file://%s", migrationsPath), + dsn, + ) + if err != nil { + return fmt.Errorf("failed to create migrator: %w", err) + } + defer m.Close() + + if err := m.Migrate(version); err != nil && err != migrate.ErrNoChange { + return fmt.Errorf("failed to migrate to version %d: %w", version, err) + } + + current, dirty, err := m.Version() + if err != nil { + return fmt.Errorf("failed to get migration version: %w", err) + } + if dirty { + return fmt.Errorf("database is in dirty state at version %d", current) + } + if current != version { + return fmt.Errorf("expected migration version %d, got %d", version, current) + } + + log.Printf("Migrated to version %d", current) + return nil +} + // GetMigrationVersion returns the current migration version func GetMigrationVersion(ctx context.Context, pool *pgxpool.Pool, migrationsPath string) (uint, bool, error) { dsn := buildMigrateDSN(pool.Config(), "") diff --git a/internal/database/postgres/migrations/savings_snapshots_pk_test.go b/internal/database/postgres/migrations/savings_snapshots_pk_test.go index 128c86332..046b2c97a 100644 --- a/internal/database/postgres/migrations/savings_snapshots_pk_test.go +++ b/internal/database/postgres/migrations/savings_snapshots_pk_test.go @@ -56,28 +56,14 @@ func TestMigration_SavingsSnapshotsPK(t *testing.T) { defer container.Cleanup(ctx) pool := container.DB.Pool() - require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) - - // Count how many applied migrations are above version 26. - // Using COUNT from schema_migrations is correct even when migration - // numbering has gaps: Steps(-n) steps back through n applied - // migrations, so we need the actual row count, not an arithmetic - // difference between version numbers. - var stepsToRollback int - err = pool.QueryRow(ctx, `SELECT COUNT(*) FROM schema_migrations WHERE version > 26`).Scan(&stepsToRollback) - require.NoError(t, err) - require.Greater(t, stepsToRollback, 0, "there should be migrations above 000026") - - // RollbackMigrations caps a single call at 10 steps; call in a loop. - const maxRollbackPerCall = 10 - for remaining := stepsToRollback; remaining > 0; remaining -= maxRollbackPerCall { - batch := remaining - if batch > maxRollbackPerCall { - batch = maxRollbackPerCall - } - require.NoError(t, migrations.RollbackMigrations(ctx, pool, migrationsPath, batch), - "rollback to 000026 should succeed") - } + // Pin the schema at 000027 (which re-created the PK over 000018's + // version), then roll back exactly 000027. The previous + // count-rows-in-schema_migrations approach was broken: golang-migrate + // keeps a single (version, dirty) row, so the count was always 1 and + // the loop only ever rolled back the newest migration. + require.NoError(t, migrations.MigrateToVersion(ctx, pool, migrationsPath, 27)) + require.NoError(t, migrations.RollbackMigrations(ctx, pool, migrationsPath, 1), + "rollback of 000027 should succeed") // Verify the constraint is gone (000027 was rolled back). var constraintExists bool diff --git a/internal/database/postgres/migrations/split_savingsplans_test.go b/internal/database/postgres/migrations/split_savingsplans_test.go index 81fb905bb..53d0916ca 100644 --- a/internal/database/postgres/migrations/split_savingsplans_test.go +++ b/internal/database/postgres/migrations/split_savingsplans_test.go @@ -59,13 +59,11 @@ func TestMigration_SplitSavingsPlans(t *testing.T) { defer container.Cleanup(ctx) pool := container.DB.Pool() - // Run migrations up to but not including 000040 by running all, - // then forcing the migration version back to 000039 and undoing - // 000040 — equivalent to "stop just before 000040". Simpler: run - // all migrations, then run down once to undo 000040, then - // truncate and seed. - require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) - require.NoError(t, migrations.RollbackMigrations(ctx, pool, migrationsPath, 1)) + // Pin the DB at 000039 (just below the migration under test) and + // seed before applying 000040. (Pinning by version stays correct + // as newer migrations land; the old "head minus one step" + // approach did not.) + require.NoError(t, migrations.MigrateToVersion(ctx, pool, migrationsPath, 39)) // Seed: a single umbrella row with non-default values. _, err = pool.Exec(ctx, ` @@ -73,8 +71,8 @@ func TestMigration_SplitSavingsPlans(t *testing.T) { VALUES ('aws', 'savings-plans', true, 1, 'no-upfront', 90.50, 'weekly-25pct')`) require.NoError(t, err) - // Up: run 000040. - require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) + // Up: apply exactly 000040. + require.NoError(t, migrations.MigrateToVersion(ctx, pool, migrationsPath, 40)) got := queryAWSSPRows(t, pool) expected := []string{ @@ -104,6 +102,14 @@ func TestMigration_SplitSavingsPlans(t *testing.T) { require.True(t, ok) assert.Equal(t, 1, r.term) assert.Equal(t, "no-upfront", r.payment) + + // Forward again to head: the full chain must re-apply cleanly and + // land on the four split rows. + require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) + got = queryAWSSPRows(t, pool) + require.Len(t, got, 4, "expected 4 per-plan-type rows after migrating to head") + _, hasUmbrella = got["savings-plans"] + assert.False(t, hasUmbrella, "umbrella row should stay deleted at head") }) t.Run("scenario 2: umbrella + PR #71 sagemaker row (different values)", func(t *testing.T) { @@ -112,8 +118,8 @@ func TestMigration_SplitSavingsPlans(t *testing.T) { defer container.Cleanup(ctx) pool := container.DB.Pool() - require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) - require.NoError(t, migrations.RollbackMigrations(ctx, pool, migrationsPath, 1)) + // Pin the DB at 000039, just below the migration under test. + require.NoError(t, migrations.MigrateToVersion(ctx, pool, migrationsPath, 39)) // Seed both rows with intentionally divergent term/payment so // we can verify which value wins per slot. @@ -161,13 +167,13 @@ func TestMigration_SplitSavingsPlans(t *testing.T) { defer container.Cleanup(ctx) pool := container.DB.Pool() - require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) - require.NoError(t, migrations.RollbackMigrations(ctx, pool, migrationsPath, 1)) + // Pin the DB at 000039, just below the migration under test. + require.NoError(t, migrations.MigrateToVersion(ctx, pool, migrationsPath, 39)) // Confirm no SP rows present pre-migration. require.Empty(t, queryAWSSPRows(t, pool)) - require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) + require.NoError(t, migrations.MigrateToVersion(ctx, pool, migrationsPath, 40)) // Migration is a no-op with no SP rows to seed from. assert.Empty(t, queryAWSSPRows(t, pool), "no-op when no umbrella row exists") @@ -175,6 +181,10 @@ func TestMigration_SplitSavingsPlans(t *testing.T) { // Down should also be a no-op. require.NoError(t, migrations.RollbackMigrations(ctx, pool, migrationsPath, 1)) assert.Empty(t, queryAWSSPRows(t, pool)) + + // Full chain to head stays a no-op for SP rows. + require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) + assert.Empty(t, queryAWSSPRows(t, pool)) }) } @@ -193,8 +203,8 @@ func TestMigration_SplitSavingsPlans_PurchasePlansJSONB(t *testing.T) { defer container.Cleanup(ctx) pool := container.DB.Pool() - require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) - require.NoError(t, migrations.RollbackMigrations(ctx, pool, migrationsPath, 1)) + // Pin the DB at 000039, just below the migration under test. + require.NoError(t, migrations.MigrateToVersion(ctx, pool, migrationsPath, 39)) // Seed two plans: one with the umbrella SP key, one with only RDS // (untouched control). Both have a non-SP key (aws:rds) that must @@ -207,6 +217,18 @@ func TestMigration_SplitSavingsPlans_PurchasePlansJSONB(t *testing.T) { '{"aws:rds": {"term": "1yr"}}'::jsonb)`) require.NoError(t, err) + // Scope both plans to an account: migration 000060 deletes purchase_plans + // rows that have no plan_accounts entry, and the chain to head runs it. + _, err = pool.Exec(ctx, ` + INSERT INTO cloud_accounts (id, name, provider, external_id) + VALUES ('cccccccc-cccc-cccc-cccc-000000000001', 'sp-test-acct', 'aws', '333333333333')`) + require.NoError(t, err) + _, err = pool.Exec(ctx, ` + INSERT INTO plan_accounts (plan_id, account_id) VALUES + ('11111111-1111-1111-1111-111111111111', 'cccccccc-cccc-cccc-cccc-000000000001'), + ('22222222-2222-2222-2222-222222222222', 'cccccccc-cccc-cccc-cccc-000000000001')`) + require.NoError(t, err) + require.NoError(t, migrations.RunMigrations(ctx, pool, migrationsPath, "", "")) // SP plan: services should now have four aws:savings-plans- From 7464bde82ee261f29d03c574983868b66cfcb501 Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Wed, 10 Jun 2026 17:52:17 -0700 Subject: [PATCH 6/8] chore(deps): fix govulncheck findings via x/net, x/crypto, testcontainers bumps govulncheck (v1.1.4, as pinned in CI) reported reachable vulnerabilities on main: - golang.org/x/crypto v0.49.0 (GO-2026-5013/5017/5018/5019/5020) -> v0.53.0 - golang.org/x/net v0.52.0 (GO-2026-5026, GO-2026-4918) -> v0.56.0 - github.com/docker/docker v28.5.1 (GO-2026-4883, GO-2026-4887; no fixed release under that module path) -> unreachable after upgrading testcontainers-go to v0.42.0, which switched to the moby client modules. - providers/azure and providers/gcp: x/net and protobuf bumps for the same advisories. testcontainers v0.42 changed MappedPort to return the moby network.Port value type; adapt testhelpers to port.Num(). All five modules scanned by the CI security job now report zero call-level findings with the same pinned govulncheck. --- go.mod | 45 ++++---- go.sum | 104 ++++++++---------- .../database/postgres/testhelpers/postgres.go | 2 +- providers/azure/go.mod | 14 +-- providers/azure/go.sum | 20 ++-- providers/gcp/go.mod | 12 +- providers/gcp/go.sum | 28 ++--- 7 files changed, 108 insertions(+), 117 deletions(-) diff --git a/go.mod b/go.mod index 660842604..de601d7dc 100644 --- a/go.mod +++ b/go.mod @@ -60,18 +60,18 @@ require ( github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.5.2 // indirect + github.com/stretchr/objx v0.5.3 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.opentelemetry.io/otel v1.42.0 // indirect go.opentelemetry.io/otel/metric v1.42.0 // indirect go.opentelemetry.io/otel/trace v1.42.0 // indirect - golang.org/x/crypto v0.49.0 - golang.org/x/net v0.52.0 // indirect + golang.org/x/crypto v0.53.0 + golang.org/x/net v0.56.0 // indirect golang.org/x/oauth2 v0.36.0 - golang.org/x/sync v0.20.0 - golang.org/x/sys v0.42.0 // indirect - golang.org/x/text v0.35.0 // indirect + golang.org/x/sync v0.21.0 + golang.org/x/sys v0.46.0 // indirect + golang.org/x/text v0.38.0 // indirect golang.org/x/time v0.15.0 // indirect google.golang.org/api v0.274.0 google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect @@ -111,9 +111,9 @@ require ( github.com/google/uuid v1.6.0 github.com/jackc/pgx/v5 v5.8.0 github.com/pashagolub/pgxmock/v4 v4.9.0 - github.com/testcontainers/testcontainers-go v0.40.0 - github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0 - golang.org/x/term v0.41.0 + github.com/testcontainers/testcontainers-go v0.42.0 + github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0 + golang.org/x/term v0.44.0 ) require ( @@ -128,7 +128,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cosmos/armcosmos/v2 v2.7.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/search/armsearch v1.4.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect @@ -149,36 +149,35 @@ require ( github.com/docker/docker v28.5.1+incompatible // indirect github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/ebitengine/purego v0.8.4 // indirect + github.com/ebitengine/purego v0.10.0 // indirect github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.6 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/compress v1.18.5 // indirect github.com/lib/pq v1.10.9 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.10 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/go-archive v0.1.0 // indirect - github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/go-archive v0.2.0 // indirect + github.com/moby/moby/api v1.54.1 // indirect + github.com/moby/moby/client v0.4.0 // indirect + github.com/moby/patternmatcher v0.6.1 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/user v0.4.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect - github.com/moby/term v0.5.0 // indirect - github.com/morikuni/aec v1.0.0 // indirect + github.com/moby/term v0.5.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect - github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/shirou/gopsutil/v4 v4.25.6 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/shirou/gopsutil/v4 v4.26.3 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect - github.com/tklauser/go-sysconf v0.3.12 // indirect - github.com/tklauser/numcpus v0.6.1 // indirect + github.com/tklauser/go-sysconf v0.3.16 // indirect + github.com/tklauser/numcpus v0.11.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect diff --git a/go.sum b/go.sum index 0fdc9d75f..a726f9e1a 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 h1:E4MgwLB github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0/go.mod h1:Y2b/1clN4zsAoUd/pgNAQHjLDnTis/6ROkUfyob6psM= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= @@ -187,10 +187,9 @@ github.com/coreos/go-oidc/v3 v3.18.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26 github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= @@ -205,8 +204,8 @@ github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pM github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= -github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= +github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= @@ -245,8 +244,6 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= github.com/googleapis/gax-go/v2 v2.21.0 h1:h45NjjzEO3faG9Lg/cFrBh2PgegVVgzqKzuZl/wMbiI= github.com/googleapis/gax-go/v2 v2.21.0/go.mod h1:But/NJU6TnZsrLai/xBAQLLz+Hc7fHZJt/hsCz3Fih4= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.6 h1:1ufTZkFXIQQ9EmgPjcIPIi2krfxG03lQ8OLoY1MJ3UM= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.6/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= @@ -259,8 +256,8 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= +github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -277,20 +274,22 @@ github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5L github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= -github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= -github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= -github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= -github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= -github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= +github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= +github.com/moby/moby/api v1.54.1 h1:TqVzuJkOLsgLDDwNLmYqACUuTehOHRGKiPhvH8V3Nn4= +github.com/moby/moby/api v1.54.1/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= +github.com/moby/moby/client v0.4.0 h1:S+2XegzHQrrvTCvF6s5HFzcrywWQmuVnhOXe2kiWjIw= +github.com/moby/moby/client v0.4.0/go.mod h1:QWPbvWchQbxBNdaLSpoKpCdf5E+WxFAgNHogCWDoa7g= +github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= +github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -308,17 +307,17 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= -github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/shirou/gopsutil/v4 v4.26.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc= +github.com/shirou/gopsutil/v4 v4.26.3/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -326,20 +325,20 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU= -github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY= -github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0 h1:s2bIayFXlbDFexo96y+htn7FzuhpXLYJNnIuglNKqOk= -github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0/go.mod h1:h+u/2KoREGTnTl9UwrQ/g+XhasAT8E6dClclAADeXoQ= -github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= -github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/testcontainers/testcontainers-go v0.42.0 h1:He3IhTzTZOygSXLJPMX7n44XtK+qhjat1nI9cneBbUY= +github.com/testcontainers/testcontainers-go v0.42.0/go.mod h1:vZjdY1YmUA1qEForxOIOazfsrdyORJAbhi0bp8plN30= +github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0 h1:GCbb1ndrF7OTDiIvxXyItaDab4qkzTFJ48LKFdM7EIo= +github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0/go.mod h1:IRPBaI8jXdrNfD0e4Zm7Fbcgaz5shKxOQv4axiL09xs= +github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= +github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= +github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= +github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= @@ -352,10 +351,6 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6h go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= @@ -366,29 +361,24 @@ go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9 go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= -go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= -go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= -golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= -golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= -golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= -golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -414,3 +404,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/internal/database/postgres/testhelpers/postgres.go b/internal/database/postgres/testhelpers/postgres.go index 55ba58561..190e3781f 100644 --- a/internal/database/postgres/testhelpers/postgres.go +++ b/internal/database/postgres/testhelpers/postgres.go @@ -53,7 +53,7 @@ func SetupPostgresContainer(ctx context.Context, t *testing.T) (*PostgresContain // Build database config config := &database.Config{ Host: host, - Port: port.Int(), + Port: int(port.Num()), Database: "cudly_test", User: "cudly_test", Password: "test_password", diff --git a/providers/azure/go.mod b/providers/azure/go.mod index 71fb497f5..62bd7b38b 100644 --- a/providers/azure/go.mod +++ b/providers/azure/go.mod @@ -6,6 +6,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/advisor/armadvisor v1.2.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/billingbenefits/armbillingbenefits v1.0.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.4.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/consumption/armconsumption v1.1.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cosmos/armcosmos/v2 v2.7.0 @@ -15,27 +16,26 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/search/armsearch v1.4.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql v1.2.0 github.com/LeanerCloud/CUDly/pkg v0.0.0 - github.com/google/uuid v1.6.0 github.com/stretchr/testify v1.11.1 - golang.org/x/sync v0.20.0 + golang.org/x/sync v0.21.0 ) require ( github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/billingbenefits/armbillingbenefits v1.0.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect github.com/aws/aws-sdk-go-v2 v1.40.1 // indirect github.com/aws/smithy-go v1.24.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang-jwt/jwt/v5 v5.2.2 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/objx v0.5.2 // indirect - golang.org/x/crypto v0.40.0 // indirect - golang.org/x/net v0.42.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/crypto v0.53.0 // indirect + golang.org/x/net v0.56.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/text v0.38.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/providers/azure/go.sum b/providers/azure/go.sum index 3ad42d7ce..e5d2ff45c 100644 --- a/providers/azure/go.sum +++ b/providers/azure/go.sum @@ -72,17 +72,17 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/providers/gcp/go.mod b/providers/gcp/go.mod index ea76ed0ff..b8fb550fe 100644 --- a/providers/gcp/go.mod +++ b/providers/gcp/go.mod @@ -12,10 +12,11 @@ require ( github.com/googleapis/gax-go/v2 v2.12.0 github.com/stretchr/testify v1.11.1 golang.org/x/oauth2 v0.16.0 - golang.org/x/sync v0.20.0 + golang.org/x/sync v0.21.0 google.golang.org/api v0.160.0 google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac google.golang.org/grpc v1.61.0 + google.golang.org/protobuf v1.36.10 ) require ( @@ -41,15 +42,14 @@ require ( go.opentelemetry.io/otel v1.22.0 // indirect go.opentelemetry.io/otel/metric v1.22.0 // indirect go.opentelemetry.io/otel/trace v1.22.0 // indirect - golang.org/x/crypto v0.18.0 // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/crypto v0.53.0 // indirect + golang.org/x/net v0.56.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/text v0.38.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect - google.golang.org/protobuf v1.32.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/providers/gcp/go.sum b/providers/gcp/go.sum index 61f0858ca..c76b08648 100644 --- a/providers/gcp/go.sum +++ b/providers/gcp/go.sum @@ -68,8 +68,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= @@ -110,8 +110,8 @@ go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -126,8 +126,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= @@ -135,8 +135,8 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -145,16 +145,16 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -201,8 +201,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 1017f66a887db15016bd0f71cc0349a9f0cf7dcc Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Wed, 10 Jun 2026 18:04:38 -0700 Subject: [PATCH 7/8] fix(test): align GetExecutionByID not-found test with (nil, nil) contract The integration test expected GetExecutionByID to return a "not found" error, but the store's documented contract (and all 14 production call sites) returns (nil, nil) for missing executions, with each caller mapping the nil result to its own not-found error. The stale assertion also dereferenced the nil error, panicking the suite. This was masked until the package's integration build was fixed. --- internal/config/store_postgres_db_test.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/config/store_postgres_db_test.go b/internal/config/store_postgres_db_test.go index 3561d5ef1..868536d2f 100644 --- a/internal/config/store_postgres_db_test.go +++ b/internal/config/store_postgres_db_test.go @@ -593,9 +593,11 @@ func TestPostgresStoreDB_PurchaseExecutions(t *testing.T) { t.Run("GetExecutionByID not found", func(t *testing.T) { nonexistentID := uuid.New().String() - _, err := store.GetExecutionByID(ctx, nonexistentID) - assert.Error(t, err) - assert.Contains(t, err.Error(), "not found") + exec, err := store.GetExecutionByID(ctx, nonexistentID) + // Contract: not-found lookups return (nil, nil); every caller maps + // the nil execution to its own not-found error. + require.NoError(t, err) + assert.Nil(t, exec) }) t.Run("GetExecutionByPlanAndDate success", func(t *testing.T) { From 877653d3893f313fe9640e0e414ffd06a48d9fc5 Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Wed, 10 Jun 2026 22:06:10 -0700 Subject: [PATCH 8/8] ci(e2e): run profile-gated compose E2E suite for real (refs #1180) The E2E Tests job failed in seconds on main because the compose invocation omitted --profile test (the test-runner service is profile-gated) and the referenced Dockerfile.test plus the test suite did not exist; continue-on-error then masked the failure from the ci-success gate. - Add tests/e2e: stdlib-only black-box suite (health readiness with lazy-init nudge, /version, auth enforcement) as its own Go module. - Add Dockerfile.test for the test-runner image, digest-pinned to the same golang base as Dockerfile, pre-compiling the suite at build. - docker-compose.test.yml: wire admin bootstrap secrets via the env secret provider, allow the dev encryption key, disable email and scheduled-task auth, and run the e2e-tagged suite as the command. - scripts/entrypoint.sh: guard DB_PASSWORD_SECRET/SECRET_PROVIDER expansions for set -u so the env-provider compose stack boots. - ci.yml: pass --profile test to every compose call, build via compose, drop continue-on-error so the job gates ci-success, and scan the new tests/e2e module with govulncheck. - .gitignore: exempt Dockerfile.test from the *.test binary pattern. Verified locally: the full compose stack builds and the suite passes end to end against postgres + the app container. --- .github/workflows/ci.yml | 20 ++--- .gitignore | 2 + Dockerfile.test | 20 +++++ docker-compose.test.yml | 29 +++++--- scripts/entrypoint.sh | 7 +- tests/e2e/e2e_test.go | 156 +++++++++++++++++++++++++++++++++++++++ tests/e2e/go.mod | 6 ++ 7 files changed, 217 insertions(+), 23 deletions(-) create mode 100644 Dockerfile.test create mode 100644 tests/e2e/e2e_test.go create mode 100644 tests/e2e/go.mod diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9472636a8..27713cb78 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -306,7 +306,7 @@ jobs: # so scanning the root would silently miss pkg/ and providers/*. # Walk every module independently and fail on any HIGH/CRITICAL. set -e - for mod in . pkg providers/aws providers/azure providers/gcp; do + for mod in . pkg providers/aws providers/azure providers/gcp tests/e2e; do echo "==> govulncheck in $mod" (cd "$mod" && govulncheck ./...) done @@ -372,13 +372,13 @@ jobs: with: args: --severity-threshold=high - # Docker Compose E2E tests - # NOTE: Dockerfile.test and docker-compose.test.yml are not yet implemented. - # continue-on-error prevents this unimplemented job from blocking the ci-success gate. + # Docker Compose E2E tests: build the app + test-runner images, bring up + # postgres + cudly-app, then run the black-box suite in tests/e2e against + # the app over HTTP. The test-runner service is profile-gated, so every + # compose invocation needs --profile test (issue #1180). e2e-tests: name: E2E Tests runs-on: ubuntu-latest - continue-on-error: true steps: - name: Checkout code @@ -389,20 +389,20 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - name: Build test image + - name: Build images run: | - docker build -t cudly:test -f Dockerfile.test . + docker compose -f docker-compose.test.yml --profile test build - - name: Run E2E tests with docker-compose + - name: Run E2E tests with docker compose run: | - docker compose -f docker-compose.test.yml up --abort-on-container-exit --exit-code-from test-runner + docker compose -f docker-compose.test.yml --profile test up --abort-on-container-exit --exit-code-from test-runner env: COMPOSE_INTERACTIVE_NO_CLI: 1 - name: Cleanup if: always() run: | - docker compose -f docker-compose.test.yml down -v + docker compose -f docker-compose.test.yml --profile test down -v # Assert that the Azure custom-role actions list is identical in the TF module # and the ARM onboarding template. Fast (shell + jq only), so it always runs. diff --git a/.gitignore b/.gitignore index a16630aed..13b4ae391 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,8 @@ cmd.test # Test binary, built with `go test -c` *.test +# ... but not the E2E test-runner image definition +!Dockerfile.test # Output of the go coverage tool *.out diff --git a/Dockerfile.test b/Dockerfile.test new file mode 100644 index 000000000..2b319e850 --- /dev/null +++ b/Dockerfile.test @@ -0,0 +1,20 @@ +# Test-runner image for the docker-compose E2E suite (docker-compose.test.yml, +# profile "test"). It only needs the Go toolchain and the stdlib-only module +# under tests/e2e, so the build is fast and independent of the app's +# dependency tree. +# +# Base image pinned by digest for the same supply-chain reasons as Dockerfile; +# keep the digest in sync with the builder stage there. +FROM golang:1.25.4-alpine3.21@sha256:3289aac2aac769e031d644313d094dbda745f28af81cd7a94137e73eefd58b33 + +WORKDIR /e2e + +COPY tests/e2e/ ./ + +# Pre-compile the test binary so `docker compose up` failures are runtime +# failures, not compile errors discovered after the stack is already up. +RUN go vet -tags=e2e ./... && go test -tags=e2e -run NONE ./... + +# docker-compose.test.yml overrides the command; this default keeps the image +# usable standalone. +CMD ["go", "test", "-v", "-tags=e2e", "./..."] diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 3825fb67b..8043691f6 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -48,8 +48,19 @@ services: DB_AUTO_MIGRATE: "true" DB_MIGRATIONS_PATH: /app/migrations - # Secret provider (use env for testing) + # Secret provider (use env for testing). With the env provider, + # *_SECRET variables name the environment variable holding the value. SECRET_PROVIDER: env + ADMIN_EMAIL: admin@test.example + ADMIN_PASSWORD_SECRET: TEST_ADMIN_PASSWORD + TEST_ADMIN_PASSWORD: e2e-local-test-only-password + # Allow the all-zero dev encryption key; this stack never holds real + # cloud credentials. + CREDENTIAL_ENCRYPTION_ALLOW_DEV_KEY: "1" + + # Scheduled-task auth: disabled for the local/CI compose stack (no + # cloud OIDC issuer or bearer secret available) + SCHEDULED_TASK_AUTH_MODE: disabled # DynamoDB tables (for backward compatibility testing) CONFIG_TABLE: test-config @@ -59,7 +70,9 @@ services: GROUPS_TABLE: test-groups SESSIONS_TABLE: test-sessions - # Email configuration (mock) + # Email configuration: disabled, the app falls back to a no-op sender + # (SECRET_PROVIDER=env is not a supported email backend) + EMAIL_ENABLED: "false" EMAIL_ADDRESS: test@example.com # Feature flags @@ -79,7 +92,7 @@ services: retries: 3 start_period: 10s - # Test runner (optional - for running integration tests in container) + # Test runner: black-box E2E suite (tests/e2e) against cudly-app over HTTP. test-runner: build: context: . @@ -101,15 +114,9 @@ services: DB_SSL_MODE: disable networks: - cudly-test - command: > - sh -c " - echo 'Waiting for services to be ready...'; - sleep 5; - echo 'Running E2E tests...'; - go test -v -tags=integration ./...; - " + command: ["go", "test", "-v", "-tags=e2e", "./..."] profiles: - - test # Only start with: docker-compose --profile test up + - test # Only start with: docker compose --profile test up networks: cudly-test: diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index 5061f00a8..568241008 100644 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -46,8 +46,11 @@ if [ "$DB_AUTO_MIGRATE" = "true" ]; then DB_USER=${DB_USER:-cudly} DB_SSL_MODE=${DB_SSL_MODE:-require} - # Get database password from secret or environment - if [ -n "$DB_PASSWORD_SECRET" ] && [ "$SECRET_PROVIDER" != "env" ]; then + # Get database password from secret or environment. Both variables default + # to empty: under `set -u` a bare expansion aborts the container when they + # are unset (e.g. the docker-compose test environment, which passes + # DB_PASSWORD directly). + if [ -n "${DB_PASSWORD_SECRET:-}" ] && [ "${SECRET_PROVIDER:-}" != "env" ]; then echo " Resolving DB password from secret manager..." # Password will be resolved by the application # Migration will use the environment variable if available diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go new file mode 100644 index 000000000..845e0cbc0 --- /dev/null +++ b/tests/e2e/e2e_test.go @@ -0,0 +1,156 @@ +//go:build e2e + +// Package e2e contains black-box end-to-end tests that exercise a running +// CUDly HTTP server over the network. They are driven by docker-compose +// (docker-compose.test.yml, profile "test"): the test-runner container sets +// API_URL to the cudly-app service and runs `go test -tags=e2e ./...`. +// +// The suite intentionally uses only the standard library (see go.mod) so the +// runner image stays small and fast to build. +package e2e + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "testing" + "time" +) + +// apiURL returns the base URL of the app under test. The suite fails loudly +// when API_URL is missing instead of silently falling back to a default that +// would mask a broken compose wiring. +func apiURL(t *testing.T) string { + t.Helper() + u := os.Getenv("API_URL") + if u == "" { + t.Fatal("API_URL environment variable must be set (see docker-compose.test.yml)") + } + return u +} + +var httpClient = &http.Client{Timeout: 10 * time.Second} + +// healthResponse mirrors the fields of internal/server.HealthStatus that the +// suite asserts on. +type healthResponse struct { + Status string `json:"status"` + Checks map[string]json.RawMessage `json:"checks"` +} + +// waitForHealthy polls /health until the app reports overall status +// "healthy" (DB connected, migrations applied) or the deadline passes. +// The endpoint always returns 200 and reports "degraded" while lazy DB +// initialization is still in flight, so polling on the JSON body is the +// correct readiness signal. The app initializes the database lazily on the +// first API request (never from /health itself), so each iteration nudges an +// API endpoint first; without that the status stays "pending" forever. +func waitForHealthy(t *testing.T, base string, deadline time.Duration) healthResponse { + t.Helper() + var last healthResponse + var lastErr error + stop := time.Now().Add(deadline) + for time.Now().Before(stop) { + nudgeLazyInit(base) + last, lastErr = fetchHealth(base) + if lastErr == nil && last.Status == "healthy" { + return last + } + time.Sleep(2 * time.Second) + } + t.Fatalf("app never became healthy within %s: last status %q, last error %v", deadline, last.Status, lastErr) + return healthResponse{} +} + +// nudgeLazyInit fires a throwaway API request to trigger the app's lazy +// database initialization. Errors are ignored: the subsequent /health poll +// is the actual readiness check. +func nudgeLazyInit(base string) { + resp, err := httpClient.Get(base + "/api/recommendations") + if err != nil { + return + } + _ = resp.Body.Close() +} + +func fetchHealth(base string) (healthResponse, error) { + var h healthResponse + resp, err := httpClient.Get(base + "/health") + if err != nil { + return h, err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return h, fmt.Errorf("GET /health: status %d, want %d", resp.StatusCode, http.StatusOK) + } + if err := json.NewDecoder(resp.Body).Decode(&h); err != nil { + return h, fmt.Errorf("GET /health: decode body: %w", err) + } + return h, nil +} + +// TestHealthEndpoint verifies the app boots, connects to Postgres, applies +// migrations and reports fully healthy dependency checks. +func TestHealthEndpoint(t *testing.T) { + base := apiURL(t) + + h := waitForHealthy(t, base, 90*time.Second) + + for _, check := range []string{"config_store", "auth_store", "migrations"} { + if _, ok := h.Checks[check]; !ok { + t.Errorf("health response missing %q check; got checks %v", check, keys(h.Checks)) + } + } +} + +// TestVersionEndpoint verifies the public /version endpoint serves JSON build +// metadata without authentication. +func TestVersionEndpoint(t *testing.T) { + base := apiURL(t) + waitForHealthy(t, base, 90*time.Second) + + resp, err := httpClient.Get(base + "/version") + if err != nil { + t.Fatalf("GET /version: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Fatalf("GET /version: status %d, want %d", resp.StatusCode, http.StatusOK) + } + var body map[string]any + if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { + t.Fatalf("GET /version: decode body: %v", err) + } + if len(body) == 0 { + t.Error("GET /version: empty JSON body") + } +} + +// TestAPIRequiresAuth verifies the auth middleware is live end-to-end: an +// unauthenticated request to a protected endpoint must be rejected, not +// served. +func TestAPIRequiresAuth(t *testing.T) { + base := apiURL(t) + waitForHealthy(t, base, 90*time.Second) + + resp, err := httpClient.Get(base + "/api/recommendations") + if err != nil { + t.Fatalf("GET /api/recommendations: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusUnauthorized { + t.Fatalf("GET /api/recommendations without credentials: status %d, want %d", + resp.StatusCode, http.StatusUnauthorized) + } +} + +func keys(m map[string]json.RawMessage) []string { + out := make([]string, 0, len(m)) + for k := range m { + out = append(out, k) + } + return out +} diff --git a/tests/e2e/go.mod b/tests/e2e/go.mod new file mode 100644 index 000000000..97f38118a --- /dev/null +++ b/tests/e2e/go.mod @@ -0,0 +1,6 @@ +// Standalone module: the E2E suite uses only the standard library so the +// test-runner image (Dockerfile.test) builds without downloading the main +// module's dependency tree. Excluded from the root module on purpose. +module github.com/LeanerCloud/CUDly/tests/e2e + +go 1.25