From fc8ba4d6150f0bca2817c885e64661407417c5f3 Mon Sep 17 00:00:00 2001 From: zhangwenjian Date: Wed, 9 Sep 2026 16:40:14 +0800 Subject: [PATCH 1/9] =?UTF-8?q?feat=E2=9C=A8:=20let=20the=20example=20appl?= =?UTF-8?q?ication=20say=20what=20it=20is?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit app-order registered its migrations and its menus and nothing else, so `migrate install order` answered that no application in the binary registers a manifest. Which was true, and made the installer untestable against the one application this repository ships. The manifest goes in the migration package rather than one of its own because that is the package a host has to import for the application to exist at all - its migrations register from there too. A second package would be a second thing to remember to import, and forgetting it would leave an application whose migrations run and which no installer can name. Its Version is not the migration version and the two move independently: adding a migration file without renaming the application is normal, and so is a release that changes no schema. The migration versions decide what runs; this decides what the installed row says. go-admin-core moves to v2.8.0, which is where contract/app lives. --- example/app-order/go.mod | 2 +- example/app-order/go.sum | 4 +-- example/app-order/migration/manifest.go | 39 +++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 example/app-order/migration/manifest.go diff --git a/example/app-order/go.mod b/example/app-order/go.mod index af552c75..7aed2dd5 100644 --- a/example/app-order/go.mod +++ b/example/app-order/go.mod @@ -5,7 +5,7 @@ go 1.25.13 require ( github.com/gin-gonic/gin v1.12.0 github.com/glebarez/sqlite v1.11.0 - github.com/go-admin-team/go-admin-core/v2 v2.5.0 + github.com/go-admin-team/go-admin-core/v2 v2.8.0 gorm.io/gorm v1.31.2 ) diff --git a/example/app-order/go.sum b/example/app-order/go.sum index f6730f22..0489c274 100644 --- a/example/app-order/go.sum +++ b/example/app-order/go.sum @@ -58,8 +58,8 @@ github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= -github.com/go-admin-team/go-admin-core/v2 v2.5.0 h1:aD1SALklBxizGB9u8cOgm4OT8z656FM83F4fD6dMz9g= -github.com/go-admin-team/go-admin-core/v2 v2.5.0/go.mod h1:LG/XvEfOplbuadKrPTPm0Nu5pN06aQUNZZC3ao4B4gs= +github.com/go-admin-team/go-admin-core/v2 v2.8.0 h1:ZTw5Z/UT1/7OltbGPEaEVerRk4z3koB6O8nDbb84tPM= +github.com/go-admin-team/go-admin-core/v2 v2.8.0/go.mod h1:LG/XvEfOplbuadKrPTPm0Nu5pN06aQUNZZC3ao4B4gs= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= diff --git a/example/app-order/migration/manifest.go b/example/app-order/migration/manifest.go new file mode 100644 index 00000000..b183f193 --- /dev/null +++ b/example/app-order/migration/manifest.go @@ -0,0 +1,39 @@ +package migration + +import ( + "github.com/go-admin-team/go-admin-core/v2/sdk/contract/app" +) + +// Version is what this application calls itself. A host's installer records +// it, compares it against what is already installed to tell an upgrade from a +// downgrade, and shows it in `migrate status`. +// +// It is not the same thing as the migration version above, and the two move +// independently: adding a migration file without changing what the +// application is called is normal, and so is a release that changes no +// schema. The migration versions decide what runs; this decides what the +// installed row says. +const Version = "1.0.0" + +// The manifest is registered from this package rather than one of its own +// because this is the package a host has to import for the application to +// exist at all - the migrations register here too. A second package would be +// a second thing to remember to import, and forgetting it would leave an +// application whose migrations run and which no installer can name. +func init() { + app.Register(app.Manifest{ + Code: AppCode, + Name: "Order example", + Version: Version, + Description: "A worked example of an application that ships its own tables, menus and APIs.", + Author: "go-admin", + // Nothing yet. When an application does declare dependencies, a host + // refuses to install it until they are installed - it does not + // install them for you, because the blast radius of installing one + // application should not be "and everything it happens to name". + Requires: nil, + // Reserved. A host stores both and interprets neither. + Pricing: "", + License: "MIT", + }) +} From 3a5afeb518a3bacef15508ed28f211cf73a23710 Mon Sep 17 00:00:00 2001 From: zhangwenjian Date: Wed, 9 Sep 2026 16:40:15 +0800 Subject: [PATCH 2/9] =?UTF-8?q?test=E2=9C=85:=20install=20and=20uninstall?= =?UTF-8?q?=20the=20example=20application=20end=20to=20end?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Everything under `migrate install` was covered with an injected engine and a hand-built schema, which is where the shapes belong. What none of it could catch is the wiring: whether an application's init() reaches both registries, whether the installer finds a manifest through app.Snapshot, whether the seeder writes what the uninstaller goes looking for, and whether the command exits non-zero when a migration fails - which a deployment reads to decide whether to start the new version. This builds a go-admin binary with the example application linked in, migrates a real database with it, and drives the whole sequence: framework migrations only, install, install again, put a row in the application's own table, uninstall, reinstall. It lives in its own module. A tagged import in the main module would still be resolved by `go mod tidy`, which considers every build tag and would go looking for github.com/go-admin-team/example-app-order on the network - a repository that does not exist, because the example is a directory inside this one. That was checked rather than assumed: tidy fails there with "Repository not found". A build tag of `ignore` is skipped by tidy but cannot be turned on either, because the standard library uses it for files that are not meant to build at all. A separate module with replace directives is invisible to the main module's tidy, its build, its tests and checksilent, and needs no go.work. Three degradations turn it red: the example application not registering a manifest, the uninstall not clearing sys_migration - where the reinstall then seeds nothing and the assertion reads "menus = 0, want 4" - and the seeder not recording its grants, where the uninstall then leaves every policy behind. --- test/e2e-apporder/e2e_test.go | 268 +++++++++++ test/e2e-apporder/go.mod | 156 ++++++ test/e2e-apporder/go.sum | 883 ++++++++++++++++++++++++++++++++++ test/e2e-apporder/main.go | 28 ++ 4 files changed, 1335 insertions(+) create mode 100644 test/e2e-apporder/e2e_test.go create mode 100644 test/e2e-apporder/go.mod create mode 100644 test/e2e-apporder/go.sum create mode 100644 test/e2e-apporder/main.go diff --git a/test/e2e-apporder/e2e_test.go b/test/e2e-apporder/e2e_test.go new file mode 100644 index 00000000..a8a0bd65 --- /dev/null +++ b/test/e2e-apporder/e2e_test.go @@ -0,0 +1,268 @@ +package main + +import ( + "database/sql" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + _ "github.com/glebarez/go-sqlite" +) + +// The whole 008 chain, against a binary that was actually built and a +// database that was actually migrated. +// +// Everything below this level is covered by unit tests with an injected +// engine and a hand-built schema, which is where the shapes are pinned down. +// What only this can catch is the wiring: that an application's init() +// reaches both registries, that `install` finds a manifest through +// app.Snapshot, that the seeder writes what the uninstaller looks for, and +// that the command exits non-zero when a migration fails - the last of which +// a deployment reads to decide whether to start the new version. +const settings = `settings: + application: + host: 0.0.0.0 + mode: dev + name: e2e + port: 8000 + readtimeout: 10000 + writertimeout: 20000 + database: + driver: sqlite3 + source: ./e2e.db + jwt: + secret: e2e + timeout: 3600 + logger: + path: temp/logs + stdout: default + level: error + enableddb: false + queue: + memory: + poolSize: 10 +` + +type env struct { + t *testing.T + dir string + bin string +} + +// newEnv builds the binary and lays out a working directory for it. +func newEnv(t *testing.T) *env { + t.Helper() + if testing.Short() { + t.Skip("builds a binary and migrates a database; skipped under -short") + } + + dir := t.TempDir() + if err := os.MkdirAll(filepath.Join(dir, "config"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.MkdirAll(filepath.Join(dir, "temp", "logs"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "config", "settings.yml"), []byte(settings), 0o644); err != nil { + t.Fatal(err) + } + // The framework's first migration reads this file rather than carrying + // the rows in Go. + seedSQL, err := os.ReadFile(filepath.Join("..", "..", "config", "db.sql")) + if err != nil { + t.Fatalf("reading config/db.sql: %v", err) + } + if err := os.WriteFile(filepath.Join(dir, "config", "db.sql"), seedSQL, 0o644); err != nil { + t.Fatal(err) + } + + bin := filepath.Join(dir, "go-admin-e2e") + build := exec.Command("go", "build", "-tags", "sqlite3", "-o", bin, ".") + if out, err := build.CombinedOutput(); err != nil { + t.Fatalf("building the binary: %v\n%s", err, out) + } + return &env{t: t, dir: dir, bin: bin} +} + +// run executes the binary and returns its combined output and exit code. +func (e *env) run(args ...string) (string, int) { + e.t.Helper() + args = append(args, "-c", "config/settings.yml") + cmd := exec.Command(e.bin, args...) + cmd.Dir = e.dir + out, err := cmd.CombinedOutput() + code := 0 + if ee, ok := err.(*exec.ExitError); ok { + code = ee.ExitCode() + } else if err != nil { + e.t.Fatalf("running %v: %v", args, err) + } + return string(out), code +} + +func (e *env) mustRun(args ...string) string { + e.t.Helper() + out, code := e.run(args...) + if code != 0 { + e.t.Fatalf("%v exited %d:\n%s", args, code, out) + } + return out +} + +// exec runs one statement against the database the binary uses. +func (e *env) exec(stmt string) { + e.t.Helper() + db, err := sql.Open("sqlite", filepath.Join(e.dir, "e2e.db")) + if err != nil { + e.t.Fatal(err) + } + defer db.Close() + if _, err := db.Exec(stmt); err != nil { + e.t.Fatalf("%s: %v", stmt, err) + } +} + +func (e *env) count(query string, args ...any) int { + e.t.Helper() + db, err := sql.Open("sqlite", filepath.Join(e.dir, "e2e.db")) + if err != nil { + e.t.Fatal(err) + } + defer db.Close() + var n int + if err := db.QueryRow(query, args...).Scan(&n); err != nil { + e.t.Fatalf("%s: %v", query, err) + } + return n +} + +func TestInstallUninstallReinstall(t *testing.T) { + e := newEnv(t) + + // Framework only. The application's migration must not run here, or the + // install below has nothing left to do and the interesting half of it + // goes untested - which is what happens if this uses plain `migrate`. + e.mustRun("migrate", "--app", "core") + if n := e.count("SELECT COUNT(*) FROM sys_migration WHERE app_code = 'order'"); n != 0 { + t.Fatalf("the application's migration ran during the framework's: %d rows", n) + } + if n := e.count("SELECT COUNT(*) FROM sqlite_master WHERE name = 'app_order'"); n != 0 { + t.Fatal("the application's own table exists before it was installed") + } + + // A1. + out := e.mustRun("migrate", "install", "order") + if !strings.Contains(out, "order-1793800000000") { + t.Errorf("the install did not report applying the migration:\n%s", out) + } + // A7. + if !strings.Contains(out, "rebuild") { + t.Errorf("the install did not say the code is not running yet:\n%s", out) + } + for _, c := range []struct { + what string + query string + want int + }{ + {"menus", "SELECT COUNT(*) FROM sys_menu WHERE app_code = 'order'", 4}, + {"apis", "SELECT COUNT(*) FROM sys_api WHERE app_code = 'order'", 4}, + {"ledger", "SELECT COUNT(*) FROM sys_app_casbin_grant WHERE app_code = 'order'", 4}, + {"policies", "SELECT COUNT(*) FROM casbin_rule WHERE v1 LIKE '/api/v1/order%'", 4}, + {"migration records", "SELECT COUNT(*) FROM sys_migration WHERE app_code = 'order'", 1}, + {"sys_app rows", "SELECT COUNT(*) FROM sys_app WHERE app_code = 'order'", 1}, + {"its own table", "SELECT COUNT(*) FROM sqlite_master WHERE name = 'app_order'", 1}, + {"status=installed", "SELECT COUNT(*) FROM sys_app WHERE app_code = 'order' AND status = 2", 1}, + } { + if n := e.count(c.query); n != c.want { + t.Errorf("after install, %s = %d, want %d", c.what, n, c.want) + } + } + + // A2: installing the same version again does nothing and says so. + out = e.mustRun("migrate", "install", "order") + if !strings.Contains(out, "already installed") { + t.Errorf("a second install was not reported as a no-op:\n%s", out) + } + + // A3: the application's own data is not the uninstaller's to remove. + e.exec("INSERT INTO app_order (created_at, updated_at) VALUES (datetime('now'), datetime('now'))") + if n := e.count("SELECT COUNT(*) FROM app_order"); n != 1 { + t.Fatalf("the business row was not written: %d", n) + } + + out = e.mustRun("migrate", "uninstall", "order") + if !strings.Contains(out, "own tables were not touched") { + t.Errorf("the uninstall did not say what it left alone:\n%s", out) + } + for _, c := range []struct { + what string + query string + }{ + {"menus", "SELECT COUNT(*) FROM sys_menu WHERE app_code = 'order'"}, + {"apis", "SELECT COUNT(*) FROM sys_api WHERE app_code = 'order'"}, + {"ledger", "SELECT COUNT(*) FROM sys_app_casbin_grant WHERE app_code = 'order'"}, + {"policies", "SELECT COUNT(*) FROM casbin_rule WHERE v1 LIKE '/api/v1/order%'"}, + {"migration records", "SELECT COUNT(*) FROM sys_migration WHERE app_code = 'order'"}, + {"sys_app rows", "SELECT COUNT(*) FROM sys_app WHERE app_code = 'order'"}, + } { + if n := e.count(c.query); n != 0 { + t.Errorf("after uninstall, %s = %d, want 0", c.what, n) + } + } + if n := e.count("SELECT COUNT(*) FROM sqlite_master WHERE name = 'app_order'"); n != 1 { + t.Error("the uninstall dropped the application's own table") + } + if n := e.count("SELECT COUNT(*) FROM app_order"); n != 1 { + t.Errorf("the uninstall removed %d business row(s)", 1-n) + } + + // A4: the migration records had to go, or this reinstall finds every + // version applied, seeds nothing, and reports success. + e.mustRun("migrate", "install", "order") + for _, c := range []struct { + what string + query string + want int + }{ + {"menus", "SELECT COUNT(*) FROM sys_menu WHERE app_code = 'order'", 4}, + {"ledger", "SELECT COUNT(*) FROM sys_app_casbin_grant WHERE app_code = 'order'", 4}, + {"policies", "SELECT COUNT(*) FROM casbin_rule WHERE v1 LIKE '/api/v1/order%'", 4}, + } { + if n := e.count(c.query); n != c.want { + t.Errorf("after reinstall, %s = %d, want %d", c.what, n, c.want) + } + } + if n := e.count("SELECT COUNT(*) FROM app_order"); n != 1 { + t.Error("the business row did not survive an uninstall and reinstall") + } +} + +// A deployment decides whether to start the new version on this exit code. +func TestMigrateExitsNonZeroWhenItFails(t *testing.T) { + e := newEnv(t) + + // The framework's first migration reads config/db.sql. Without it the + // migration fails, which is the cheapest real failure to arrange. + if err := os.Remove(filepath.Join(e.dir, "config", "db.sql")); err != nil { + t.Fatal(err) + } + out, code := e.run("migrate") + if code == 0 { + t.Errorf("a failed migration exited 0:\n%s", out) + } +} + +func TestUnknownAppIsRefused(t *testing.T) { + e := newEnv(t) + e.mustRun("migrate", "--app", "core") + + out, code := e.run("migrate", "install", "ordr") + if code == 0 { + t.Errorf("a mistyped code was installed:\n%s", out) + } + if !strings.Contains(out, "order") { + t.Errorf("the refusal does not name what is registered:\n%s", out) + } +} diff --git a/test/e2e-apporder/go.mod b/test/e2e-apporder/go.mod new file mode 100644 index 00000000..c1a9b53b --- /dev/null +++ b/test/e2e-apporder/go.mod @@ -0,0 +1,156 @@ +module go-admin-e2e-apporder + +go 1.26.5 + +require ( + github.com/glebarez/go-sqlite v1.22.0 + github.com/go-admin-team/example-app-order v0.0.0 + go-admin v0.0.0 +) + +require ( + dario.cat/mergo v1.0.2 // indirect + filippo.io/edwards25519 v1.2.0 // indirect + github.com/BurntSushi/toml v1.5.0 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect + github.com/alibaba/sentinel-golang v1.0.4 // indirect + github.com/alibaba/sentinel-golang/pkg/adapters/gin v0.0.0-20241224061304-f4c2c5964666 // indirect + github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible // indirect + github.com/andeya/ameda v1.5.3 // indirect + github.com/andeya/goutil v1.1.2 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bitly/go-simplejson v0.5.1 // indirect + github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect + github.com/bytedance/go-tagexpr/v2 v2.9.11 // indirect + github.com/bytedance/gopkg v0.1.4 // indirect + github.com/bytedance/sonic v1.15.2 // indirect + github.com/bytedance/sonic/loader v0.5.2 // indirect + github.com/casbin/casbin/v3 v3.8.1 // indirect + github.com/casbin/gorm-adapter/v3 v3.41.0 // indirect + github.com/casbin/govaluate v1.10.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/chanxuehong/rand v0.0.0-20211009035549-2f07823e8e99 // indirect + github.com/chanxuehong/wechat v0.0.0-20230222024006-36f0325263cd // indirect + github.com/cloudwego/base64x v0.1.7 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.15 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/gin-contrib/sse v1.1.1 // indirect + github.com/gin-gonic/gin v1.12.0 // indirect + github.com/glebarez/sqlite v1.11.0 // indirect + github.com/go-admin-team/go-admin-core/v2 v2.8.0 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-openapi/jsonpointer v1.0.0 // indirect + github.com/go-openapi/jsonreference v1.0.0 // indirect + github.com/go-openapi/spec v0.22.9 // indirect + github.com/go-openapi/swag/conv v0.28.0 // indirect + github.com/go-openapi/swag/jsonutils v0.28.0 // indirect + github.com/go-openapi/swag/loading v0.28.0 // indirect + github.com/go-openapi/swag/pools v0.28.0 // indirect + github.com/go-openapi/swag/stringutils v0.28.0 // indirect + github.com/go-openapi/swag/typeutils v0.28.0 // indirect + github.com/go-openapi/swag/yamlutils v0.28.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.30.3 // indirect + github.com/go-sql-driver/mysql v1.10.0 // indirect + github.com/goccy/go-json v0.10.6 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect + github.com/gofrs/flock v0.8.1 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect + github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/huaweicloud/huaweicloud-sdk-go-obs v3.26.6+incompatible // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.10.0 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.4.0 // indirect + github.com/leodido/go-urn v1.5.0 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect + github.com/mattn/go-sqlite3 v1.14.49 // indirect + github.com/microsoft/go-mssqldb v1.10.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mojocn/base64Captcha v1.3.8 // indirect + github.com/mssola/user_agent v0.6.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/ncruces/go-strftime v1.0.0 // indirect + github.com/nyaruka/phonenumbers v1.2.2 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/pelletier/go-toml/v2 v2.4.3 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/prometheus/client_golang v1.24.1 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.70.1 // indirect + github.com/prometheus/procfs v0.21.1 // indirect + github.com/qiniu/go-sdk/v7 v7.27.0 // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.61.0 // indirect + github.com/redis/go-redis/v9 v9.22.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect + github.com/shamsher31/goimgext v1.0.0 // indirect + github.com/shirou/gopsutil/v3 v3.24.5 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/cobra v1.10.2 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/swaggo/files v1.0.1 // indirect + github.com/swaggo/gin-swagger v1.6.1 // indirect + github.com/swaggo/swag v1.16.6 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.2 // indirect + github.com/unrolled/secure v1.17.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.mongodb.org/mongo-driver/v2 v2.8.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.1 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/arch v0.30.0 // indirect + golang.org/x/crypto v0.54.0 // indirect + golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect + golang.org/x/image v0.41.0 // indirect + golang.org/x/mod v0.38.0 // indirect + golang.org/x/net v0.57.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.40.0 // indirect + golang.org/x/time v0.15.0 // indirect + golang.org/x/tools v0.48.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gorm.io/driver/mysql v1.6.0 // indirect + gorm.io/driver/postgres v1.6.2 // indirect + gorm.io/driver/sqlite v1.6.0 // indirect + gorm.io/driver/sqlserver v1.6.4 // indirect + gorm.io/gorm v1.31.2 // indirect + gorm.io/plugin/dbresolver v1.6.2 // indirect + gorm.io/plugin/soft_delete v1.2.1 // indirect + modernc.org/fileutil v1.3.40 // indirect + modernc.org/libc v1.67.4 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect + modernc.org/sqlite v1.42.2 // indirect +) + +replace go-admin => ../.. + +replace github.com/go-admin-team/example-app-order => ../../example/app-order diff --git a/test/e2e-apporder/go.sum b/test/e2e-apporder/go.sum new file mode 100644 index 00000000..bacd6323 --- /dev/null +++ b/test/e2e-apporder/go.sum @@ -0,0 +1,883 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= +filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 h1:jHb/wfvRikGdxMXYV3QG/SzUOPYN9KEUUuC0Yd0/vC0= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1/go.mod h1:pzBXCYn05zvYIrwLgtK8Ap8QcjRg+0i76tMQdWN6wOk= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 h1:E4MgwLBGeVB5f2MdcIVD3ELVAWpr+WD6MUe1i+tM/PA= +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/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 h1:7dONQ3WNZ1zy960TmkxJPuwoolZwL7xKtpcM04MBnt4= +github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI= +github.com/alibaba/sentinel-golang v1.0.2/go.mod h1:QsB99f/z35D2AiMrAWwgWE85kDTkBUIkcmPrRt+61NI= +github.com/alibaba/sentinel-golang v1.0.4 h1:i0wtMvNVdy7vM4DdzYrlC4r/Mpk1OKUUBurKKkWhEo8= +github.com/alibaba/sentinel-golang v1.0.4/go.mod h1:Lag5rIYyJiPOylK8Kku2P+a23gdKMMqzQS7wTnjWEpk= +github.com/alibaba/sentinel-golang/pkg/adapters/gin v0.0.0-20241224061304-f4c2c5964666 h1:nLA94XbUtqArHLPtBZuPuIlOOS1rWnWs8ANnMj+TW4Y= +github.com/alibaba/sentinel-golang/pkg/adapters/gin v0.0.0-20241224061304-f4c2c5964666/go.mod h1:RWwrQy9bLKVZyr47l34uImkq7LQEc2ngkKn2O2LV1gE= +github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g= +github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/andeya/ameda v1.5.3 h1:SvqnhQPZwwabS8HQTRGfJwWPl2w9ZIPInHAw9aE1Wlk= +github.com/andeya/ameda v1.5.3/go.mod h1:FQDHRe1I995v6GG+8aJ7UIUToEmbdTJn/U26NCPIgXQ= +github.com/andeya/goutil v1.0.1/go.mod h1:jEG5/QnnhG7yGxwFUX6Q+JGMif7sjdHmmNVjn7nhJDo= +github.com/andeya/goutil v1.1.2 h1:RiFWFkL/9yXh2SjQkNWOHqErU1x+RauHmeR23eNUzSg= +github.com/andeya/goutil v1.1.2/go.mod h1:jEG5/QnnhG7yGxwFUX6Q+JGMif7sjdHmmNVjn7nhJDo= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow= +github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= +github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bytedance/go-tagexpr/v2 v2.9.11 h1:jJgmoDKPKacGl0llPYbYL/+/2N+Ng0vV0ipbnVssXHY= +github.com/bytedance/go-tagexpr/v2 v2.9.11/go.mod h1:UAyKh4ZRLBPGsyTRFZoPqTni1TlojMdOJXQnEIPCX84= +github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM= +github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4= +github.com/bytedance/sonic v1.15.2 h1:90H+rcF/FwLXwfB1cudOLq/je83n683Utf4Cbp0xHCo= +github.com/bytedance/sonic v1.15.2/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4UzmKx2SA= +github.com/bytedance/sonic/loader v0.5.2 h1:0QtP1gevc1OZ6/H8Lb9BRZiCXd1Ftjd3OKuj1T1lBIo= +github.com/bytedance/sonic/loader v0.5.2/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/casbin/casbin/v3 v3.8.1 h1:D4dEY4knePPR4YgNP5WZtWNaOxD0UK0LpPy9+zxtBwo= +github.com/casbin/casbin/v3 v3.8.1/go.mod h1:5rJbQr2e6AuuDDNxnPc5lQlC9nIgg6nS1zYwKXhpHC8= +github.com/casbin/gorm-adapter/v3 v3.41.0 h1:Xhpi0tfRP9aKPDWDf6dgBxHZ9UM6IophxxPIEGWqCNM= +github.com/casbin/gorm-adapter/v3 v3.41.0/go.mod h1:BQZRJhwUnwMpI+pT2m7/cUJwXxrHfzpBpPcNTyMGeGA= +github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= +github.com/casbin/govaluate v1.10.0 h1:ffGw51/hYH3w3rZcxO/KcaUIDOLP84w7nsidMVgaDG0= +github.com/casbin/govaluate v1.10.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chanxuehong/rand v0.0.0-20211009035549-2f07823e8e99 h1:K62Lb6bsgLOB++z/VAvRvtiEBdNCuMfmQGTGGWMdPpM= +github.com/chanxuehong/rand v0.0.0-20211009035549-2f07823e8e99/go.mod h1:9+sJ9zvvkXC5sPjPEZM3Jpb9n2Q2VtcrGZly0UHYF5I= +github.com/chanxuehong/util v0.0.0-20200304121633-ca8141845b13/go.mod h1:XEYt99iTxMqkv+gW85JX/DdUINHUe43Sbe5AtqSaDAQ= +github.com/chanxuehong/wechat v0.0.0-20230222024006-36f0325263cd h1:v3JNsFZmplLO/Cmiyr/rGvR7lW1ld9lB+d5h4yR0MTI= +github.com/chanxuehong/wechat v0.0.0-20230222024006-36f0325263cd/go.mod h1:mysjrtCs9MmN8hqDf4/mc4eQ26Rt9s1p5oO+fhJlLB4= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudwego/base64x v0.1.7 h1:NppS+Fgzg5ovhn4NkUXaDT3x9jldgH5ToMCqzBSi2zI= +github.com/cloudwego/base64x v0.1.7/go.mod h1:Cu1PV9zfrSf7ET2tIbWbbEy7jO7HHJ13q4X2SQ8aWYg= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI= +github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= +github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko= +github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s= +github.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= +github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= +github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= +github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= +github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= +github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= +github.com/go-admin-team/go-admin-core/v2 v2.8.0 h1:ZTw5Z/UT1/7OltbGPEaEVerRk4z3koB6O8nDbb84tPM= +github.com/go-admin-team/go-admin-core/v2 v2.8.0/go.mod h1:LG/XvEfOplbuadKrPTPm0Nu5pN06aQUNZZC3ao4B4gs= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s= +github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y= +github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY= +github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0= +github.com/go-openapi/spec v0.22.9 h1:/vKIFDcGKp0ktZWGbym/tJEWbk6/XOEmAVU0kqKMH+w= +github.com/go-openapi/spec v0.22.9/go.mod h1:b/mNUYIOQOyIiUzUzXEE8xzyZqf93KvM9hQGP91yfl0= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag/conv v0.28.0 h1:GtqqbyFe7vR5Y7ehxG9W6/OvrSFdf1OLeTGp40TqxH8= +github.com/go-openapi/swag/conv v0.28.0/go.mod h1:mbUE+mzctnhxi864m0Q07SpN8OowD9JhxmxuYvZZD/k= +github.com/go-openapi/swag/jsonutils v0.28.0 h1:YIch6FwO7RXzeAnbO8Tu7dWBZeUEH+4nA0HXltVTnv4= +github.com/go-openapi/swag/jsonutils v0.28.0/go.mod h1:CYM3WlTUcagR2ZoHdz54di/cbBqt82tuxuXgAjxw+mg= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0 h1:qV+VVUAx5Oro8WjVWpZeql7YReTKhT4smR4zhcOQZr0= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY= +github.com/go-openapi/swag/loading v0.28.0 h1:td8QZdZC9MIYGGSnSPKShKiK22I2tU5UQvuUhIBPRLU= +github.com/go-openapi/swag/loading v0.28.0/go.mod h1:rXB0QiQX5mMveXEA7ouM4KiiM9jVJe4K6BVbwhD1M4k= +github.com/go-openapi/swag/pools v0.28.0 h1:HPMZWSAfce3rdVTFcjFiCIBtDg9h4x2QlRrHipwhxeU= +github.com/go-openapi/swag/pools v0.28.0/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= +github.com/go-openapi/swag/stringutils v0.28.0 h1:ixsc9iYgDPubHL/8nSkbnryEHpD2VRlBMLKpQyPXcDU= +github.com/go-openapi/swag/stringutils v0.28.0/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= +github.com/go-openapi/swag/typeutils v0.28.0 h1:nRBKSBXjDgf01VDPB3fWeD9nQuhCOVeIYAkUx2tbkyY= +github.com/go-openapi/swag/typeutils v0.28.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= +github.com/go-openapi/swag/yamlutils v0.28.0 h1:TV3JXH6DS46KUroDtMLAYHGkdWf5VDq3wVWFirmzROY= +github.com/go-openapi/swag/yamlutils v0.28.0/go.mod h1:x0q/yndZHEgk9Rx3DyDqzFUmHy55KTvIZldvF2dTJXs= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo= +github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= +github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-playground/validator/v10 v10.30.3 h1:4MU6YkEwx7GbcPJOZxrtbu+QfF3pJLJuaYTeAH0DYy8= +github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw= +github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= +github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/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.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huaweicloud/huaweicloud-sdk-go-obs v3.26.6+incompatible h1:lX3m9hvP5tSnJ8bFg/TdT2BYHj1nSBulealy5VN9mPU= +github.com/huaweicloud/huaweicloud-sdk-go-obs v3.26.6+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= +github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk= +github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw= +github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.5.0 h1:pLqT2kq1zpHW/1D18QMjMpdtX7cekxqtJJjg5ANyWw0= +github.com/leodido/go-urn v1.5.0/go.mod h1:9BORnCDhdPBJNDEX+w1bJisa8yOKYi116VeO96s4ifE= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= +github.com/mattn/go-sqlite3 v1.14.49 h1:B8jBHC3xhxZgxztrgruTuLucebnULQnx4W7cF7SAE9w= +github.com/mattn/go-sqlite3 v1.14.49/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/microsoft/go-mssqldb v1.10.0 h1:pHEt+Qz6YFPWqREq10mqSE524QQo+/QremwTCQht7TY= +github.com/microsoft/go-mssqldb v1.10.0/go.mod h1:mnG7lGa9iYJbzJqGCXyuQCegStKMr3kogDLD6+bmggg= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mojocn/base64Captcha v1.3.8 h1:rrN9BhCwXKS8ht1e21kvR3iTaMgf4qPC9sRoV52bqEg= +github.com/mojocn/base64Captcha v1.3.8/go.mod h1:QFZy927L8HVP3+VV5z2b1EAEiv1KxVJKZbAucVgLUy4= +github.com/mssola/user_agent v0.6.0 h1:uwPR4rtWlCHRFyyP9u2KOV0u8iQXmS7Z7feTrstQwk4= +github.com/mssola/user_agent v0.6.0/go.mod h1:TTPno8LPY3wAIEKRpAtkdMT0f8SE24pLRGPahjCH4uw= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/nyaruka/phonenumbers v1.0.55/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U= +github.com/nyaruka/phonenumbers v1.2.2 h1:OwVjf7Y4uHoK9VJUrA8ebR0ha2yc6sEYbfrwkq0asCY= +github.com/nyaruka/phonenumbers v1.2.2/go.mod h1:wzk2qq7qwsaBKrfbkWKdgHYOOH+QFTesSpIq53ELw8M= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= +github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= +github.com/prometheus/client_golang v1.24.1 h1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU= +github.com/prometheus/client_golang v1.24.1/go.mod h1:F+oSRECHg4sse5ucfYpYDeIv/hu68Zo0uoHKetWnzcE= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.70.1 h1:1HvjP4D5oL3t8RsPlwxA9onvvStjtIHYE5XuuwOi/PY= +github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86TJz5qbJRhc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= +github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= +github.com/qiniu/go-sdk/v7 v7.27.0 h1:n+2U0S5fhbmG/lN/agO8KcYJaQDqROUVShtnp56Mkw8= +github.com/qiniu/go-sdk/v7 v7.27.0/go.mod h1:pTwVR1B+8SXcPLhDzBUasiKFTD9F7jRglRDR553BW3k= +github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0= +github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.61.0 h1:ui88A53s8MSVYLC56en0KQ17HARk+9986Dn0SBfKNvA= +github.com/quic-go/quic-go v0.61.0/go.mod h1:9So2anK4Tp22URSQq00k+Vo2PNkle96ycDPDHL4s9vs= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= +github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shamsher31/goimgext v1.0.0 h1:wFKf9GeeE0Xr6UQtliaPgYYgTju2izobM7XpCEgUCC8= +github.com/shamsher31/goimgext v1.0.0/go.mod h1:rYLKgXuTGBIaH49z+jUVSWz7gUWIZmqvYUsdvJbNNOc= +github.com/shirou/gopsutil v3.20.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil/v3 v3.21.6/go.mod h1:JfVbDpIBLVzT8oKbvMg9P3wEIMDDpVn+LwHTKj0ST88= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= +github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= +github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= +github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= +github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs7cY= +github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw= +github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= +github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tklauser/go-sysconf v0.3.6/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= +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.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ugorji/go/codec v1.3.2 h1:zkEASHHyEClGeURfgNT9PJZVfAbs9oEX9QXggwWNJbc= +github.com/ugorji/go/codec v1.3.2/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/unrolled/secure v1.17.0 h1:Io7ifFgo99Bnh0J7+Q+qcMzWM6kaDPCA5FroFZEdbWU= +github.com/unrolled/secure v1.17.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= +github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.mongodb.org/mongo-driver/v2 v2.8.0 h1:CxWDGQYY8QQwNjAl/aq2sfWakdnWZynnqJ9F4DhHbP8= +go.mongodb.org/mongo-driver/v2 v2.8.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/arch v0.30.0 h1:sB9h+1gRGa2+LauFSV0tm8bK1J2yo1bx6/Uyi/P6DTU= +golang.org/x/arch v0.30.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +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.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0= +golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= +golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= +golang.org/x/image v0.41.0 h1:8wS72eGJMJaBxK6okTzd4WaXumUlTVlb753MlsSvTCo= +golang.org/x/image v0.41.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA= +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= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= +golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +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.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-20190911185100-cd5d95a43a6e/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/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= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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.5.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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +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/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= +golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= +gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= +gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= +gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/sqlite v1.1.3/go.mod h1:AKDgRWk8lcSQSw+9kxCJnX/yySj8G3rdwYlU57cB45c= +gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= +gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= +gorm.io/driver/sqlserver v1.6.4 h1:kGA9Z0D7dnIz7yVvWp18qLBSPFpUQWGqMA4rnxkScdQ= +gorm.io/driver/sqlserver v1.6.4/go.mod h1:oRtXDKFRYj8MqyMq+JFEdaA+StSQKC4zupU6blIdB0s= +gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.23.0/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= +gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= +gorm.io/plugin/dbresolver v1.6.2 h1:F4b85TenghUeITqe3+epPSUtHH7RIk3fXr5l83DF8Pc= +gorm.io/plugin/dbresolver v1.6.2/go.mod h1:tctw63jdrOezFR9HmrKnPkmig3m5Edem9fdxk9bQSzM= +gorm.io/plugin/soft_delete v1.2.1 h1:qx9D/c4Xu6w5KT8LviX8DgLcB9hkKl6JC9f44Tj7cGU= +gorm.io/plugin/soft_delete v1.2.1/go.mod h1:Zv7vQctOJTGOsJ/bWgrN1n3od0GBAZgnLjEx+cApLGk= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= +modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc= +modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM= +modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= +modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE= +modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.67.4 h1:zZGmCMUVPORtKv95c2ReQN5VDjvkoRm9GWPTEPuvlWg= +modernc.org/libc v1.67.4/go.mod h1:QvvnnJ5P7aitu0ReNpVIEyesuhmDLQ8kaEoyMjIFZJA= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.42.2 h1:7hkZUNJvJFN2PgfUdjni9Kbvd4ef4mNLOu0B9FGxM74= +modernc.org/sqlite v1.42.2/go.mod h1:+VkC6v3pLOAE0A0uVucQEcbVW0I5nHCeDaBf+DpsQT8= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/test/e2e-apporder/main.go b/test/e2e-apporder/main.go new file mode 100644 index 00000000..2ee9ee3a --- /dev/null +++ b/test/e2e-apporder/main.go @@ -0,0 +1,28 @@ +// Command e2e-apporder is a go-admin binary with the example application +// linked into it. +// +// It exists because there is otherwise nothing to install. `migrate install +// order` looks the code up in the registries an application fills from its +// own init(), and an application the binary was not built with registers +// nothing - so every path through the installer past its first check went +// untested, and so did the seeding, the ledger and the uninstall against +// anything but a hand-built fixture. +// +// This lives in its own module, not behind a build tag in the main one. A +// tagged import there would still be resolved by `go mod tidy`, which +// considers every build tag and would go looking for +// github.com/go-admin-team/example-app-order on the network - a repository +// that does not exist, because the example is a directory inside this one. +// A separate module with replace directives is invisible to the main +// module's tidy, its build and its tests, and needs no go.work. +package main + +import ( + _ "github.com/go-admin-team/example-app-order/migration" + + "go-admin/cmd" +) + +func main() { + cmd.Execute() +} From 0bee8ec46c66e45bb542d3e1b3d4ca56980a3f7a Mon Sep 17 00:00:00 2001 From: zhangwenjian Date: Wed, 9 Sep 2026 16:40:15 +0800 Subject: [PATCH 3/9] =?UTF-8?q?ci=F0=9F=91=B7:=20run=20the=20end-to-end=20?= =?UTF-8?q?install=20on=20every=20push?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The separate module is invisible to `go test ./...` in the main module, which is the point, and also means nothing would ever run it. This step does. --- .github/workflows/go.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 195a2d80..da0cd440 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -109,6 +109,16 @@ jobs: - name: Build run: make build + # A separate module, so none of the steps above see it: the main module's + # go.mod, its build and its tests are all unaware of the example + # application. This is the only thing that exercises an application being + # installed at all - everything below it runs against an injected engine + # and a hand-built schema, and none of that can catch an application's + # init() reaching one registry and not the other. + - name: End-to-end install and uninstall + working-directory: test/e2e-apporder + run: go test ./... -count=1 + # Fails the build on the silent-failure classes listed in # tools/checksilent, one of which is the contract boundary: nothing under # common/ may import app/. A boundary that is only written down erodes; this From 25344aa572d30f53cdc9f3b6543acb3bd0ae92c9 Mon Sep 17 00:00:00 2001 From: zhangwenjian Date: Thu, 10 Sep 2026 21:30:03 +0800 Subject: [PATCH 4/9] =?UTF-8?q?feat=E2=9C=A8:=20let=20migrate=20status=20s?= =?UTF-8?q?ay=20what=20sys=5Fapp=20knows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The migration rows answer "did this run". They cannot answer "is this application installed", and the difference is not academic: an install that stopped partway leaves every migration reading applied and a row saying the install never finished. Until now nothing printed that row. [order] 1.0.0 failed at order-1793800000000 applied order-1793800000000 2026-09-10 21:02:07 The application list is the union of the two sources rather than either one. Reading it from sys_app alone would drop an application whose migrations ran under plain `migrate`, which records no row; reading it from the migration rows alone drops one whose code has been taken out of the binary, which is when somebody most wants to see it named - that one now gets a group of its own, empty, saying why. A database from before sys_app existed prints exactly what it printed before. `migrate status` has to keep working on a database that has not been migrated at all, which is when it is most wanted. Four degradations turn the new assertions red: dropping the sys_app-only applications from the listing, printing no summary, not narrowing sys_app by --app, and reporting an unfinished install as an installed one. --- cmd/migrate/install.go | 22 +++++++ cmd/migrate/server.go | 10 ++- cmd/migrate/status.go | 84 +++++++++++++++++++++++-- cmd/migrate/status_test.go | 124 +++++++++++++++++++++++++++++++++++-- 4 files changed, 230 insertions(+), 10 deletions(-) diff --git a/cmd/migrate/install.go b/cmd/migrate/install.go index 5eeadd00..0ce8e2e6 100644 --- a/cmd/migrate/install.go +++ b/cmd/migrate/install.go @@ -156,6 +156,28 @@ func loadApp(db *gorm.DB, code string) (adminmodels.SysApp, bool, error) { return adminmodels.SysApp{}, false, fmt.Errorf("reading sys_app for %q: %w", code, err) } +// loadApps reads every sys_app row, keyed by app code. +// +// A database that has never had 1786700007000 applied has no such table, and +// that is not an error here: `migrate status` has to keep working on a +// database that has not been migrated at all, which is when it is most wanted. +// A nil map is the honest answer there, and the caller prints what it always +// printed. +func loadApps(db *gorm.DB) (map[string]adminmodels.SysApp, error) { + if !db.Migrator().HasTable(&adminmodels.SysApp{}) { + return nil, nil + } + var rows []adminmodels.SysApp + if err := db.Find(&rows).Error; err != nil { + return nil, fmt.Errorf("reading sys_app: %w", err) + } + out := make(map[string]adminmodels.SysApp, len(rows)) + for _, r := range rows { + out[r.AppCode] = r + } + return out, nil +} + // pendingFor is the authoritative answer to "what is left to apply", and it // is recomputed every time rather than stored: what is registered in this // process, minus what sys_migration says has run. sys_app.failed_version is a diff --git a/cmd/migrate/server.go b/cmd/migrate/server.go index 93222f97..afc2768a 100644 --- a/cmd/migrate/server.go +++ b/cmd/migrate/server.go @@ -275,7 +275,15 @@ func runStatus() { fmt.Println(err) return } - if err = printStatus(os.Stdout, entries, appCode); err != nil { + // Which applications exist is a different question from which + // migrations ran, and an install that stopped partway is only + // visible in the answer to the first. + apps, err := loadApps(db) + if err != nil { + fmt.Println(err) + return + } + if err = printStatus(os.Stdout, entries, apps, appCode); err != nil { fmt.Println(err) } }, diff --git a/cmd/migrate/status.go b/cmd/migrate/status.go index 1d809ab5..9ff46d64 100644 --- a/cmd/migrate/status.go +++ b/cmd/migrate/status.go @@ -7,6 +7,7 @@ import ( "strings" "time" + adminmodels "go-admin/app/admin/models" "go-admin/cmd/migrate/migration" ) @@ -15,11 +16,24 @@ const applyTimeLayout = "2006-01-02 15:04:05" // printStatus lists every migration this binary knows about together with every // row already in sys_migration, grouped by app. // +// apps is what sys_app says about each of them, keyed by app code, and it +// answers a different question from the migration rows: an install that +// stopped partway leaves migrations that all read "applied" and a row that +// says the install never finished. A nil map is a database from before +// sys_app existed, and the listing is then exactly what it was. +// +// The app list is the union of the two. Reading it from sys_app alone would +// drop an application whose migrations ran under plain `migrate` and which +// therefore has no row; reading it from the migration rows alone drops one +// whose code has been taken out of the binary, which is when somebody most +// wants to see it named. +// // filter is an app code as typed on the command line; empty means every app. -func printStatus(w io.Writer, entries []migration.StatusEntry, filter string) error { +func printStatus(w io.Writer, entries []migration.StatusEntry, apps map[string]adminmodels.SysApp, filter string) error { entries = filterByApp(entries, filter) + apps = filterAppsByApp(apps, filter) - groups, order := groupByApp(entries) + groups, order := groupByApp(entries, apps) if len(order) == 0 { _, err := fmt.Fprintln(w, "no migrations registered and none recorded") return err @@ -34,7 +48,14 @@ func printStatus(w io.Writer, entries []migration.StatusEntry, filter string) er if i > 0 { fmt.Fprintln(w) } - fmt.Fprintf(w, "[%s]\n", app) + fmt.Fprintf(w, "[%s]%s\n", app, appSummary(apps, app)) + if len(groups[app]) == 0 { + // A row in sys_app and not one migration, recorded or + // registered. Its code is out of this binary and its migration + // records have been removed, and the row is all that is left to + // say it was ever here. + fmt.Fprintln(w, " no migrations registered in this binary and none recorded") + } for _, e := range groups[app] { state := "pending" switch { @@ -133,12 +154,21 @@ func filterByApp(entries []migration.StatusEntry, filter string) []migration.Sta // order to print them in: the framework first, then apps alphabetically. That // is also the order a full run executes them in, because version strings sort // as ASCII and the framework's are bare digits. -func groupByApp(entries []migration.StatusEntry) (map[string][]migration.StatusEntry, []string) { +func groupByApp(entries []migration.StatusEntry, apps map[string]adminmodels.SysApp) (map[string][]migration.StatusEntry, []string) { groups := make(map[string][]migration.StatusEntry) for _, e := range entries { app := migration.DisplayAppCode(e.AppCode) groups[app] = append(groups[app], e) } + // An application sys_app knows about and no migration mentions still gets + // a group, empty. That is the one case the migration rows cannot report + // at all. + for code := range apps { + app := migration.DisplayAppCode(code) + if _, ok := groups[app]; !ok { + groups[app] = nil + } + } order := make([]string, 0, len(groups)) for app := range groups { order = append(order, app) @@ -152,6 +182,52 @@ func groupByApp(entries []migration.StatusEntry) (map[string][]migration.StatusE return groups, order } +// appSummary is what sys_app says about one application, as a suffix for its +// group header. Empty when there is no row: an application whose migrations +// ran under plain `migrate` has none, and neither does any application on a +// database from before sys_app existed. +func appSummary(apps map[string]adminmodels.SysApp, display string) string { + code := display + if display == migration.FrameworkAppCode { + // The framework has no sys_app row and is not an application. + return "" + } + row, ok := apps[migration.NormalizeAppCode(code)] + if !ok { + return "" + } + switch row.Status { + case adminmodels.AppInstalled: + return fmt.Sprintf(" %s installed", row.Version) + case adminmodels.AppFailed: + if row.FailedVersion != "" { + return fmt.Sprintf(" %s failed at %s", row.Version, row.FailedVersion) + } + return fmt.Sprintf(" %s failed", row.Version) + case adminmodels.AppInstalling: + // Not "installing" as in "right now": nothing holds this state while + // it works. It is what is left when an attempt did not reach either + // end, and running the install again is what clears it. + return fmt.Sprintf(" %s did not finish installing", row.Version) + default: + return fmt.Sprintf(" %s status %d", row.Version, row.Status) + } +} + +// filterAppsByApp narrows the sys_app rows the same way filterByApp narrows +// the migrations, so --app names one application in both halves of the report. +func filterAppsByApp(apps map[string]adminmodels.SysApp, filter string) map[string]adminmodels.SysApp { + if filter == "" || apps == nil { + return apps + } + want := migration.AppFilter(filter) + out := make(map[string]adminmodels.SysApp, 1) + if row, ok := apps[want]; ok { + out[want] = row + } + return out +} + func formatApplyTime(t *time.Time) string { if t == nil { return "" diff --git a/cmd/migrate/status_test.go b/cmd/migrate/status_test.go index d35f1ea1..ff972662 100644 --- a/cmd/migrate/status_test.go +++ b/cmd/migrate/status_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + adminmodels "go-admin/app/admin/models" "go-admin/cmd/migrate/migration" ) @@ -29,7 +30,7 @@ func sampleEntries() []migration.StatusEntry { func TestPrintStatusGroupsByApp(t *testing.T) { var buf bytes.Buffer - if err := printStatus(&buf, sampleEntries(), ""); err != nil { + if err := printStatus(&buf, sampleEntries(), nil, ""); err != nil { t.Fatal(err) } got := buf.String() @@ -62,7 +63,7 @@ func TestPrintStatusMarksOrphanedRows(t *testing.T) { Version: "gone-1786800000000", AppCode: "gone", Applied: true, ApplyTime: at("2026-08-01 09:00:00"), }) var buf bytes.Buffer - if err := printStatus(&buf, entries, ""); err != nil { + if err := printStatus(&buf, entries, nil, ""); err != nil { t.Fatal(err) } got := buf.String() @@ -79,7 +80,7 @@ func TestPrintStatusMarksOrphanedRows(t *testing.T) { func TestPrintStatusFiltersByApp(t *testing.T) { var buf bytes.Buffer - if err := printStatus(&buf, sampleEntries(), "crm"); err != nil { + if err := printStatus(&buf, sampleEntries(), nil, "crm"); err != nil { t.Fatal(err) } got := buf.String() @@ -94,7 +95,7 @@ func TestPrintStatusFiltersByApp(t *testing.T) { // status prints [core]; --app core has to mean the same thing. func TestPrintStatusAppCoreSelectsTheFramework(t *testing.T) { var buf bytes.Buffer - if err := printStatus(&buf, sampleEntries(), migration.FrameworkAppCode); err != nil { + if err := printStatus(&buf, sampleEntries(), nil, migration.FrameworkAppCode); err != nil { t.Fatal(err) } got := buf.String() @@ -108,7 +109,7 @@ func TestPrintStatusAppCoreSelectsTheFramework(t *testing.T) { func TestPrintStatusOnAnEmptyRegistry(t *testing.T) { var buf bytes.Buffer - if err := printStatus(&buf, nil, ""); err != nil { + if err := printStatus(&buf, nil, nil, ""); err != nil { t.Fatal(err) } if !strings.Contains(buf.String(), "no migrations registered and none recorded") { @@ -168,3 +169,116 @@ func TestPrintPendingFiltersByApp(t *testing.T) { t.Errorf("output = %s", got) } } + +func appRows(rows ...adminmodels.SysApp) map[string]adminmodels.SysApp { + out := make(map[string]adminmodels.SysApp, len(rows)) + for _, r := range rows { + out[r.AppCode] = r + } + return out +} + +// The migration rows say every one of an application's migrations ran. Only +// sys_app can say the install that ran them never finished. +func TestPrintStatusShowsWhatSysAppSays(t *testing.T) { + apps := appRows( + adminmodels.SysApp{AppCode: "crm", Version: "1.2.0", Status: adminmodels.AppInstalled}, + ) + var buf bytes.Buffer + if err := printStatus(&buf, sampleEntries(), apps, ""); err != nil { + t.Fatal(err) + } + got := buf.String() + if !strings.Contains(got, "[crm] 1.2.0 installed") { + t.Errorf("the header does not carry what sys_app says:\n%s", got) + } + // The framework is not an application and has no row. + if strings.Contains(got, "[core] ") { + t.Errorf("the framework was given an application summary:\n%s", got) + } +} + +func TestPrintStatusNamesAFailedInstallAndWhereItStopped(t *testing.T) { + apps := appRows(adminmodels.SysApp{ + AppCode: "crm", Version: "1.2.0", Status: adminmodels.AppFailed, + FailedVersion: "crm-1786800002000", + }) + var buf bytes.Buffer + if err := printStatus(&buf, sampleEntries(), apps, ""); err != nil { + t.Fatal(err) + } + if !strings.Contains(buf.String(), "[crm] 1.2.0 failed at crm-1786800002000") { + t.Errorf("output:\n%s", buf.String()) + } +} + +// A process killed partway leaves this, and nothing else records it. +func TestPrintStatusNamesAnInstallThatDidNotFinish(t *testing.T) { + apps := appRows(adminmodels.SysApp{AppCode: "crm", Version: "1.2.0", Status: adminmodels.AppInstalling}) + var buf bytes.Buffer + if err := printStatus(&buf, sampleEntries(), apps, ""); err != nil { + t.Fatal(err) + } + if !strings.Contains(buf.String(), "did not finish installing") { + t.Errorf("output:\n%s", buf.String()) + } +} + +// An application whose code was taken out of the binary after its migration +// records were removed has nothing left but a sys_app row. The migration rows +// cannot report it at all. +func TestPrintStatusListsAnAppWithNoMigrationsAtAll(t *testing.T) { + apps := appRows(adminmodels.SysApp{AppCode: "billing", Version: "3.0.0", Status: adminmodels.AppInstalled}) + var buf bytes.Buffer + if err := printStatus(&buf, sampleEntries(), apps, ""); err != nil { + t.Fatal(err) + } + got := buf.String() + if !strings.Contains(got, "[billing] 3.0.0 installed") { + t.Errorf("an application only sys_app knows about was not listed:\n%s", got) + } + if !strings.Contains(got, "no migrations registered in this binary and none recorded") { + t.Errorf("the empty group needs to say why it is empty:\n%s", got) + } + if !strings.Contains(got, "across 3 app(s)") { + t.Errorf("the count does not include it:\n%s", got) + } +} + +// --app narrows both halves, or the report names one application and +// summarises another. +func TestPrintStatusFilterAppliesToSysAppToo(t *testing.T) { + apps := appRows( + adminmodels.SysApp{AppCode: "crm", Version: "1.2.0", Status: adminmodels.AppInstalled}, + adminmodels.SysApp{AppCode: "billing", Version: "3.0.0", Status: adminmodels.AppInstalled}, + ) + var buf bytes.Buffer + if err := printStatus(&buf, sampleEntries(), apps, "crm"); err != nil { + t.Fatal(err) + } + got := buf.String() + if strings.Contains(got, "billing") { + t.Errorf("--app crm listed billing:\n%s", got) + } + if !strings.Contains(got, "across 1 app(s)") { + t.Errorf("output:\n%s", got) + } +} + +// A database from before sys_app existed. The listing is what it always was, +// rather than an error or an empty report. +func TestPrintStatusWithoutSysApp(t *testing.T) { + var withRows, without bytes.Buffer + if err := printStatus(&without, sampleEntries(), nil, ""); err != nil { + t.Fatal(err) + } + if err := printStatus(&withRows, sampleEntries(), map[string]adminmodels.SysApp{}, ""); err != nil { + t.Fatal(err) + } + if without.String() != withRows.String() { + t.Errorf("an empty sys_app and no sys_app print differently:\n%s\n---\n%s", without.String(), withRows.String()) + } + if !strings.Contains(without.String(), "[crm]\n") { + t.Errorf("the header carries a summary it has no row for:\n%s", without.String()) + } +} From 1b9868b72bf737a2f0a9fd9893a5fdba79dd61c0 Mon Sep 17 00:00:00 2001 From: zhangwenjian Date: Thu, 10 Sep 2026 21:30:29 +0800 Subject: [PATCH 5/9] =?UTF-8?q?feat=E2=9C=A8:=20refuse=20an=20install=20wh?= =?UTF-8?q?ose=20dependencies=20are=20not=20installed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An application's manifest can name others it needs. Until now the list was stored and never read. It is checked, not satisfied. Installing the dependencies too would make "install this application" mean "and everything it happens to name, and everything those name" - a blast radius the operator did not ask for and cannot see beforehand. What they get is the list and the order to do it in. A dependency whose own install failed, or never finished, is not a dependency that is there. The message says which, because the two send you to different places: one to install it, the other to look at why it did not take. The check runs before anything is written, so a refusal cannot cost the operator the row that told them what they had. Separately, a cycle anywhere in the registered manifests is refused, whether or not the application being installed is in it. A cycle between two others is still an authoring mistake, and the day somebody installs into it - with an error naming two applications they did not ask for - is the worse time to find out. The error is the cycle rather than the walk that reached it, and the walk's order is sorted, so the same set of manifests always reports the same one. Requires naming an application that is not registered is not a cycle; it is the database's answer to give, at the time it matters. Six degradations turn the new assertions red: accepting any dependency, accepting a row regardless of its status, returning no cycle, not trimming the reported path to the cycle itself, and running the check after the row has already been written - the last of which was rebuilt after the first attempt at it deleted the check rather than moving it, and so went red on the wrong assertion. --- cmd/migrate/install.go | 125 +++++++++++++++++++++++++ cmd/migrate/install_test.go | 176 ++++++++++++++++++++++++++++++++++-- cmd/migrate/server.go | 9 ++ 3 files changed, 304 insertions(+), 6 deletions(-) diff --git a/cmd/migrate/install.go b/cmd/migrate/install.go index 0ce8e2e6..87279fb0 100644 --- a/cmd/migrate/install.go +++ b/cmd/migrate/install.go @@ -99,6 +99,10 @@ func install(db *gorm.DB, eng engine, m app.Manifest) (installReport, error) { sameVersion = cmp == 0 } + if err := requiresInstalled(db, m); err != nil { + return rep, err + } + pending, err := pendingFor(eng, code) if err != nil { return rep, err @@ -156,6 +160,127 @@ func loadApp(db *gorm.DB, code string) (adminmodels.SysApp, bool, error) { return adminmodels.SysApp{}, false, fmt.Errorf("reading sys_app for %q: %w", code, err) } +// requiresInstalled refuses an install whose declared dependencies are not +// installed, and names the ones that are not. +// +// It does not install them. "Install this application" would otherwise mean +// "and everything it happens to name, and everything those name" - a blast +// radius the operator did not ask for and cannot see before it happens. What +// they get instead is a list and the order to do it in. +// +// An unfinished or failed dependency counts as missing, and says which it is: +// "not installed" sends someone to install it, "did not finish" sends them to +// look at why. +func requiresInstalled(db *gorm.DB, m app.Manifest) error { + if len(m.Requires) == 0 { + return nil + } + apps, err := loadApps(db) + if err != nil { + return err + } + var ( + why []string + what []string + ) + for _, req := range m.Requires { + want := migration.NormalizeAppCode(req) + if want == "" { + continue + } + row, ok := apps[want] + switch { + case !ok: + why, what = append(why, want+" (not installed)"), append(what, want) + case row.Status == adminmodels.AppFailed: + why, what = append(why, want+" (its install failed)"), append(what, want) + case row.Status != adminmodels.AppInstalled: + why, what = append(why, want+" (its install did not finish)"), append(what, want) + } + } + if len(why) > 0 { + return fmt.Errorf("%s requires %s; install %s first", + migration.NormalizeAppCode(m.Code), strings.Join(why, ", "), strings.Join(what, " and ")) + } + return nil +} + +// refuseOnDependencyCycle reports a cycle anywhere in the registered +// manifests, whether or not the application being installed is part of it. +// +// Over the whole set rather than one application's closure, because a cycle +// between two applications neither of which is the one being installed is +// still an authoring mistake, and finding it the day somebody happens to +// install into it - with an error naming two applications they did not ask +// for - is the worse time to find it. +// +// Requires naming an application that is not registered is not a cycle and +// not reported here; that is requiresInstalled's answer to give, against the +// database, at the time it matters. +func refuseOnDependencyCycle(manifests map[string]app.Manifest) error { + const ( + white = 0 // not visited + grey = 1 // on the current path + black = 2 // finished + ) + colour := make(map[string]int, len(manifests)) + + codes := make([]string, 0, len(manifests)) + for code := range manifests { + codes = append(codes, code) + } + // Sorted, so the same set of manifests always reports the same cycle + // rather than whichever one the map happened to hand over first. + sort.Strings(codes) + + var path []string + var walk func(code string) error + walk = func(code string) error { + switch colour[code] { + case grey: + // Trim the path to where this code first appears, so the error + // is the cycle and not the walk that reached it. + for i, c := range path { + if c == code { + return fmt.Errorf("the declared dependencies form a cycle: %s", + strings.Join(append(append([]string{}, path[i:]...), code), " -> ")) + } + } + return fmt.Errorf("the declared dependencies form a cycle at %s", code) + case black: + return nil + } + colour[code] = grey + path = append(path, code) + m := manifests[code] + reqs := make([]string, 0, len(m.Requires)) + for _, r := range m.Requires { + if n := migration.NormalizeAppCode(r); n != "" { + reqs = append(reqs, n) + } + } + sort.Strings(reqs) + for _, r := range reqs { + if _, registered := manifests[r]; !registered { + continue + } + if err := walk(r); err != nil { + return err + } + } + path = path[:len(path)-1] + colour[code] = black + return nil + } + + for _, code := range codes { + if err := walk(code); err != nil { + return err + } + } + return nil +} + // loadApps reads every sys_app row, keyed by app code. // // A database that has never had 1786700007000 applied has no such table, and diff --git a/cmd/migrate/install_test.go b/cmd/migrate/install_test.go index af7c64d8..12a6fd0f 100644 --- a/cmd/migrate/install_test.go +++ b/cmd/migrate/install_test.go @@ -66,9 +66,22 @@ func orderManifest(version string) app.Manifest { Version: version, Description: "order management", Author: "go-admin", - Requires: []string{"crm"}, - Pricing: "free", - License: "MIT", + // No dependency by default: these tests are about installing, and a + // declared requirement would make every one of them set up a second + // application first. requiresInstalled has its own tests below. + Requires: nil, + Pricing: "free", + License: "MIT", + } +} + +// installedApp writes the sys_app row a satisfied dependency looks like. +func installedApp(t *testing.T, db *gorm.DB, code string) { + t.Helper() + if err := db.Create(&adminmodels.SysApp{ + AppCode: code, Name: code, Version: "1.0.0", Status: adminmodels.AppInstalled, + }).Error; err != nil { + t.Fatalf("seeding %q as installed: %v", code, err) } } @@ -119,9 +132,6 @@ func TestInstallRecordsAFirstInstall(t *testing.T) { if row.Name != "Orders" || row.Author != "go-admin" || row.Description != "order management" { t.Errorf("descriptive columns not copied from the manifest: %+v", row) } - if row.Requires != "crm" { - t.Errorf("requires = %q, want the manifest's list as CSV", row.Requires) - } if row.Pricing != "free" || row.License != "MIT" { t.Errorf("the reserved fields were not carried through: %+v", row) } @@ -466,3 +476,157 @@ func TestInstallNormalizesTheAppCode(t *testing.T) { t.Errorf("applied = %v; the normalized code has to match what Status reports", rep.Applied) } } + +// The manifest's dependency list is stored as it was declared, in the CSV +// shape sys_app.requires carries. +func TestInstallStoresTheDeclaredRequires(t *testing.T) { + db := newInstallDB(t) + installedApp(t, db, "crm") + installedApp(t, db, "billing") + eng := &fakeEngine{entries: []migration.StatusEntry{ + {Version: "order-1786800001000", AppCode: "order", Registered: true}, + }} + m := orderManifest("1.0.0") + m.Requires = []string{"crm", "billing"} + + if _, err := install(db, eng, m); err != nil { + t.Fatalf("install: %v", err) + } + if row := loadRow(t, db, "order"); row.Requires != "crm,billing" { + t.Errorf("requires = %q, want the manifest's list as CSV", row.Requires) + } +} + +// An application is not installed for you because something else names it. +// "Install this" would otherwise mean "and everything it happens to name, and +// everything those name". +func TestInstallRefusesWhenADependencyIsNotInstalled(t *testing.T) { + db := newInstallDB(t) + eng := &fakeEngine{entries: []migration.StatusEntry{ + {Version: "order-1786800001000", AppCode: "order", Registered: true}, + }} + m := orderManifest("1.0.0") + m.Requires = []string{"crm"} + + _, err := install(db, eng, m) + if err == nil { + t.Fatal("an application with an uninstalled dependency was installed") + } + if !strings.Contains(err.Error(), "crm") || !strings.Contains(err.Error(), "not installed") { + t.Errorf("error = %q, it has to name what is missing and why", err) + } + if len(eng.calls) != 0 { + t.Errorf("the engine ran anyway: %v", eng.calls) + } + // Refused before phase A, so a refusal leaves nothing behind. + var n int64 + db.Model(&adminmodels.SysApp{}).Where("app_code = ?", "order").Count(&n) + if n != 0 { + t.Errorf("a refused install wrote %d sys_app row(s)", n) + } +} + +// A dependency whose own install failed or never finished is not a dependency +// that is there, and the two say which they are - one sends you to install it, +// the other to look at why. +func TestInstallRefusesWhenADependencyIsNotFinished(t *testing.T) { + for _, tc := range []struct { + name string + status int + want string + }{ + {"failed", adminmodels.AppFailed, "its install failed"}, + {"installing", adminmodels.AppInstalling, "did not finish"}, + } { + t.Run(tc.name, func(t *testing.T) { + db := newInstallDB(t) + if err := db.Create(&adminmodels.SysApp{ + AppCode: "crm", Name: "crm", Version: "1.0.0", Status: tc.status, + }).Error; err != nil { + t.Fatal(err) + } + m := orderManifest("1.0.0") + m.Requires = []string{"crm"} + _, err := install(db, &fakeEngine{}, m) + if err == nil { + t.Fatal("the dependency was accepted") + } + if !strings.Contains(err.Error(), tc.want) { + t.Errorf("error = %q, want it to say %q", err, tc.want) + } + }) + } +} + +func TestInstallAcceptsASatisfiedDependency(t *testing.T) { + db := newInstallDB(t) + installedApp(t, db, "crm") + eng := &fakeEngine{entries: []migration.StatusEntry{ + {Version: "order-1786800001000", AppCode: "order", Registered: true}, + }} + m := orderManifest("1.0.0") + m.Requires = []string{"crm"} + + if _, err := install(db, eng, m); err != nil { + t.Fatalf("install: %v", err) + } + if row := loadRow(t, db, "order"); row.Status != adminmodels.AppInstalled { + t.Errorf("status = %d, want installed", row.Status) + } +} + +func TestDependencyCycleIsRefused(t *testing.T) { + manifests := map[string]app.Manifest{ + "a": {Code: "a", Requires: []string{"b"}}, + "b": {Code: "b", Requires: []string{"c"}}, + "c": {Code: "c", Requires: []string{"a"}}, + } + err := refuseOnDependencyCycle(manifests) + if err == nil { + t.Fatal("a cycle was accepted") + } + // The error is the cycle, not the walk that reached it. + if !strings.Contains(err.Error(), "a -> b -> c -> a") { + t.Errorf("error = %q", err) + } +} + +// A cycle between two applications neither of which is being installed is +// still an authoring mistake, and the day somebody installs into it is the +// worse time to find out. +func TestDependencyCycleIsRefusedEvenAwayFromTheTarget(t *testing.T) { + manifests := map[string]app.Manifest{ + "order": {Code: "order"}, + "x": {Code: "x", Requires: []string{"y"}}, + "y": {Code: "y", Requires: []string{"x"}}, + } + if err := refuseOnDependencyCycle(manifests); err == nil { + t.Fatal("a cycle away from the target was accepted") + } +} + +func TestDependencyGraphWithoutACycle(t *testing.T) { + manifests := map[string]app.Manifest{ + "a": {Code: "a", Requires: []string{"b", "c"}}, + "b": {Code: "b", Requires: []string{"c"}}, + "c": {Code: "c"}, + // Naming something that is not registered is not a cycle. Whether it + // is installed is a question for the database, at install time. + "d": {Code: "d", Requires: []string{"nowhere"}}, + } + if err := refuseOnDependencyCycle(manifests); err != nil { + t.Errorf("a graph with no cycle was refused: %v", err) + } +} + +// An application that names itself. +func TestDependencyCycleOfOne(t *testing.T) { + manifests := map[string]app.Manifest{"a": {Code: "a", Requires: []string{"a"}}} + err := refuseOnDependencyCycle(manifests) + if err == nil { + t.Fatal("an application requiring itself was accepted") + } + if !strings.Contains(err.Error(), "a -> a") { + t.Errorf("error = %q", err) + } +} diff --git a/cmd/migrate/server.go b/cmd/migrate/server.go index afc2768a..88ad9a6a 100644 --- a/cmd/migrate/server.go +++ b/cmd/migrate/server.go @@ -15,6 +15,7 @@ import ( "gorm.io/gorm" "github.com/go-admin-team/go-admin-core/v2/config/source/file" + "github.com/go-admin-team/go-admin-core/v2/sdk/contract/app" "github.com/spf13/cobra" "github.com/go-admin-team/go-admin-core/v2/sdk/config" @@ -305,6 +306,14 @@ func runInstall(code string) { exitOnError(os.Stderr, err) return } + // Over every registered manifest, not just this one's closure: a + // cycle between two other applications is still an authoring + // mistake, and the day somebody installs into it is the worse + // time to find out. + if err := refuseOnDependencyCycle(app.Snapshot()); err != nil { + exitOnError(os.Stderr, err) + return + } rep, err := install(db, migration.Migrate, m) if err != nil { exitOnError(os.Stderr, err) From 309b400bc0c60da048d12df15b44489a0bb44d99 Mon Sep 17 00:00:00 2001 From: zhangwenjian Date: Fri, 11 Sep 2026 08:20:56 +0800 Subject: [PATCH 6/9] =?UTF-8?q?refactor=E2=99=BB=EF=B8=8F:=20take=20one=20?= =?UTF-8?q?registry=20snapshot,=20and=20one=20route=20into=20sys=5Fapp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cleanup from a review pass over this branch. No behaviour changes except the two noted below. runInstall took app.Snapshot() twice, once inside manifestFor and once for the cycle check. Snapshot is a deep copy of the registry, and worse than the copying, the two calls could in principle disagree - the set the cycle check validated was not provably the set the manifest came from. One snapshot, passed to both. appSummary converted a display code back to a stored one with NormalizeAppCode, which is not that inverse: it leaves "core" as "core", so the framework needed a branch of its own to stay out of the listing. AppFilter is the documented inverse and maps it to the empty string, which is not a code any row is filed under - so the branch goes, and the function now matches filterAppsByApp twenty lines below it, which was already using AppFilter. That branch only half-covered what it guarded: a sys_app row carrying an empty or reserved app_code was still merged into the framework's group by groupByApp, with only its summary suppressed. loadApps now drops such rows, which is the one place that settles it for every reader of the map. requiresInstalled built two parallel slices with a tuple assignment repeated in three branches; it now picks a reason and appends once. Its last arm was a catch-all on "not installed", so a status constant added later would have been described as "did not finish" - a sentence that would be wrong for whatever reason the constant was added. Unrecognised values now say so. It also takes the normalised code the caller already has rather than computing it a third time. refuseOnDependencyCycle sorted each manifest's Requires before walking them. Requires is a slice and already has a fixed order, so the sort bought no determinism - that comes from the sorted outer loop, which walks a map - and only made a reported cycle harder to line up against the manifest that caused it. The filtering pass that went with it is covered by the registration check underneath. The cycle path is trimmed with slices.Index, which also removes a fallback return that the grey/path invariant made unreachable. --- cmd/migrate/install.go | 83 +++++++++++++++++++++++++----------------- cmd/migrate/server.go | 5 ++- cmd/migrate/status.go | 13 +++---- 3 files changed, 59 insertions(+), 42 deletions(-) diff --git a/cmd/migrate/install.go b/cmd/migrate/install.go index 87279fb0..eebc136f 100644 --- a/cmd/migrate/install.go +++ b/cmd/migrate/install.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io" + "slices" "sort" "strings" "time" @@ -99,7 +100,7 @@ func install(db *gorm.DB, eng engine, m app.Manifest) (installReport, error) { sameVersion = cmp == 0 } - if err := requiresInstalled(db, m); err != nil { + if err := requiresInstalled(db, code, m); err != nil { return rep, err } @@ -171,7 +172,7 @@ func loadApp(db *gorm.DB, code string) (adminmodels.SysApp, bool, error) { // An unfinished or failed dependency counts as missing, and says which it is: // "not installed" sends someone to install it, "did not finish" sends them to // look at why. -func requiresInstalled(db *gorm.DB, m app.Manifest) error { +func requiresInstalled(db *gorm.DB, code string, m app.Manifest) error { if len(m.Requires) == 0 { return nil } @@ -179,28 +180,35 @@ func requiresInstalled(db *gorm.DB, m app.Manifest) error { if err != nil { return err } - var ( - why []string - what []string - ) + var why, what []string for _, req := range m.Requires { want := migration.NormalizeAppCode(req) if want == "" { continue } - row, ok := apps[want] - switch { + var reason string + switch row, ok := apps[want]; { case !ok: - why, what = append(why, want+" (not installed)"), append(what, want) + reason = "not installed" case row.Status == adminmodels.AppFailed: - why, what = append(why, want+" (its install failed)"), append(what, want) + reason = "its install failed" + case row.Status == adminmodels.AppInstalling: + reason = "its install did not finish" case row.Status != adminmodels.AppInstalled: - why, what = append(why, want+" (its install did not finish)"), append(what, want) + // A status this binary has no name for. Saying so beats the + // catch-all this used to be, which read any future value as + // "did not finish" - a sentence that would be wrong for + // whatever reason the value was added. + reason = fmt.Sprintf("its status is %d, which this binary does not recognise", row.Status) + default: + continue } + why = append(why, want+" ("+reason+")") + what = append(what, want) } if len(why) > 0 { return fmt.Errorf("%s requires %s; install %s first", - migration.NormalizeAppCode(m.Code), strings.Join(why, ", "), strings.Join(what, " and ")) + code, strings.Join(why, ", "), strings.Join(what, " and ")) } return nil } @@ -239,32 +247,30 @@ func refuseOnDependencyCycle(manifests map[string]app.Manifest) error { switch colour[code] { case grey: // Trim the path to where this code first appears, so the error - // is the cycle and not the walk that reached it. - for i, c := range path { - if c == code { - return fmt.Errorf("the declared dependencies form a cycle: %s", - strings.Join(append(append([]string{}, path[i:]...), code), " -> ")) - } - } - return fmt.Errorf("the declared dependencies form a cycle at %s", code) + // is the cycle and not the walk that reached it. grey is only + // ever set together with the append below, and cleared together + // with the matching trim, so the code is always on the path. + cycle := append(slices.Clone(path[slices.Index(path, code):]), code) + return fmt.Errorf("the declared dependencies form a cycle: %s", + strings.Join(cycle, " -> ")) case black: return nil } colour[code] = grey path = append(path, code) - m := manifests[code] - reqs := make([]string, 0, len(m.Requires)) - for _, r := range m.Requires { - if n := migration.NormalizeAppCode(r); n != "" { - reqs = append(reqs, n) - } - } - sort.Strings(reqs) - for _, r := range reqs { - if _, registered := manifests[r]; !registered { + // In the order the manifest declared them, which is a fixed order + // already - sorting here would only make the reported cycle harder + // to line up against the manifest that caused it. The determinism + // that matters comes from the sorted outer loop, because that one + // walks a map. + for _, r := range manifests[code].Requires { + n := migration.NormalizeAppCode(r) + if _, registered := manifests[n]; !registered { + // Including the empty string, which Register rejects, so + // no manifest is filed under it. continue } - if err := walk(r); err != nil { + if err := walk(n); err != nil { return err } } @@ -298,6 +304,15 @@ func loadApps(db *gorm.DB) (map[string]adminmodels.SysApp, error) { } out := make(map[string]adminmodels.SysApp, len(rows)) for _, r := range rows { + // An application cannot be filed under the empty code or the one + // reserved for the framework - Register rejects both - so a row + // carrying either was not written by an install. Dropping it here + // is the one place that settles it: every reader of this map would + // otherwise have to decide separately, and `migrate status` would + // merge such a row into the framework's own group. + if r.AppCode == "" || r.AppCode == migration.FrameworkAppCode { + continue + } out[r.AppCode] = r } return out, nil @@ -431,9 +446,11 @@ func reportInstall(w io.Writer, rep installReport) { // A code nothing registered is an error naming what is registered, for the // same reason exitUnlessAppRegistered exists: the alternative is telling an // operator who typed `install ordr` that there was nothing to do. -func manifestFor(code string) (app.Manifest, error) { +// Takes the snapshot rather than reading it, so this lookup and the caller's +// cycle check see the same set. Two calls to app.Snapshot() would also be two +// deep copies of the registry for one install. +func manifestFor(all map[string]app.Manifest, code string) (app.Manifest, error) { want := migration.NormalizeAppCode(code) - all := app.Snapshot() if m, ok := all[want]; ok { return m, nil } diff --git a/cmd/migrate/server.go b/cmd/migrate/server.go index 88ad9a6a..a4e60858 100644 --- a/cmd/migrate/server.go +++ b/cmd/migrate/server.go @@ -301,7 +301,8 @@ func runInstall(code string) { exitOnError(os.Stderr, err) return } - m, err := manifestFor(code) + registered := app.Snapshot() + m, err := manifestFor(registered, code) if err != nil { exitOnError(os.Stderr, err) return @@ -310,7 +311,7 @@ func runInstall(code string) { // cycle between two other applications is still an authoring // mistake, and the day somebody installs into it is the worse // time to find out. - if err := refuseOnDependencyCycle(app.Snapshot()); err != nil { + if err := refuseOnDependencyCycle(registered); err != nil { exitOnError(os.Stderr, err) return } diff --git a/cmd/migrate/status.go b/cmd/migrate/status.go index 9ff46d64..c8a21409 100644 --- a/cmd/migrate/status.go +++ b/cmd/migrate/status.go @@ -187,12 +187,11 @@ func groupByApp(entries []migration.StatusEntry, apps map[string]adminmodels.Sys // ran under plain `migrate` has none, and neither does any application on a // database from before sys_app existed. func appSummary(apps map[string]adminmodels.SysApp, display string) string { - code := display - if display == migration.FrameworkAppCode { - // The framework has no sys_app row and is not an application. - return "" - } - row, ok := apps[migration.NormalizeAppCode(code)] + // AppFilter, not NormalizeAppCode: this takes a display code back to the + // stored one, and only AppFilter is that inverse. It maps the framework + // to the empty string, which loadApps never files a row under, so the + // framework needs no branch of its own here. + row, ok := apps[migration.AppFilter(display)] if !ok { return "" } @@ -217,7 +216,7 @@ func appSummary(apps map[string]adminmodels.SysApp, display string) string { // filterAppsByApp narrows the sys_app rows the same way filterByApp narrows // the migrations, so --app names one application in both halves of the report. func filterAppsByApp(apps map[string]adminmodels.SysApp, filter string) map[string]adminmodels.SysApp { - if filter == "" || apps == nil { + if filter == "" { return apps } want := migration.AppFilter(filter) From 37aece9791458a7a6b3a88a51756340066ea5d97 Mon Sep 17 00:00:00 2001 From: zhangwenjian Date: Fri, 11 Sep 2026 08:21:35 +0800 Subject: [PATCH 7/9] =?UTF-8?q?test=E2=9C=85:=20share=20one=20sys=5Fapp=20?= =?UTF-8?q?fixture,=20and=20count=20through=20the=20helper=20that=20checks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two tests in this package each wrote out what an installed sys_app row looks like, field for field, and a third inlined the same Create with a different status. One appRow(t, db, code, status) now covers all three, so a new NOT NULL column on SysApp is one edit rather than three. One assertion counted with a bare db.Model(...).Count(&n) and dropped the error that call returns. A failing query leaves n at zero, which is exactly what that assertion wanted to see - so the test would have passed on a broken query. The package already had a count helper that fails on the error, and this now uses it. Also a cycle reached from outside itself. The existing case walks straight into its own cycle from the first code, so the path trimming had nothing to do and replacing it with the untrimmed path left the test green - the trimming was never covered. With a requiring b, b requiring c and c requiring b, the untrimmed report names a as part of a cycle it is not in, and the test goes red. --- cmd/migrate/install_test.go | 47 ++++++++++++++++++++++++----------- cmd/migrate/uninstall_test.go | 6 +---- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/cmd/migrate/install_test.go b/cmd/migrate/install_test.go index 12a6fd0f..44ff8dbd 100644 --- a/cmd/migrate/install_test.go +++ b/cmd/migrate/install_test.go @@ -75,13 +75,14 @@ func orderManifest(version string) app.Manifest { } } -// installedApp writes the sys_app row a satisfied dependency looks like. -func installedApp(t *testing.T, db *gorm.DB, code string) { +// appRow writes one sys_app row: what another application looks like to the +// installer, in whichever state the caller is testing against. +func appRow(t *testing.T, db *gorm.DB, code string, status int) { t.Helper() if err := db.Create(&adminmodels.SysApp{ - AppCode: code, Name: code, Version: "1.0.0", Status: adminmodels.AppInstalled, + AppCode: code, Name: code, Version: "1.0.0", Status: status, }).Error; err != nil { - t.Fatalf("seeding %q as installed: %v", code, err) + t.Fatalf("seeding %q with status %d: %v", code, status, err) } } @@ -481,8 +482,8 @@ func TestInstallNormalizesTheAppCode(t *testing.T) { // shape sys_app.requires carries. func TestInstallStoresTheDeclaredRequires(t *testing.T) { db := newInstallDB(t) - installedApp(t, db, "crm") - installedApp(t, db, "billing") + appRow(t, db, "crm", adminmodels.AppInstalled) + appRow(t, db, "billing", adminmodels.AppInstalled) eng := &fakeEngine{entries: []migration.StatusEntry{ {Version: "order-1786800001000", AppCode: "order", Registered: true}, }} @@ -519,9 +520,7 @@ func TestInstallRefusesWhenADependencyIsNotInstalled(t *testing.T) { t.Errorf("the engine ran anyway: %v", eng.calls) } // Refused before phase A, so a refusal leaves nothing behind. - var n int64 - db.Model(&adminmodels.SysApp{}).Where("app_code = ?", "order").Count(&n) - if n != 0 { + if n := count(t, db, "sys_app", "app_code = ?", "order"); n != 0 { t.Errorf("a refused install wrote %d sys_app row(s)", n) } } @@ -540,11 +539,7 @@ func TestInstallRefusesWhenADependencyIsNotFinished(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { db := newInstallDB(t) - if err := db.Create(&adminmodels.SysApp{ - AppCode: "crm", Name: "crm", Version: "1.0.0", Status: tc.status, - }).Error; err != nil { - t.Fatal(err) - } + appRow(t, db, "crm", tc.status) m := orderManifest("1.0.0") m.Requires = []string{"crm"} _, err := install(db, &fakeEngine{}, m) @@ -560,7 +555,7 @@ func TestInstallRefusesWhenADependencyIsNotFinished(t *testing.T) { func TestInstallAcceptsASatisfiedDependency(t *testing.T) { db := newInstallDB(t) - installedApp(t, db, "crm") + appRow(t, db, "crm", adminmodels.AppInstalled) eng := &fakeEngine{entries: []migration.StatusEntry{ {Version: "order-1786800001000", AppCode: "order", Registered: true}, }} @@ -630,3 +625,25 @@ func TestDependencyCycleOfOne(t *testing.T) { t.Errorf("error = %q", err) } } + +// The cycle reached from outside it. a is not part of anything circular; b +// and c are. Reporting the walk instead of the cycle would name a as well, +// and sending somebody to look at an application that is not involved is +// the whole reason the path is trimmed. +func TestDependencyCycleReportsOnlyTheCycleItReached(t *testing.T) { + manifests := map[string]app.Manifest{ + "a": {Code: "a", Requires: []string{"b"}}, + "b": {Code: "b", Requires: []string{"c"}}, + "c": {Code: "c", Requires: []string{"b"}}, + } + err := refuseOnDependencyCycle(manifests) + if err == nil { + t.Fatal("a cycle was accepted") + } + if !strings.Contains(err.Error(), "b -> c -> b") { + t.Errorf("error = %q, want just the cycle", err) + } + if strings.Contains(err.Error(), "a ->") { + t.Errorf("the walk that reached the cycle was reported as part of it: %q", err) + } +} diff --git a/cmd/migrate/uninstall_test.go b/cmd/migrate/uninstall_test.go index 2ae79df4..76694a3a 100644 --- a/cmd/migrate/uninstall_test.go +++ b/cmd/migrate/uninstall_test.go @@ -87,11 +87,7 @@ func seedApp(t *testing.T, db *gorm.DB, code string) { }).Error; err != nil { t.Fatalf("recording the migration for %q: %v", code, err) } - if err := db.Create(&adminmodels.SysApp{ - AppCode: code, Name: code, Version: "1.0.0", Status: adminmodels.AppInstalled, - }).Error; err != nil { - t.Fatalf("recording sys_app for %q: %v", code, err) - } + appRow(t, db, code, adminmodels.AppInstalled) } func count(t *testing.T, db *gorm.DB, table, where string, args ...any) int64 { From 1f56b956d22fda256e826fd0b74e283cc098e37c Mon Sep 17 00:00:00 2001 From: zhangwenjian Date: Fri, 11 Sep 2026 08:21:36 +0800 Subject: [PATCH 8/9] =?UTF-8?q?test=E2=9C=85:=20build=20the=20end-to-end?= =?UTF-8?q?=20binary=20once,=20and=20assert=20from=20one=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three tests each called newEnv, and newEnv built the binary, so the same binary was linked three times - about 17 seconds of the run, measured. A binary is read-only and there is nothing to isolate between tests; each test still gets its own directory and its own database. The package now builds it on the first test that needs one and removes it in TestMain. The suite goes from 39 seconds to 9. The three assertion blocks listed the same six queries, two or three times each, differing only in the counts expected - so renaming a table meant finding three places. They now share one list, with a flag for the uninstall's "all of them at zero". The reinstall check gets stronger on the way past: it was three of the six and is now all six. The per-call sql.Open in count and exec stays. It looks like waste and is not: the binary under test writes the same file, and a connection held open across a run of it is a second writer for nothing. The shared part is factored out; the opening is still per call, and the comment now says why. --- test/e2e-apporder/e2e_test.go | 157 +++++++++++++++++++++------------- 1 file changed, 99 insertions(+), 58 deletions(-) diff --git a/test/e2e-apporder/e2e_test.go b/test/e2e-apporder/e2e_test.go index a8a0bd65..a12a1f9d 100644 --- a/test/e2e-apporder/e2e_test.go +++ b/test/e2e-apporder/e2e_test.go @@ -2,10 +2,12 @@ package main import ( "database/sql" + "fmt" "os" "os/exec" "path/filepath" "strings" + "sync" "testing" _ "github.com/glebarez/go-sqlite" @@ -51,7 +53,47 @@ type env struct { bin string } -// newEnv builds the binary and lays out a working directory for it. +// The binary is built once for the whole package. Every test drives the same +// one against its own directory and its own database, and a binary is +// read-only, so there is nothing to isolate - building it per test was three +// links of the same thing. +var ( + buildOnce sync.Once + sharedDir string + sharedBin string + buildErr error +) + +func TestMain(m *testing.M) { + code := m.Run() + if sharedDir != "" { + os.RemoveAll(sharedDir) + } + os.Exit(code) +} + +// binary builds the go-admin binary with the example application linked in, +// on the first call that needs it. +func binary(t *testing.T) string { + t.Helper() + buildOnce.Do(func() { + sharedDir, buildErr = os.MkdirTemp("", "go-admin-e2e") + if buildErr != nil { + return + } + sharedBin = filepath.Join(sharedDir, "go-admin-e2e") + out, err := exec.Command("go", "build", "-tags", "sqlite3", "-o", sharedBin, ".").CombinedOutput() + if err != nil { + buildErr = fmt.Errorf("building the binary: %v\n%s", err, out) + } + }) + if buildErr != nil { + t.Fatal(buildErr) + } + return sharedBin +} + +// newEnv lays out a working directory for the binary to run in. func newEnv(t *testing.T) *env { t.Helper() if testing.Short() { @@ -78,12 +120,7 @@ func newEnv(t *testing.T) *env { t.Fatal(err) } - bin := filepath.Join(dir, "go-admin-e2e") - build := exec.Command("go", "build", "-tags", "sqlite3", "-o", bin, ".") - if out, err := build.CombinedOutput(); err != nil { - t.Fatalf("building the binary: %v\n%s", err, out) - } - return &env{t: t, dir: dir, bin: bin} + return &env{t: t, dir: dir, bin: binary(t)} } // run executes the binary and returns its combined output and exit code. @@ -111,13 +148,25 @@ func (e *env) mustRun(args ...string) string { return out } -// exec runs one statement against the database the binary uses. -func (e *env) exec(stmt string) { +// open connects to the database the binary uses. +// +// Per call, and closed again straight away, on purpose: the binary under test +// writes this same file, and a connection the test process holds open across +// a run of it is a second writer for no reason. The cost is a few +// milliseconds against a build measured in seconds. +func (e *env) open() *sql.DB { e.t.Helper() db, err := sql.Open("sqlite", filepath.Join(e.dir, "e2e.db")) if err != nil { e.t.Fatal(err) } + return db +} + +// exec runs one statement against the database the binary uses. +func (e *env) exec(stmt string) { + e.t.Helper() + db := e.open() defer db.Close() if _, err := db.Exec(stmt); err != nil { e.t.Fatalf("%s: %v", stmt, err) @@ -126,10 +175,7 @@ func (e *env) exec(stmt string) { func (e *env) count(query string, args ...any) int { e.t.Helper() - db, err := sql.Open("sqlite", filepath.Join(e.dir, "e2e.db")) - if err != nil { - e.t.Fatal(err) - } + db := e.open() defer db.Close() var n int if err := db.QueryRow(query, args...).Scan(&n); err != nil { @@ -138,6 +184,38 @@ func (e *env) count(query string, args ...any) int { return n } +// seeded is what installing this application writes, with the counts a +// finished install leaves behind. An uninstall wants every one of them at +// zero, and a reinstall wants them back - which is why one list serves all +// three checks instead of three lists drifting apart. +var seeded = []struct { + what string + query string + want int +}{ + {"menus", "SELECT COUNT(*) FROM sys_menu WHERE app_code = 'order'", 4}, + {"apis", "SELECT COUNT(*) FROM sys_api WHERE app_code = 'order'", 4}, + {"ledger", "SELECT COUNT(*) FROM sys_app_casbin_grant WHERE app_code = 'order'", 4}, + {"policies", "SELECT COUNT(*) FROM casbin_rule WHERE v1 LIKE '/api/v1/order%'", 4}, + {"migration records", "SELECT COUNT(*) FROM sys_migration WHERE app_code = 'order'", 1}, + {"sys_app rows", "SELECT COUNT(*) FROM sys_app WHERE app_code = 'order'", 1}, +} + +// assertSeeded checks every row of seeded. gone flips the expectation to +// zero, which is the whole of what an uninstall has to leave. +func (e *env) assertSeeded(when string, gone bool) { + e.t.Helper() + for _, c := range seeded { + want := c.want + if gone { + want = 0 + } + if n := e.count(c.query); n != want { + e.t.Errorf("%s, %s = %d, want %d", when, c.what, n, want) + } + } +} + func TestInstallUninstallReinstall(t *testing.T) { e := newEnv(t) @@ -161,23 +239,12 @@ func TestInstallUninstallReinstall(t *testing.T) { if !strings.Contains(out, "rebuild") { t.Errorf("the install did not say the code is not running yet:\n%s", out) } - for _, c := range []struct { - what string - query string - want int - }{ - {"menus", "SELECT COUNT(*) FROM sys_menu WHERE app_code = 'order'", 4}, - {"apis", "SELECT COUNT(*) FROM sys_api WHERE app_code = 'order'", 4}, - {"ledger", "SELECT COUNT(*) FROM sys_app_casbin_grant WHERE app_code = 'order'", 4}, - {"policies", "SELECT COUNT(*) FROM casbin_rule WHERE v1 LIKE '/api/v1/order%'", 4}, - {"migration records", "SELECT COUNT(*) FROM sys_migration WHERE app_code = 'order'", 1}, - {"sys_app rows", "SELECT COUNT(*) FROM sys_app WHERE app_code = 'order'", 1}, - {"its own table", "SELECT COUNT(*) FROM sqlite_master WHERE name = 'app_order'", 1}, - {"status=installed", "SELECT COUNT(*) FROM sys_app WHERE app_code = 'order' AND status = 2", 1}, - } { - if n := e.count(c.query); n != c.want { - t.Errorf("after install, %s = %d, want %d", c.what, n, c.want) - } + e.assertSeeded("after install", false) + if n := e.count("SELECT COUNT(*) FROM sqlite_master WHERE name = 'app_order'"); n != 1 { + t.Error("the application's own table was not created") + } + if n := e.count("SELECT COUNT(*) FROM sys_app WHERE app_code = 'order' AND status = 2"); n != 1 { + t.Error("sys_app does not say the install finished") } // A2: installing the same version again does nothing and says so. @@ -196,21 +263,7 @@ func TestInstallUninstallReinstall(t *testing.T) { if !strings.Contains(out, "own tables were not touched") { t.Errorf("the uninstall did not say what it left alone:\n%s", out) } - for _, c := range []struct { - what string - query string - }{ - {"menus", "SELECT COUNT(*) FROM sys_menu WHERE app_code = 'order'"}, - {"apis", "SELECT COUNT(*) FROM sys_api WHERE app_code = 'order'"}, - {"ledger", "SELECT COUNT(*) FROM sys_app_casbin_grant WHERE app_code = 'order'"}, - {"policies", "SELECT COUNT(*) FROM casbin_rule WHERE v1 LIKE '/api/v1/order%'"}, - {"migration records", "SELECT COUNT(*) FROM sys_migration WHERE app_code = 'order'"}, - {"sys_app rows", "SELECT COUNT(*) FROM sys_app WHERE app_code = 'order'"}, - } { - if n := e.count(c.query); n != 0 { - t.Errorf("after uninstall, %s = %d, want 0", c.what, n) - } - } + e.assertSeeded("after uninstall", true) if n := e.count("SELECT COUNT(*) FROM sqlite_master WHERE name = 'app_order'"); n != 1 { t.Error("the uninstall dropped the application's own table") } @@ -221,19 +274,7 @@ func TestInstallUninstallReinstall(t *testing.T) { // A4: the migration records had to go, or this reinstall finds every // version applied, seeds nothing, and reports success. e.mustRun("migrate", "install", "order") - for _, c := range []struct { - what string - query string - want int - }{ - {"menus", "SELECT COUNT(*) FROM sys_menu WHERE app_code = 'order'", 4}, - {"ledger", "SELECT COUNT(*) FROM sys_app_casbin_grant WHERE app_code = 'order'", 4}, - {"policies", "SELECT COUNT(*) FROM casbin_rule WHERE v1 LIKE '/api/v1/order%'", 4}, - } { - if n := e.count(c.query); n != c.want { - t.Errorf("after reinstall, %s = %d, want %d", c.what, n, c.want) - } - } + e.assertSeeded("after reinstall", false) if n := e.count("SELECT COUNT(*) FROM app_order"); n != 1 { t.Error("the business row did not survive an uninstall and reinstall") } From 03d587db6a0787dca185fc92ff9e2f55e46f8812 Mon Sep 17 00:00:00 2001 From: zhangwenjian Date: Fri, 11 Sep 2026 08:23:42 +0800 Subject: [PATCH 9/9] =?UTF-8?q?ci=F0=9F=91=B7:=20give=20the=20end-to-end?= =?UTF-8?q?=20install=20a=20make=20target?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was an inlined `go test` in the workflow, and the only thing in the build that runs it. Every other gate there goes through make - make test, make build, make checksilent - and `make test` is `go test ./...` in this module, which cannot reach test/e2e-apporder because that is a module of its own. So the one check that exercises installing an application was the one check a developer had no command for, and the only place it could turn red was after pushing. --- .github/workflows/go.yml | 3 +-- Makefile | 12 ++++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index da0cd440..78c1b89b 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -116,8 +116,7 @@ jobs: # and a hand-built schema, and none of that can catch an application's # init() reaching one registry and not the other. - name: End-to-end install and uninstall - working-directory: test/e2e-apporder - run: go test ./... -count=1 + run: make test-e2e # Fails the build on the silent-failure classes listed in # tools/checksilent, one of which is the contract boundary: nothing under diff --git a/Makefile b/Makefile index 97a0273e..b4fe1778 100644 --- a/Makefile +++ b/Makefile @@ -54,6 +54,18 @@ stop: test: go test -race -cover ./... +# The end-to-end install, which `test` above cannot reach: test/e2e-apporder +# is its own module, so `./...` in this one does not include it. It builds a +# go-admin binary with the example application linked in and drives +# `migrate install` / `migrate uninstall` against a real database. +# +# Its own target rather than a line in the CI workflow, so the one thing in +# the build that exercises installing an application is also the one thing +# somebody can run before pushing. +.PHONY: test-e2e +test-e2e: + cd test/e2e-apporder && go test ./... -count=1 + # Reports the failures that do not announce themselves - see # tools/checksilent. Exits non-zero on an ERROR; the one WARN-level check # prints and does not fail the build.