Skip to content

Commit d9f4904

Browse files
committed
Support for database/sql in migrations + framework for multi-driver River
Here, add a new minimal driver called `riverdriver/riversql` that supports Go's built-in `database/sql` package, but only for purposes of migrations. The idea here is to fully complete #57 by providing a way of making `rivermigrate` interoperable with Go migration frameworks that support Go-based migrations like Goose, which provides hooks for `*sql.Tx` [1] rather than pgx. `riverdriver/riversql` is not a full driver and is only meant to be used with `rivermigrate`. We document this clearly in a number of places. To make a multi-driver world possible with River, we have to start the work of building a platform that does more than `riverpgxv5`'s "cheat" workaround. This works by having each driver implement specific database operations like `MigrationGetAll`, which target their wrapped database package of choice. This is accomplished by having each driver bundle in its own sqlc that targets its package. So `riverpgxv5` has an `sqlc.yaml` that targets `pgx/v5`, while `riversql` has one that targets `database/sql`. There's some `sqlc.yaml` duplication involved here, but luckily both drivers can share a `river_migration.sql` file that contains all queries involved, so you only need to change one place. `river_migration.sql` also migrates entirely out of the main `./internal/dbsqlc`. The idea here is that eventually `./internal/dbsqlc` will disappear completely, usurped entirely by driver-specific versions. As this is done, all references to `pgx` will disappear from the top-level package. There are some complications here to figure out like `LISTEN`/`NOTIFY` though, and I'm not clear whether `database/sql` could ever become a fully functional driver as it might be missing some needed functionality (e.g. subtransactions are still not supported after talking about them for ten f*ing years [2]. However, even if it's not, the system would let us support other fully functional packages or future major versions of pgx (or even past ones like `pgx/v4` if there's demand). `river/riverdriver` becomes a package as it now has types in it that need to be referenced by driver implementations, and this would otherwise not be possible without introducing a circular dependency. [1] https://github.com/pressly/goose#go-migrations [2] golang/go#7898
1 parent 1468298 commit d9f4904

36 files changed

+1162
-148
lines changed

.github/workflows/ci.yml

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,15 @@ jobs:
7272
env:
7373
TEST_DATABASE_URL: postgres://postgres:[email protected]:5432/river_testdb?sslmode=disable
7474

75-
- name: Test riverpgxv5
75+
- name: Test riverdriver
76+
working-directory: ./riverdriver
77+
run: go test -race ./...
78+
79+
- name: Test riverdriver/riverdatabasesql
80+
working-directory: ./riverdriver/riverdatabasesql
81+
run: go test -race ./...
82+
83+
- name: Test riverdriver/riverpgxv5
7684
working-directory: ./riverdriver/riverpgxv5
7785
run: go test -race ./...
7886

@@ -117,10 +125,13 @@ jobs:
117125
golangci:
118126
name: lint
119127
runs-on: ubuntu-latest
128+
env:
129+
GOLANGCI_LINT_VERSION: v1.55.2
120130
permissions:
121131
contents: read
122132
# allow read access to pull request. Use with `only-new-issues` option.
123133
pull-requests: read
134+
124135
steps:
125136
- uses: actions/setup-go@v4
126137
with:
@@ -130,13 +141,33 @@ jobs:
130141
- name: Checkout
131142
uses: actions/checkout@v3
132143

133-
- name: golangci-lint
144+
- name: Lint
134145
uses: golangci/golangci-lint-action@v3
135146
with:
136-
# Optional: show only new issues if it's a pull request. The default value is `false`.
137-
only-new-issues: true
147+
only-new-issues: true # Optional: show only new issues if it's a pull request. The default value is `false`.
148+
version: ${{ env.GOLANGCI_LINT_VERSION }}
149+
working-directory: .
138150

139-
version: v1.55.2
151+
- name: Lint riverdriver
152+
uses: golangci/golangci-lint-action@v3
153+
with:
154+
only-new-issues: true # Optional: show only new issues if it's a pull request. The default value is `false`.
155+
version: ${{ env.GOLANGCI_LINT_VERSION }}
156+
working-directory: ./riverdriver
157+
158+
- name: Lint riverdriver/riverdatabasesql
159+
uses: golangci/golangci-lint-action@v3
160+
with:
161+
only-new-issues: true # Optional: show only new issues if it's a pull request. The default value is `false`.
162+
version: ${{ env.GOLANGCI_LINT_VERSION }}
163+
working-directory: ./riverdriver/riverdatabasesql
164+
165+
- name: Lint riverdriver/riverpgxv5
166+
uses: golangci/golangci-lint-action@v3
167+
with:
168+
only-new-issues: true # Optional: show only new issues if it's a pull request. The default value is `false`.
169+
version: ${{ env.GOLANGCI_LINT_VERSION }}
170+
working-directory: ./riverdriver/riverpgxv5
140171

141172
producer_sample:
142173
runs-on: ubuntu-latest
@@ -204,7 +235,6 @@ jobs:
204235
sqlc-version: "1.22.0"
205236

206237
- name: Run sqlc diff
207-
working-directory: ./internal/dbsqlc
208238
run: |
209239
echo "Please make sure that all sqlc changes are checked in!"
210-
sqlc diff
240+
make verify

.golangci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ linters-settings:
6464
- Default
6565
- Prefix(github.com/riverqueue)
6666

67+
gomoddirectives:
68+
replace-local: true
69+
6770
gosec:
6871
excludes:
6972
- G404 # use of non-crypto random; overly broad for our use case

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Added `riverdriver/riverdatabasesql` driver to enable River Go migrations through Go's built in `database/sql` package. [PR #98](https://github.com/riverqueue/river/pull/98).
13+
1014
### Changed
1115

1216
- Errored jobs that have a very short duration before their next retry (<5 seconds) are set to `available` immediately instead of being made `scheduled` and having to wait for the scheduler to make a pass to make them workable. [PR #105](https://github.com/riverqueue/river/pull/105).
17+
- `riverdriver` becomes its own submodule. It contains types that `riverdriver/riverdatabasesql` and `riverdriver/riverpgxv5` need to reference. [PR #98](https://github.com/riverqueue/river/pull/98).
1318

1419
## [0.0.12] - 2023-12-02
1520

Makefile

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,16 @@ generate: generate/sqlc
44

55
.PHONY: generate/sqlc
66
generate/sqlc:
7-
cd internal/dbsqlc && sqlc generate
7+
cd internal/dbsqlc && sqlc generate
8+
cd riverdriver/riverdatabasesql/internal/dbsqlc && sqlc generate
9+
cd riverdriver/riverpgxv5/internal/dbsqlc && sqlc generate
10+
11+
.PHONY: verify
12+
verify:
13+
verify: verify/sqlc
14+
15+
.PHONY: verify/sqlc
16+
verify/sqlc:
17+
cd internal/dbsqlc && sqlc diff
18+
cd riverdriver/riverdatabasesql/internal/dbsqlc && sqlc diff
19+
cd riverdriver/riverpgxv5/internal/dbsqlc && sqlc diff

docs/development.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ queries. After changing an sqlc `.sql` file, generate Go with:
3232
```shell
3333
git checkout master && git pull --rebase
3434
VERSION=v0.0.x
35+
git tag riverdriver/VERSION -m "release riverdriver/VERSION"
3536
git tag riverdriver/riverpgxv5/$VERSION -m "release riverdriver/riverpgxv5/$VERSION"
37+
git tag riverdriver/riverdatabasesql/$VERSION -m "release riverdriver/riverdatabasesql/$VERSION"
3638
git tag $VERSION
3739
git push --tags
3840
```

go.mod

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
module github.com/riverqueue/river
22

3-
go 1.21.0
3+
go 1.21.4
44

5-
// replace github.com/riverqueue/river/riverdriver/riverpgxv5 => ./riverdriver/riverpgxv5
5+
replace github.com/riverqueue/river/riverdriver => ./riverdriver
6+
7+
replace github.com/riverqueue/river/riverdriver/riverpgxv5 => ./riverdriver/riverpgxv5
8+
9+
replace github.com/riverqueue/river/riverdriver/riverdatabasesql => ./riverdriver/riverdatabasesql
610

711
require (
812
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa
913
github.com/jackc/pgx/v5 v5.5.1
1014
github.com/jackc/puddle/v2 v2.2.1
1115
github.com/oklog/ulid/v2 v2.1.0
16+
github.com/riverqueue/river/riverdriver v0.0.0-00010101000000-000000000000
1217
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.0.12
18+
github.com/riverqueue/river/riverdriver/riverdatabasesql v0.0.0-00010101000000-000000000000
1319
github.com/robfig/cron/v3 v3.0.1
1420
github.com/spf13/cobra v1.8.0
1521
github.com/stretchr/testify v1.8.4
@@ -23,6 +29,7 @@ require (
2329
github.com/inconshreveable/mousetrap v1.1.0 // indirect
2430
github.com/jackc/pgpassfile v1.0.0 // indirect
2531
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
32+
github.com/lib/pq v1.10.9 // indirect
2633
github.com/pmezard/go-difflib v1.0.0 // indirect
2734
github.com/spf13/pflag v1.0.5 // indirect
2835
golang.org/x/crypto v0.15.0 // indirect

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
1818
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
1919
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
2020
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
21+
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
22+
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
2123
github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
2224
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
2325
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
2426
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
2527
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
26-
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.0.12 h1:mcDBnqwzEXY9WDOwbkd8xmFdSr/H6oHb1F3NCNCmLDY=
27-
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.0.12/go.mod h1:k6hsPkW9Fl3qURzyLHbvxUCqWDpit0WrZ3oEaKezD3E=
2828
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
2929
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
3030
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=

internal/dbsqlc/models.go

Lines changed: 0 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/dbsqlc/sqlc.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@ sql:
44
queries:
55
- river_job.sql
66
- river_leader.sql
7-
- river_migration.sql
87
schema:
98
- river_job.sql
109
- river_leader.sql
11-
- river_migration.sql
1210
gen:
1311
go:
1412
package: "dbsqlc"

internal/riverinternaltest/riverinternaltest.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"log"
1010
"log/slog"
11+
"net/url"
1112
"os"
1213
"runtime"
1314
"sync"
@@ -62,19 +63,39 @@ func BaseServiceArchetype(tb testing.TB) *baseservice.Archetype {
6263
}
6364

6465
func DatabaseConfig(databaseName string) *pgxpool.Config {
65-
databaseURL := valutil.ValOrDefault(os.Getenv("TEST_DATABASE_URL"), "postgres:///river_testdb?sslmode=disable")
66-
67-
config, err := pgxpool.ParseConfig(databaseURL)
66+
config, err := pgxpool.ParseConfig(DatabaseURL(databaseName))
6867
if err != nil {
6968
panic(fmt.Sprintf("error parsing database URL: %v", err))
7069
}
7170
config.MaxConns = dbPoolMaxConns
7271
config.ConnConfig.ConnectTimeout = 10 * time.Second
73-
config.ConnConfig.Database = databaseName
7472
config.ConnConfig.RuntimeParams["timezone"] = "UTC"
7573
return config
7674
}
7775

76+
// DatabaseURL gets a test database URL from TEST_DATABASE_URL or falls back on
77+
// a default pointing to `river_testdb`. If databaseName is set, it replaces the
78+
// database in the URL, although the host and other parameters are preserved.
79+
//
80+
// Most of the time DatabaseConfig should be used instead of this function, but
81+
// it may be useful in non-pgx situations like for examples showing the use of
82+
// `database/sql`.
83+
func DatabaseURL(databaseName string) string {
84+
u, err := url.Parse(valutil.ValOrDefault(
85+
os.Getenv("TEST_DATABASE_URL"),
86+
"postgres://localhost/river_testdb?sslmode=disable"),
87+
)
88+
if err != nil {
89+
panic(err)
90+
}
91+
92+
if databaseName != "" {
93+
u.Path = databaseName
94+
}
95+
96+
return u.String()
97+
}
98+
7899
// DiscardContinuously drains continuously out of the given channel and discards
79100
// anything that comes out of it. Returns a stop function that should be invoked
80101
// to stop draining. Stop must be invoked before tests finish to stop an

internal/util/dbutil/db_util.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/jackc/pgx/v5"
88

99
"github.com/riverqueue/river/internal/dbsqlc"
10+
"github.com/riverqueue/river/riverdriver"
1011
)
1112

1213
// Executor is an interface for a type that can begin a transaction and also
@@ -56,3 +57,35 @@ func WithTxV[T any](ctx context.Context, txBeginner TxBeginner, innerFunc func(c
5657

5758
return res, nil
5859
}
60+
61+
// WithExecutorTx starts and commits a transaction on a driver executor around
62+
// the given function, allowing the return of a generic value.
63+
func WithExecutorTx(ctx context.Context, exec riverdriver.Executor, innerFunc func(ctx context.Context, tx riverdriver.ExecutorTx) error) error {
64+
_, err := WithExecutorTxV(ctx, exec, func(ctx context.Context, tx riverdriver.ExecutorTx) (struct{}, error) {
65+
return struct{}{}, innerFunc(ctx, tx)
66+
})
67+
return err
68+
}
69+
70+
// WithExecutorTxV starts and commits a transaction on a driver executor around
71+
// the given function, allowing the return of a generic value.
72+
func WithExecutorTxV[T any](ctx context.Context, exec riverdriver.Executor, innerFunc func(ctx context.Context, tx riverdriver.ExecutorTx) (T, error)) (T, error) {
73+
var defaultRes T
74+
75+
tx, err := exec.Begin(ctx)
76+
if err != nil {
77+
return defaultRes, fmt.Errorf("error beginning transaction: %w", err)
78+
}
79+
defer tx.Rollback(ctx)
80+
81+
res, err := innerFunc(ctx, tx)
82+
if err != nil {
83+
return defaultRes, err
84+
}
85+
86+
if err := tx.Commit(ctx); err != nil {
87+
return defaultRes, fmt.Errorf("error committing transaction: %w", err)
88+
}
89+
90+
return res, nil
91+
}

internal/util/dbutil/db_util_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"github.com/stretchr/testify/require"
99

1010
"github.com/riverqueue/river/internal/riverinternaltest"
11+
"github.com/riverqueue/river/riverdriver"
12+
"github.com/riverqueue/river/riverdriver/riverpgxv5"
1113
)
1214

1315
func TestWithTx(t *testing.T) {
@@ -40,3 +42,36 @@ func TestWithTxV(t *testing.T) {
4042
require.NoError(t, err)
4143
require.Equal(t, 7, ret)
4244
}
45+
46+
func TestWithExecutorTx(t *testing.T) {
47+
t.Parallel()
48+
49+
ctx := context.Background()
50+
dbPool := riverinternaltest.TestDB(ctx, t)
51+
driver := riverpgxv5.New(dbPool)
52+
53+
err := WithExecutorTx(ctx, driver.GetExecutor(), func(ctx context.Context, tx riverdriver.ExecutorTx) error {
54+
_, err := tx.Exec(ctx, "SELECT 1")
55+
require.NoError(t, err)
56+
57+
return nil
58+
})
59+
require.NoError(t, err)
60+
}
61+
62+
func TestWithExecutorTxV(t *testing.T) {
63+
t.Parallel()
64+
65+
ctx := context.Background()
66+
dbPool := riverinternaltest.TestDB(ctx, t)
67+
driver := riverpgxv5.New(dbPool)
68+
69+
ret, err := WithExecutorTxV(ctx, driver.GetExecutor(), func(ctx context.Context, tx riverdriver.ExecutorTx) (int, error) {
70+
_, err := tx.Exec(ctx, "SELECT 1")
71+
require.NoError(t, err)
72+
73+
return 7, nil
74+
})
75+
require.NoError(t, err)
76+
require.Equal(t, 7, ret)
77+
}

riverdriver/go.mod

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module github.com/riverqueue/river/riverdriver
2+
3+
go 1.21.4
4+
5+
require github.com/jackc/pgx/v5 v5.5.0
6+
7+
require (
8+
github.com/jackc/pgpassfile v1.0.0 // indirect
9+
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
10+
github.com/jackc/puddle/v2 v2.2.1 // indirect
11+
golang.org/x/crypto v0.15.0 // indirect
12+
golang.org/x/sync v0.5.0 // indirect
13+
golang.org/x/text v0.14.0 // indirect
14+
)

riverdriver/go.sum

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
5+
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
6+
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
7+
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
8+
github.com/jackc/pgx/v5 v5.5.0 h1:NxstgwndsTRy7eq9/kqYc/BZh5w2hHJV86wjvO+1xPw=
9+
github.com/jackc/pgx/v5 v5.5.0/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
10+
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
11+
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
12+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
13+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
14+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
15+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
16+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
17+
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
18+
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
19+
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
20+
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
21+
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
22+
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
23+
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
24+
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
25+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
26+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
27+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
28+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)