├── .cz.yaml ├── .gitattributes ├── .github ├── dependabot.yaml └── workflows │ ├── codeql.yaml │ ├── gitleaks.yaml │ ├── grype.yaml │ ├── lint-test.yaml │ └── sonar.yaml ├── .gitignore ├── .golangci.yml ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── doc.go ├── go.mod ├── go.sum ├── options.go ├── psql_container.go └── psql_container_test.go /.cz.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | commitizen: 3 | name: cz_conventional_commits 4 | tag_format: v$version 5 | version: 1.2.1 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | go.sum linguist-generated -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | day: "monday" 8 | time: "06:00" # 9 am in romania 9 | commit-message: 10 | prefix: "build(deps)" 11 | reviewers: 12 | - "adrianbrad" 13 | 14 | - package-ecosystem: github-actions 15 | directory: "/" 16 | schedule: 17 | interval: weekly 18 | day: "monday" 19 | time: "06:00" # 9 am in romania 20 | commit-message: 21 | prefix: "build(deps)" 22 | reviewers: 23 | - "adrianbrad" 24 | 25 | - package-ecosystem: docker 26 | directory: "/d8t" 27 | schedule: 28 | interval: weekly 29 | day: "monday" 30 | time: "06:00" # 9 am in romania 31 | commit-message: 32 | prefix: "build(deps)" 33 | reviewers: 34 | - "adrianbrad" -------------------------------------------------------------------------------- /.github/workflows/codeql.yaml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [ main ] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [ main ] 14 | schedule: 15 | - cron: '0 17 * * 5' 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: ubuntu-latest 21 | 22 | permissions: 23 | # required for all workflows 24 | security-events: write 25 | # only required for workflows in private repositories 26 | contents: read 27 | 28 | steps: 29 | - name: Checkout code 30 | uses: actions/checkout@v4 31 | 32 | # Initializes the CodeQL tools for scanning. 33 | - name: Initialize CodeQL 34 | uses: github/codeql-action/init@v3 35 | with: 36 | languages: go 37 | - name: Autobuild 38 | uses: github/codeql-action/autobuild@v3 39 | 40 | - name: Perform CodeQL Analysis 41 | uses: github/codeql-action/analyze@v3 -------------------------------------------------------------------------------- /.github/workflows/gitleaks.yaml: -------------------------------------------------------------------------------- 1 | name: gitleaks 2 | on: [pull_request, push, workflow_dispatch] 3 | 4 | permissions: 5 | contents: read 6 | 7 | jobs: 8 | scan: 9 | name: gitleaks 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 0 15 | - uses: gitleaks/gitleaks-action@v2 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/grype.yaml: -------------------------------------------------------------------------------- 1 | name: Grype 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | scan-source: 9 | name: scan-source 10 | runs-on: ubuntu-latest 11 | 12 | permissions: 13 | security-events: write 14 | actions: read 15 | contents: read 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: anchore/scan-action@v3 20 | with: 21 | path: "." 22 | fail-build: true -------------------------------------------------------------------------------- /.github/workflows/lint-test.yaml: -------------------------------------------------------------------------------- 1 | name: Lint and Test 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | 8 | jobs: 9 | lint: 10 | name: Lint 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: install Go 17 | uses: actions/setup-go@v5 18 | with: 19 | go-version: stable 20 | 21 | - name: lint 22 | uses: golangci/golangci-lint-action@v5 23 | with: 24 | version: latest 25 | args: --timeout 5m 26 | 27 | test: 28 | name: Test 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: checkout code 32 | uses: actions/checkout@v4 33 | 34 | - name: install Go 35 | uses: actions/setup-go@v5 36 | with: 37 | go-version: stable 38 | 39 | - name: test 40 | run: make test-ci 41 | 42 | - name: Upload coverage report 43 | uses: codecov/codecov-action@v4 44 | with: 45 | token: ${{ secrets.CODECOV_TOKEN }} 46 | file: ./coverage.txt 47 | flags: unittests -------------------------------------------------------------------------------- /.github/workflows/sonar.yaml: -------------------------------------------------------------------------------- 1 | name: sonar 2 | on: 3 | pull_request: 4 | branches: [ main ] 5 | push: 6 | branches: [ main ] 7 | 8 | jobs: 9 | sonar-analysis: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | - name: Generate sonar-project.properties 17 | run: | 18 | cat < sonar-project.properties 19 | sonar.organization=adrianbrad 20 | sonar.projectKey=adrianbrad_psqldocker 21 | 22 | sonar.sources=. 23 | EOF 24 | - name: Sonar analysis 25 | uses: sonarsource/sonarcloud-github-action@master 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.idea/ 3 | /.vscode/ 4 | postgres-data/ 5 | key.json 6 | coverage.txt 7 | db_pass.txt 8 | .pgpass 9 | coverage.out 10 | coverage.xml -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # .golangci.yml 2 | run: 3 | modules-download-mode: mod 4 | 5 | linters-settings: 6 | gosec: 7 | excludes: 8 | - G307 9 | 10 | staticcheck: 11 | # https://staticcheck.io/docs/options#checks 12 | checks: [ "all" ] 13 | 14 | tagliatelle: 15 | # Check the struck tag name case. 16 | case: 17 | # Use the struct field name to check the name of the struct tag. 18 | # Default: false 19 | use-field-name: true 20 | rules: 21 | # Any struct tag type can be used. 22 | # Support string case: `camel`, `pascal`, `kebab`, `snake`, `goCamel`, `goPascal`, `goKebab`, `goSnake`, `upper`, `lower` 23 | json: snake 24 | 25 | dupl: 26 | threshold: 300 # tokens count of duplicate code to trigger issue 27 | 28 | goconst: 29 | min-len: 2 # minimal length of string constant 30 | min-occurrences: 4 # minimal occurrences count to trigger 31 | 32 | gocritic: 33 | enabled-tags: 34 | - diagnostic 35 | - experimental 36 | - opinionated 37 | - performance 38 | - style 39 | disabled-checks: 40 | - dupImport # https://github.com/go-critic/go-critic/issues/845 41 | - whyNoLint 42 | - hugeParam 43 | 44 | gocyclo: 45 | min-complexity: 20 # minimal code cyclomatic complexity to report 46 | 47 | mnd: 48 | # don't include the "operation" and "assign" 49 | checks: [argument,case,condition,return] 50 | 51 | misspell: 52 | locale: US 53 | 54 | nolintlint: 55 | allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space) 56 | allow-unused: false # report any unused nolint directives 57 | require-explanation: true # don't require an explanation for nolint directives 58 | require-specific: false # don't require nolint directives to be specific about which linter is being skipped 59 | 60 | gocognit: 61 | min-complexity: 20 # minimal code cognitive complexity to report 62 | 63 | gofumpt: 64 | extra-rules: true 65 | 66 | varnamelen: 67 | max-distance: 15 68 | ignore-names: 69 | - tx 70 | - err 71 | - pk 72 | - to 73 | - db 74 | - wg 75 | - id 76 | - DB 77 | ignore-type-assert-ok: true 78 | ignore-map-index-ok: true 79 | ignore-chan-recv-ok: true 80 | ignore-decls: 81 | - t testing.T 82 | - i int 83 | - T any 84 | - i *is.I 85 | - eg *errgroup.Group 86 | - ok bool 87 | 88 | revive: 89 | ignore-generated-header: true 90 | enable-all-rules: true 91 | confidence: 0.1 92 | rules: 93 | - name: nested-structs 94 | disabled: true 95 | - name: function-result-limit 96 | arguments: [ 3 ] 97 | - name: function-length 98 | disabled: true 99 | - name: banned-characters 100 | disabled: true 101 | - name: max-public-structs 102 | disabled: true 103 | - name: line-length-limit 104 | arguments: [ 100 ] 105 | - name: argument-limit 106 | disabled: true 107 | - name: cyclomatic 108 | disabled: true 109 | - name: file-header 110 | disabled: true 111 | - name: cognitive-complexity 112 | disabled: true 113 | - name: package-comments 114 | disabled: true 115 | - name: add-constant 116 | disabled: true 117 | - name: unhandled-error 118 | disabled: true 119 | - name: var-naming 120 | arguments: [[], ["WS", "VM"]] 121 | errcheck: 122 | exclude-functions: 123 | - (*net/http.Response.Body).Close 124 | - (io.ReadCloser).Close 125 | - (*database/sql.DB).Close 126 | 127 | linters: 128 | disable-all: true 129 | enable: 130 | # - asasalint # Check for pass []any as any in variadic func(...any) 131 | - bidichk # Checks for dangerous unicode character sequences 132 | - contextcheck # check the function whether use a non-inherited context 133 | - containedctx # containedctx is a linter that detects struct contained context.Context field 134 | - decorder # check declaration order and count of types, constants, variables and functions 135 | - errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted. 136 | - grouper # An analyzer to analyze expression groups. 137 | - govet # Vet examines Go source code and reports suspicious constructs, only purpose of this tool is to detect go structs that would take less memory if their fields were sorted 138 | - bodyclose # Detects whether the HTTP response body is closed successfully, not closing the response body could lead to memory leaks 139 | - goconst # Finds repeated strings that could be replaced by a constant 140 | - godot # Check if comments end in a period 141 | - mnd # An analyzer to detect magic numbers. 142 | - err113 # Golang linter to check the errors handling expressions 143 | - gocritic # Provides many diagnostics that check for bugs, performance and style issues. 144 | - exhaustive # Check exhaustiveness of enum switch statements 145 | - exportloopref # checks for pointers to enclosing loop variables -- VERY IMPORTANT TO USE 146 | - errname # Checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error. 147 | - forcetypeassert # finds forced type assertions 148 | - importas # Enforces consistent import aliases 149 | - gci # improves imports 150 | - dupl # Detects code clones 151 | - revive # Makes code style recomandations 152 | - gocyclo # Computes and checks the cyclomatic complexity of functions 153 | - gofumpt # Stricter gofmt 154 | - errcheck # Checks unchecked errors in go programs 155 | - gosimple # Linter for Go source code that specializes in simplifying a code 156 | - ineffassign # Detects when assignments to existing variables are not used 157 | - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks 158 | - tagliatelle # Checks the struct tags. 159 | - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code 160 | - thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers 161 | - tenv # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17 162 | - unused # Checks Go code for unused constants, variables, functions and types 163 | - varnamelen # checks that the length of a variable's name matches its scope style 164 | - gocognit # Computes and checks the cognitive complexity of functions https://github.com/uudashr/gocognit 165 | - gosec # Inspects source code for security problems 166 | - prealloc # Finds slice declarations that could potentially be preallocated 167 | - nilnil # Checks that there is no simultaneous return of nil error and an invalid value. 168 | - wsl # Whitespace Linter - Forces you to use empty lines! 169 | - usestdlibvars # A linter that detect the possibility to use variables/constants from the Go standard library. 170 | - interfacebloat # A linter that checks the number of methods inside an interface. 171 | - loggercheck # Check logr arguments. 172 | - reassign # Checks that package variables are not reassigne 173 | - musttag # linter that enforces field tags in (un)marshaled structslinter that enforces field tags in (un)marshaled structs 174 | - dupword # checks for duplicate words in the source code comments. 175 | - tagalign 176 | - mirror 177 | - gosmopolitan 178 | - gochecksumtype 179 | - perfsprint 180 | - sloglint 181 | - testifylint 182 | - fatcontext 183 | - canonicalheader 184 | 185 | issues: 186 | exclude-use-default: false 187 | include: 188 | - EXC0001 189 | - EXC0004 190 | - EXC0006 191 | - EXC0007 192 | - EXC0009 193 | - EXC0010 194 | - EXC0014 195 | 196 | # Excluding configuration per-path, per-linter, per-text and per-source 197 | exclude-rules: 198 | # Exclude some linters from running on tests files. 199 | - path: _test\.go 200 | linters: 201 | - gocyclo 202 | - errcheck 203 | - gosec 204 | - gocognit 205 | - forcetypeassert 206 | - varnamelen 207 | - nilnil 208 | - goerr113 209 | - path: mock 210 | linters: 211 | - mnd 212 | - revive 213 | - gocyclo 214 | - errcheck 215 | - dupl 216 | - gosec 217 | - forcetypeassert -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/commitizen-tools/commitizen 3 | rev: 3.12.0 4 | hooks: 5 | - id: commitizen 6 | - repo: https://github.com/golangci/golangci-lint 7 | rev: v1.55.2 8 | hooks: 9 | - id: golangci-lint 10 | name: golangci-lint 11 | description: Fast linters runner for Go. 12 | entry: golangci-lint run --fix 13 | types: [go] 14 | language: golang 15 | pass_filenames: false 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v1.2.0 (2024-01-04) 2 | 3 | ### Feat 4 | 5 | - add TimescaleDB support 6 | 7 | ## v1.1.7 (2023-11-24) 8 | 9 | ### Fix 10 | 11 | - **deps**: bump all deps including those that trigger vulnerabilities 12 | 13 | ## v1.1.6 (2023-06-28) 14 | 15 | ### Fix 16 | 17 | - tests and error formatting 18 | 19 | ## v1.1.5 (2023-03-21) 20 | 21 | ### Refactor 22 | 23 | - **db-driver**: replace pq with pgx. check db close errors inside internal functions 24 | - rename readme.go to doc.go 25 | 26 | ## v1.1.4 (2022-08-15) 27 | 28 | ## v1.1.3 (2022-08-11) 29 | 30 | ## v1.1.2 (2022-08-03) 31 | 32 | ## v1.1.1 (2022-05-29) 33 | 34 | ## v1.1.0 (2022-05-02) 35 | 36 | ## v1.0.0 (2022-04-18) 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Adrian Brad 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: check 2 | lint: 3 | golangci-lint run --fix 4 | 5 | test: 6 | go test -mod=mod -count=1 --race ./... 7 | 8 | test-ci: 9 | go test -mod=mod -count=1 -timeout 60s -coverprofile=coverage.txt -covermode=atomic ./... 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | adrianbrad psqldocker 2 | 3 | # 🚢 psqldocker ![GitHub release](https://img.shields.io/github/v/release/adrianbrad/psqldocker) 4 | 5 | powered by [`ory/dockertest`](https://github.com/ory/dockertest). 6 | 7 | [![GitHub go.mod Go version of a Go module](https://img.shields.io/github/go-mod/go-version/adrianbrad/psqldocker)](https://github.com/adrianbrad/psqldocker) 8 | [![GoDoc reference example](https://img.shields.io/badge/godoc-reference-blue.svg)](https://pkg.go.dev/github.com/adrianbrad/psqldocker) 9 | 10 | [![CodeFactor](https://www.codefactor.io/repository/github/adrianbrad/psqldocker/badge)](https://www.codefactor.io/repository/github/adrianbrad/psqldocker) 11 | [![Go Report Card](https://goreportcard.com/badge/github.com/adrianbrad/psqldocker)](https://goreportcard.com/report/github.com/adrianbrad/psqldocker) 12 | [![codecov](https://codecov.io/gh/adrianbrad/psqldocker/branch/main/graph/badge.svg)](https://codecov.io/gh/adrianbrad/psqldocker) 13 | 14 | [![lint-test](https://github.com/adrianbrad/psqldocker/actions/workflows/lint-test.yaml/badge.svg)](https://github.com/adrianbrad/psqldocker/actions?query=workflow%3Alint-test) 15 | [![grype](https://github.com/adrianbrad/psqldocker/actions/workflows/grype.yaml/badge.svg)](https://github.com/adrianbrad/psqldocker/actions?query=workflow%3Agrype) 16 | [![codeql](https://github.com/adrianbrad/psqldocker/workflows/CodeQL/badge.svg)](https://github.com/adrianbrad/psqldocker/actions?query=workflow%3ACodeQL) 17 | [![gitleaks](https://github.com/adrianbrad/psqldocker/workflows/gitleaks/badge.svg)](https://github.com/adrianbrad/psqldocker/actions?query=workflow%3Agitleaks) 18 | 19 | --- 20 | Go package providing lifecycle management for PostgreSQL Docker instances. 21 | 22 | [Here](https://adrianbrad.medium.com/parallel-postgresql-tests-go-docker-6fb51c016796) is an article expanding on the usage of this package. 23 | 24 | Leverage Docker to run unit and integration tests against a real PostgreSQL database. 25 | 26 | ### Usage 27 | #### Recommended: In a TestXxx function 28 | 29 | ```go 30 | package foo_test 31 | 32 | import ( 33 | "testing" 34 | 35 | "github.com/adrianbrad/psqldocker" 36 | ) 37 | 38 | func TestXxx(t *testing.T) { 39 | const schema = "CREATE TABLE users(user_id UUID PRIMARY KEY);" 40 | 41 | c, err := psqldocker.NewContainer( 42 | "user", 43 | "password", 44 | "dbName", 45 | psqldocker.WithContainerName("test"), 46 | // initialize with a schema 47 | psqldocker.WithSql(schema), 48 | // you can add other options here 49 | ) 50 | if err != nil { 51 | t.Fatalf("cannot start new psql container: %s\n", err) 52 | } 53 | 54 | t.Cleanup(func() { 55 | err = c.Close() 56 | if err != nil { 57 | t.Logf("err while closing conainter: %w", err) 58 | } 59 | }) 60 | 61 | t.Run("Subtest", func(t *testing.T) { 62 | // execute your test logic here 63 | }) 64 | } 65 | ``` 66 | --- 67 | #### In a TestMain function 68 | 69 | ```go 70 | package foo_test 71 | 72 | import ( 73 | "log" 74 | "testing" 75 | 76 | "github.com/adrianbrad/psqldocker" 77 | ) 78 | 79 | func TestMain(m *testing.M) { 80 | const schema = "CREATE TABLE users(user_id UUID PRIMARY KEY);" 81 | 82 | c, err := psqldocker.NewContainer( 83 | "user", 84 | "password", 85 | "dbName", 86 | psqldocker.WithContainerName("test"), 87 | // initialize with a schema 88 | psqldocker.WithSql(schema), 89 | // you can add other options here 90 | ) 91 | if err != nil { 92 | log.Fatalf("cannot start new psql container: %s\n", err) 93 | } 94 | 95 | defer func() { 96 | err = c.Close() 97 | if err != nil { 98 | log.Printf("err while closing conainter: %w", err) 99 | } 100 | }() 101 | 102 | m.Run() 103 | } 104 | ``` 105 | 106 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package psqldocker provides functions 2 | // to start, stop and configure a PostgreSQL docker container. 3 | // The purpose of this package is mainly for testing. 4 | // 5 | // The docker container is created programatically using 6 | // the github.com/ory/dockertest package. 7 | package psqldocker 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/adrianbrad/psqldocker 2 | 3 | go 1.21 4 | 5 | toolchain go1.22.2 6 | 7 | require ( 8 | github.com/jackc/pgx/v5 v5.5.5 9 | github.com/matryer/is v1.4.1 10 | github.com/ory/dockertest/v3 v3.10.0 11 | ) 12 | 13 | require ( 14 | dario.cat/mergo v1.0.0 // indirect 15 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect 16 | github.com/Microsoft/go-winio v0.6.2 // indirect 17 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect 18 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 19 | github.com/containerd/continuity v0.4.3 // indirect 20 | github.com/docker/cli v26.1.1+incompatible // indirect 21 | github.com/docker/docker v26.1.1+incompatible // indirect 22 | github.com/docker/go-connections v0.5.0 // indirect 23 | github.com/docker/go-units v0.5.0 // indirect 24 | github.com/gogo/protobuf v1.3.2 // indirect 25 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 26 | github.com/jackc/pgpassfile v1.0.0 // indirect 27 | github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect 28 | github.com/jackc/puddle/v2 v2.2.1 // indirect 29 | github.com/kr/text v0.2.0 // indirect 30 | github.com/lib/pq v1.10.7 // indirect 31 | github.com/mitchellh/mapstructure v1.5.0 // indirect 32 | github.com/moby/docker-image-spec v1.3.1 // indirect 33 | github.com/moby/term v0.5.0 // indirect 34 | github.com/opencontainers/go-digest v1.0.0 // indirect 35 | github.com/opencontainers/image-spec v1.1.0 // indirect 36 | github.com/opencontainers/runc v1.1.12 // indirect 37 | github.com/pkg/errors v0.9.1 // indirect 38 | github.com/rogpeppe/go-internal v1.9.0 // indirect 39 | github.com/sirupsen/logrus v1.9.3 // indirect 40 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect 41 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect 42 | github.com/xeipuuv/gojsonschema v1.2.0 // indirect 43 | golang.org/x/crypto v0.22.0 // indirect 44 | golang.org/x/mod v0.17.0 // indirect 45 | golang.org/x/sync v0.7.0 // indirect 46 | golang.org/x/sys v0.20.0 // indirect 47 | golang.org/x/text v0.15.0 // indirect 48 | golang.org/x/tools v0.20.0 // indirect 49 | gopkg.in/yaml.v2 v2.4.0 // indirect 50 | ) 51 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= 2 | dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 3 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= 4 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 5 | github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= 6 | github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= 7 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= 8 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 9 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= 10 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= 11 | github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= 12 | github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 13 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 14 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 15 | github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= 16 | github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= 17 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 18 | github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= 19 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 21 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 22 | github.com/docker/cli v25.0.4+incompatible h1:DatRkJ+nrFoYL2HZUzjM5Z5sAmcA5XGp+AW0oEw2+cA= 23 | github.com/docker/cli v25.0.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= 24 | github.com/docker/cli v26.1.1+incompatible h1:bE1/uE2tCa08fMv+7ikLR/RDPoCqytwrLtkIkSzxLvw= 25 | github.com/docker/cli v26.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= 26 | github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE= 27 | github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 28 | github.com/docker/docker v26.1.1+incompatible h1:oI+4kkAgIwwb54b9OC7Xc3hSgu1RlJA/Lln/DF72djQ= 29 | github.com/docker/docker v26.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 30 | github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= 31 | github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= 32 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 33 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 34 | github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= 35 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 36 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 37 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 38 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 39 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 40 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 41 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 42 | github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= 43 | github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= 44 | github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= 45 | github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= 46 | github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= 47 | github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= 48 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 49 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 50 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 51 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 52 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 53 | github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= 54 | github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 55 | github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= 56 | github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= 57 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 58 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 59 | github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= 60 | github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= 61 | github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= 62 | github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= 63 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 64 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 65 | github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= 66 | github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= 67 | github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= 68 | github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= 69 | github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= 70 | github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= 71 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 72 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 73 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 74 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 75 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 76 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 77 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 78 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 79 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 80 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 81 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 82 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 83 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 84 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= 85 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 86 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= 87 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= 88 | github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= 89 | github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= 90 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 91 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 92 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 93 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 94 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 95 | golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= 96 | golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 97 | golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= 98 | golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= 99 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 100 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 101 | golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= 102 | golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 103 | golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= 104 | golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 105 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 106 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 107 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 108 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 109 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 110 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 111 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 112 | golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= 113 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 114 | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 115 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 116 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 117 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 118 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 119 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 120 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 121 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= 122 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 123 | golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= 124 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 125 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 126 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 127 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 128 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 129 | golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= 130 | golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 131 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 132 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 133 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 134 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 135 | golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= 136 | golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= 137 | golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= 138 | golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= 139 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 140 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 141 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 142 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 143 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 144 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 145 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 146 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 147 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 148 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 149 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 150 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 151 | gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= 152 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package psqldocker 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/ory/dockertest/v3" 7 | ) 8 | 9 | type options struct { 10 | containerName, 11 | imageTag, 12 | poolEndpoint, 13 | repository, 14 | dbPort string 15 | sqls []string 16 | pool *dockertest.Pool 17 | expirationSeconds uint 18 | pingRetryTimeout time.Duration 19 | } 20 | 21 | func defaultOptions() options { 22 | return options{ 23 | containerName: "go-psqldocker", 24 | imageTag: "alpine", 25 | repository: "postgres", 26 | poolEndpoint: "", 27 | dbPort: "5432", 28 | sqls: nil, 29 | pool: nil, 30 | expirationSeconds: 20, 31 | pingRetryTimeout: 20 * time.Second, 32 | } 33 | } 34 | 35 | // Option configures an BTC Node Docker. 36 | type Option interface { 37 | apply(*options) 38 | } 39 | 40 | type containerNameOption string 41 | 42 | func (c containerNameOption) apply(opts *options) { 43 | opts.containerName = string(c) 44 | } 45 | 46 | // WithContainerName configures the PSQL Container Name, if 47 | // empty, a random one will be picked. 48 | func WithContainerName(name string) Option { 49 | return containerNameOption(name) 50 | } 51 | 52 | type imageTagOption string 53 | 54 | func (t imageTagOption) apply(opts *options) { 55 | opts.imageTag = string(t) 56 | } 57 | 58 | // WithImageTag configures the PSQL Container image tag, default: alpine. 59 | func WithImageTag(tag string) Option { 60 | return imageTagOption(tag) 61 | } 62 | 63 | type useTimescaleDBOption bool 64 | 65 | func (useTimescaleDBOption) apply(opts *options) { 66 | opts.repository = "timescale/timescaledb" 67 | 68 | // Set to a valid image tag, but don't override unless it is the 69 | // default. 70 | if opts.imageTag == "alpine" { 71 | opts.imageTag = "latest-pg15" 72 | } 73 | } 74 | 75 | // WithTimescaleDB allows using the TimescaleDB repository and 76 | // images. This option also updates the image tag to 'latest-pg15' as 77 | // the default alpine is invalid. 78 | func WithTimescaleDB() Option { 79 | return useTimescaleDBOption(true) 80 | } 81 | 82 | type sqlOption string 83 | 84 | func (c sqlOption) apply(opts *options) { 85 | opts.sqls = append(opts.sqls, string(c)) 86 | } 87 | 88 | // WithSQL specifies a sqls file, to initiate the 89 | // db with. 90 | func WithSQL(sql string) Option { 91 | return sqlOption(sql) 92 | } 93 | 94 | type dbPortOption string 95 | 96 | func (c dbPortOption) apply(opts *options) { 97 | opts.dbPort = string(c) 98 | } 99 | 100 | // WithDBPort sets database port running in the container, default 5432. 101 | func WithDBPort(name string) Option { 102 | return dbPortOption(name) 103 | } 104 | 105 | type poolOption struct { 106 | p *dockertest.Pool 107 | } 108 | 109 | func (p poolOption) apply(opts *options) { 110 | opts.pool = p.p 111 | } 112 | 113 | // WithPool sets the docker container getPool. 114 | // ! This is mutually exclusive with WithPoolEndpoint, and an error 115 | // will be thrown if both are used. 116 | func WithPool(pool *dockertest.Pool) Option { 117 | return poolOption{pool} 118 | } 119 | 120 | type poolEndpoint struct { 121 | e string 122 | } 123 | 124 | func (p poolEndpoint) apply(opts *options) { 125 | opts.poolEndpoint = p.e 126 | } 127 | 128 | // WithPoolEndpoint sets the docker container pool endpoint. 129 | // ! This is mutually exclusive with WithPool, and an error 130 | // will be thrown if both are used. 131 | func WithPoolEndpoint(endpoint string) Option { 132 | return poolEndpoint{endpoint} 133 | } 134 | 135 | type pingRetryTimeout time.Duration 136 | 137 | func (p pingRetryTimeout) apply(opts *options) { 138 | opts.pingRetryTimeout = time.Duration(p) 139 | } 140 | 141 | // WithPingRetryTimeout sets the timeout in seconds 142 | // for the ping retry function. 143 | func WithPingRetryTimeout(seconds uint) Option { 144 | return pingRetryTimeout(time.Duration(seconds) * time.Second) 145 | } 146 | 147 | type expirationSeconds uint 148 | 149 | func (e expirationSeconds) apply(opts *options) { 150 | opts.expirationSeconds = uint(e) 151 | } 152 | 153 | // WithExpiration terminates the container after a period has passed. 154 | func WithExpiration(seconds uint) Option { 155 | return expirationSeconds(seconds) 156 | } 157 | -------------------------------------------------------------------------------- /psql_container.go: -------------------------------------------------------------------------------- 1 | package psqldocker 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "sync/atomic" 9 | "time" 10 | 11 | // import for init func. 12 | _ "github.com/jackc/pgx/v5/stdlib" 13 | "github.com/ory/dockertest/v3" 14 | "github.com/ory/dockertest/v3/docker" 15 | ) 16 | 17 | // ensure Container implements the io.Closer interface. 18 | var _ io.Closer = (*Container)(nil) 19 | 20 | // Container represents a Docker container 21 | // running a PostgreSQL image. 22 | type Container struct { 23 | psqlUser string 24 | psqlPassword string 25 | psqlDBName string 26 | psqlInstancePort string 27 | sqls []string 28 | 29 | runOptions *dockertest.RunOptions 30 | pool *dockertest.Pool 31 | poolEndpoint string 32 | containerExpiration uint 33 | dockerContainer *dockertest.Resource 34 | hostPort string 35 | pingRetryTimeout time.Duration 36 | 37 | closed atomic.Bool 38 | } 39 | 40 | // Port returns the container host port mapped 41 | // to the database running inside it. 42 | func (c *Container) Port() string { 43 | return c.hostPort 44 | } 45 | 46 | // Close removes the Docker container. 47 | func (c *Container) Close() error { 48 | if c.closed.Swap(true) { // returns true if already closed. 49 | // make Close() idempotent. 50 | return nil 51 | } 52 | 53 | return c.dockerContainer.Close() 54 | } 55 | 56 | // NewContainer starts a new psql database in a docker container. 57 | func NewContainer( 58 | user, 59 | password, 60 | dbName string, 61 | opts ...Option, 62 | ) (*Container, error) { 63 | options := defaultOptions() 64 | 65 | for i := range opts { 66 | opts[i].apply(&options) 67 | } 68 | 69 | container := initContainer(user, password, dbName, options) 70 | 71 | if err := container.start(); err != nil { 72 | return nil, fmt.Errorf("start container: %w", err) 73 | } 74 | 75 | return container, nil 76 | } 77 | 78 | func initContainer(user, 79 | password, 80 | dbName string, 81 | options options, 82 | ) *Container { 83 | return &Container{ 84 | psqlUser: user, 85 | psqlPassword: password, 86 | psqlDBName: dbName, 87 | psqlInstancePort: options.dbPort, 88 | sqls: options.sqls, 89 | runOptions: &dockertest.RunOptions{ 90 | Name: options.containerName, 91 | Cmd: []string{"-p " + options.dbPort}, 92 | Repository: options.repository, 93 | Tag: options.imageTag, 94 | ExposedPorts: []string{options.dbPort}, 95 | Env: envVars(user, password, dbName), 96 | }, 97 | pool: options.pool, 98 | poolEndpoint: options.poolEndpoint, 99 | containerExpiration: options.expirationSeconds, 100 | pingRetryTimeout: options.pingRetryTimeout, 101 | 102 | dockerContainer: nil, 103 | hostPort: "", 104 | } 105 | } 106 | 107 | func (c *Container) start() error { 108 | pool, err := getPool(c.pool, c.poolEndpoint, c.pingRetryTimeout) 109 | if err != nil { 110 | return fmt.Errorf("get pool: %w", err) 111 | } 112 | 113 | if err := pool.Client.Ping(); err != nil { 114 | return fmt.Errorf("ping docker server: %w", err) 115 | } 116 | 117 | c.pool = pool 118 | 119 | res, err := pool.RunWithOptions( 120 | c.runOptions, 121 | func(config *docker.HostConfig) { 122 | config.AutoRemove = true 123 | config.RestartPolicy = docker.RestartPolicy{Name: "no"} 124 | }, 125 | ) 126 | if err != nil { 127 | return fmt.Errorf("start container: %w", err) 128 | } 129 | 130 | c.dockerContainer = res 131 | c.hostPort = c.dockerContainer.GetPort(c.psqlInstancePort + "/tcp") 132 | 133 | if err := c.dockerContainer.Expire(c.containerExpiration); err != nil { 134 | return handleErrWithClose(fmt.Errorf("expire: %w", err), c.dockerContainer) 135 | } 136 | 137 | if err := pool.Retry( 138 | func() error { 139 | return pingDB( 140 | c.psqlUser, 141 | c.psqlPassword, 142 | c.psqlDBName, 143 | c.hostPort, 144 | ) 145 | }); err != nil { 146 | return handleErrWithClose(fmt.Errorf("ping db: %w", err), c.dockerContainer) 147 | } 148 | 149 | if err := executeSQLs( 150 | c.psqlUser, 151 | c.psqlPassword, 152 | c.psqlDBName, 153 | c.hostPort, 154 | c.sqls, 155 | ); err != nil { 156 | return handleErrWithClose(fmt.Errorf("execute sqls: %w", err), c.dockerContainer) 157 | } 158 | 159 | return nil 160 | } 161 | 162 | func handleErrWithClose(err error, dockerContainer *dockertest.Resource) error { 163 | if closeErr := dockerContainer.Close(); closeErr != nil { 164 | return errors.Join(fmt.Errorf("close: %w", closeErr), err) 165 | } 166 | 167 | return err 168 | } 169 | 170 | // ErrWithPoolAndWithPoolEndpoint is returned when both 171 | // WithPool and WithPoolEndpoint options are given to the 172 | // NewContainer constructor. 173 | var ErrWithPoolAndWithPoolEndpoint = errors.New( 174 | "with pool and with pool endpoint are mutually exclusive", 175 | ) 176 | 177 | func getPool( 178 | existingPool *dockertest.Pool, 179 | poolEndpoint string, 180 | pingRetryTimeout time.Duration, 181 | ) (*dockertest.Pool, error) { 182 | if existingPool != nil && poolEndpoint != "" { 183 | return nil, ErrWithPoolAndWithPoolEndpoint 184 | } 185 | 186 | if existingPool != nil { 187 | existingPool.MaxWait = pingRetryTimeout 188 | 189 | return existingPool, nil 190 | } 191 | 192 | newPool, err := dockertest.NewPool(poolEndpoint) 193 | if err != nil { 194 | return nil, fmt.Errorf("dockertest new pool: %w", err) 195 | } 196 | 197 | newPool.MaxWait = pingRetryTimeout 198 | 199 | return newPool, nil 200 | } 201 | 202 | func envVars( 203 | user, 204 | password, 205 | dbName string, 206 | ) []string { 207 | return []string{ 208 | "POSTGRES_PASSWORD=" + password, 209 | "POSTGRES_USER=" + user, 210 | "POSTGRES_DB=" + dbName, 211 | } 212 | } 213 | 214 | func pingDB( 215 | user, 216 | password, 217 | dbName, 218 | port string, 219 | ) error { 220 | db, err := sql.Open("pgx", fmt.Sprintf( 221 | "user=%s "+ 222 | "password=%s "+ 223 | "dbname=%s "+ 224 | "host=localhost "+ 225 | "port=%s "+ 226 | "sslmode=disable", 227 | user, 228 | password, 229 | dbName, 230 | port)) 231 | if err != nil { 232 | return fmt.Errorf("sql open: %w", err) 233 | } 234 | 235 | defer db.Close() 236 | 237 | if err := db.Ping(); err != nil { 238 | return fmt.Errorf("ping: %w", err) 239 | } 240 | 241 | if err := db.Close(); err != nil { 242 | return fmt.Errorf("close: %w", err) 243 | } 244 | 245 | return nil 246 | } 247 | 248 | func executeSQLs( 249 | user, 250 | password, 251 | dbName, 252 | hostPort string, 253 | sqls []string, 254 | ) error { 255 | db, err := sql.Open( 256 | "pgx", 257 | fmt.Sprintf( 258 | "user=%s "+ 259 | "password=%s "+ 260 | "dbname=%s "+ 261 | "host=localhost "+ 262 | "port=%s "+ 263 | "sslmode=disable", 264 | user, 265 | password, 266 | dbName, 267 | hostPort), 268 | ) 269 | if err != nil { 270 | return fmt.Errorf("open db: %w", err) 271 | } 272 | 273 | defer db.Close() 274 | 275 | for i := range sqls { 276 | _, err = db.Exec(sqls[i]) 277 | if err != nil { 278 | return fmt.Errorf("execute sql %d: %w", i, err) 279 | } 280 | } 281 | 282 | if err := db.Close(); err != nil { 283 | return fmt.Errorf("close db: %w", err) 284 | } 285 | 286 | return nil 287 | } 288 | -------------------------------------------------------------------------------- /psql_container_test.go: -------------------------------------------------------------------------------- 1 | package psqldocker_test 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/adrianbrad/psqldocker" 9 | "github.com/jackc/pgx/v5/pgconn" 10 | _ "github.com/jackc/pgx/v5/stdlib" 11 | "github.com/matryer/is" 12 | "github.com/ory/dockertest/v3" 13 | "github.com/ory/dockertest/v3/docker" 14 | ) 15 | 16 | func TestNewContainer(t *testing.T) { 17 | t.Parallel() 18 | 19 | const ( 20 | user = "user" 21 | password = "pass" 22 | dbName = "test" 23 | ) 24 | 25 | t.Run("AllOptions", func(t *testing.T) { 26 | t.Parallel() 27 | 28 | i := is.New(t) 29 | 30 | p, err := dockertest.NewPool("") 31 | i.NoErr(err) 32 | 33 | c, err := psqldocker.NewContainer( 34 | user, 35 | password, 36 | dbName, 37 | psqldocker.WithContainerName(containerNameFromTest(t)), 38 | psqldocker.WithDBPort("5432"), 39 | psqldocker.WithPool(p), 40 | psqldocker.WithImageTag("alpine"), 41 | psqldocker.WithPoolEndpoint(""), 42 | psqldocker.WithSQL( 43 | "CREATE TABLE users(user_id UUID PRIMARY KEY);", 44 | ), 45 | psqldocker.WithPingRetryTimeout(20), 46 | psqldocker.WithExpiration(20), 47 | ) 48 | i.NoErr(err) 49 | 50 | t.Logf("container started on port: %s", c.Port()) 51 | 52 | err = c.Close() 53 | i.NoErr(err) 54 | }) 55 | 56 | t.Run("NoOptions", func(t *testing.T) { 57 | t.Parallel() 58 | 59 | i := is.New(t) 60 | 61 | c, err := psqldocker.NewContainer( 62 | user, 63 | password, 64 | dbName, 65 | psqldocker.WithContainerName(containerNameFromTest(t)), 66 | ) 67 | i.NoErr(err) 68 | 69 | err = c.Close() 70 | i.NoErr(err) 71 | }) 72 | 73 | t.Run("WithTimescaleDBOption", func(t *testing.T) { 74 | t.Parallel() 75 | 76 | i := is.New(t) 77 | 78 | c, err := psqldocker.NewContainer( 79 | user, 80 | password, 81 | dbName, 82 | psqldocker.WithTimescaleDB(), 83 | psqldocker.WithContainerName(containerNameFromTest(t)), 84 | ) 85 | i.NoErr(err) 86 | 87 | err = c.Close() 88 | i.NoErr(err) 89 | }) 90 | 91 | t.Run("WithTimescaleDBOptionAndCustomTag", func(t *testing.T) { 92 | t.Parallel() 93 | 94 | i := is.New(t) 95 | 96 | c, err := psqldocker.NewContainer( 97 | user, 98 | password, 99 | dbName, 100 | psqldocker.WithImageTag("latest-pg14"), 101 | psqldocker.WithTimescaleDB(), 102 | psqldocker.WithContainerName(containerNameFromTest(t)), 103 | ) 104 | i.NoErr(err) 105 | 106 | err = c.Close() 107 | i.NoErr(err) 108 | }) 109 | 110 | t.Run("InvalidTagFormat", func(t *testing.T) { 111 | t.Parallel() 112 | 113 | i := is.New(t) 114 | 115 | _, err := psqldocker.NewContainer( 116 | user, 117 | password, 118 | dbName, 119 | psqldocker.WithImageTag("error:latest"), 120 | ) 121 | 122 | var dockerErr *docker.Error 123 | 124 | i.True(errors.As(err, &dockerErr)) 125 | i.Equal( 126 | "invalid tag format", 127 | dockerErr.Message, 128 | ) 129 | }) 130 | 131 | t.Run("InvalidSQL", func(t *testing.T) { 132 | t.Parallel() 133 | 134 | i := is.New(t) 135 | 136 | _, err := psqldocker.NewContainer( 137 | user, 138 | password, 139 | dbName, 140 | psqldocker.WithContainerName(containerNameFromTest(t)), 141 | psqldocker.WithSQL("error"), 142 | ) 143 | 144 | var pgErr *pgconn.PgError 145 | 146 | if errors.As(err, &pgErr) { 147 | i.Equal("syntax error at or near \"error\"", pgErr.Message) 148 | return 149 | } 150 | 151 | t.Errorf("expected error to be of type *pgconn.PgError, got %T", err) 152 | }) 153 | 154 | t.Run("ProvideWithPoolAndWithPoolEndpoint", func(t *testing.T) { 155 | t.Parallel() 156 | 157 | i := is.New(t) 158 | 159 | _, err := psqldocker.NewContainer( 160 | user, 161 | password, 162 | dbName, 163 | psqldocker.WithPool(new(dockertest.Pool)), 164 | psqldocker.WithPoolEndpoint("endpoint"), 165 | ) 166 | i.True(errors.Is( 167 | err, 168 | psqldocker.ErrWithPoolAndWithPoolEndpoint, 169 | )) 170 | }) 171 | 172 | t.Run("InvalidPoolEndpointURL", func(t *testing.T) { 173 | t.Parallel() 174 | 175 | i := is.New(t) 176 | 177 | _, err := psqldocker.NewContainer( 178 | user, 179 | password, 180 | dbName, 181 | psqldocker.WithPoolEndpoint("://endpoint"), 182 | ) 183 | i.Equal( 184 | "start container: get pool: dockertest new pool: invalid endpoint", 185 | err.Error(), 186 | ) 187 | }) 188 | 189 | t.Run("PingFail", func(t *testing.T) { 190 | t.Parallel() 191 | 192 | i := is.New(t) 193 | 194 | _, err := psqldocker.NewContainer( 195 | user, 196 | password, 197 | dbName, 198 | psqldocker.WithContainerName(containerNameFromTest(t)), 199 | psqldocker.WithPingRetryTimeout(1), 200 | ) 201 | i.True( 202 | strings.Contains( 203 | err.Error(), 204 | "ping db: reached retry deadline: "+ 205 | "ping: failed to connect to `host=localhost user=user database=test`", 206 | ), 207 | ) 208 | }) 209 | } 210 | 211 | func containerNameFromTest(t *testing.T) string { 212 | t.Helper() 213 | 214 | containerName := strings.Split(t.Name(), "/") 215 | 216 | return containerName[len(containerName)-1] 217 | } 218 | --------------------------------------------------------------------------------