diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 42dfb1f..1353df5 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -5,7 +5,6 @@ on: - v* branches: - master - - main pull_request: permissions: @@ -16,12 +15,12 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v6 with: - go-version: '1.20.x' + go-version: '1.25.x' - name: Build run: go build -v ./... diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 768b11d..462edef 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -5,42 +5,22 @@ on: - v* branches: - master - - main pull_request: + permissions: contents: read - # Optional: allow read access to pull request. Use with `only-new-issues` option. pull-requests: read + jobs: golangci: name: lint runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v6 with: - go-version: '1.20.x' - - uses: actions/checkout@v4 + go-version: '1.25.x' + - uses: actions/checkout@v6 - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v9 with: - # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version - version: v1.54.2 - - # Optional: working directory, useful for monorepos - # working-directory: somedir - - # Optional: golangci-lint command line arguments. - # args: --issues-exit-code=0 - - # Optional: show only new issues if it's a pull request. The default value is `false`. - # only-new-issues: true - - # Optional: if set to true then the all caching functionality will be complete disabled, - # takes precedence over all other caching options. - # skip-cache: true - - # Optional: if set to true then the action don't cache or restore ~/go/pkg. - # skip-pkg-cache: true - - # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. - # skip-build-cache: true + version: v2.8.0 diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 68bb655..39a7dac 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -14,20 +14,21 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v6 with: - go-version: '1.20.x' + go-version: '1.25.x' - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v4 + uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser version: latest args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} diff --git a/.golangci.yml b/.golangci.yml index 3be6dec..d90826b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,82 +1,159 @@ -run: - # default concurrency is a available CPU number - concurrency: 4 - - # timeout for analysis, e.g. 30s, 5m, default is 1m - deadline: 30m - - # exit code when at least one issue was found, default is 1 - issues-exit-code: 1 - - # include test files or not, default is true - tests: true - - # skip files - skip-files: - - ".*\\_gen\\.go$" - -output: - # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" - format: colored-line-number - - # print lines of code with issue, default is true - print-issued-lines: true - - # print linter name in the end of issue text, default is true - print-linter-name: true - -linters-settings: - goimports: - # put imports beginning with prefix after 3rd-party packages; - # it's a comma-separated list of prefixes - local-prefixes: github.com/vmkteam/zenrpc - gocritic: - # Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint` run to see all tags and checks. - # Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags". - enabled-checks: - - appendAssign - - appendCombine - - assignOp - - badCond - - boolExprSimplify - - captLocal - - caseOrder - - defaultCaseOrder - - dupArg - - dupBranchBody - - dupCase - - dupSubExpr - - elseif - - emptyFallthrough - - emptyStringTest - - equalFold - - exitAfterDefer - - flagName - - hexLiteral - - indexAlloc - - nilValReturn - - offBy1 - - regexpMust - - sloppyLen - - switchTrue - - wrapperFunc - - yodaStyleExpr +version: "2" linters: - enable-all: false - disable-all: true enable: + - asasalint + - asciicheck + - bidichk - bodyclose - - dogsled + - canonicalheader + - copyloopvar + - cyclop - dupl + - durationcheck + - errcheck + - errname + - errorlint + - exhaustive + - exptostd + - fatcontext + #- forbidigo + - funcorder + - funlen + - gocheckcompilerdirectives + - gochecknoinits + - gochecksumtype + - gocognit - goconst - - gofmt - - goimports - - gosimple - gocritic + - gocyclo + - goprintffuncname - govet + - iface - ineffassign + - intrange + - loggercheck + - makezero + - mirror + - nestif + - nilerr + - nilnesserr + - noctx + - nosprintfhostport + - perfsprint + - predeclared + - promlinter + - protogetter + - recvcheck + - rowserrcheck + - sloglint + - spancheck + - sqlclosecheck - staticcheck - - typecheck + - testableexamples + - testifylint + - tparallel - unconvert + - unparam - unused + - usestdlibvars + - usetesting + - wastedassign + - whitespace + - decorder + - ginkgolinter + - goheader + - inamedparam + - interfacebloat + - prealloc + - zerologlint + settings: + cyclop: + max-complexity: 30 + package-average: 10.0 + + errcheck: + check-type-assertions: true + + exhaustive: + check: + - switch + - map + + funcorder: + struct-method: false + + funlen: + lines: 100 + statements: 50 + + gochecksumtype: + default-signifies-exhaustive: false + + gocognit: + min-complexity: 32 + + gocritic: + settings: + captLocal: + paramsOnly: false + underef: + skipRecvDeref: false + + govet: + enable-all: true + disable: + - fieldalignment + settings: + shadow: + strict: false + + inamedparam: + skip-single-param: true + + nakedret: + max-func-lines: 0 + + perfsprint: + strconcat: false + + staticcheck: + checks: + - all + - -ST1000 + - -ST1016 + - -QF1008 + + usetesting: + os-temp-dir: true + + exclusions: + warn-unused: false + presets: + - std-error-handling + - common-false-positives + rules: + - source: 'TODO' + linters: [ godot ] + - path: '_test\.go' + linters: + - bodyclose + - dupl + - errcheck + - funlen + - goconst + - noctx + +formatters: + enable: + - gci + settings: + gci: + sections: + - standard + - localmodule + - default + no-inline-comments: true + no-prefix-comments: true + custom-order: true + no-lex-order: true diff --git a/.goreleaser.yaml b/.goreleaser.yaml index b56851b..fa7511a 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -1,3 +1,5 @@ +version: 2 + builds: - env: - CGO_ENABLED=0 @@ -5,3 +7,17 @@ builds: - linux - windows - darwin + goarch: + - amd64 + - arm64 + +homebrew_casks: + - name: pgmigrator + binaries: + - pgmigrator + homepage: "https://github.com/vmkteam/pgmigrator" + description: "Command-line tool for PostgreSQL migrations" + repository: + owner: vmkteam + name: homebrew-tap + token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" diff --git a/Makefile b/Makefile index 671d930..c4bb28f 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,14 @@ PKG := `go list -f {{.Dir}} ./...` +LINT_VERSION := v2.8.0 + fmt: - @goimports -local "github.com/vmkteam/pgmigrator" -l -w $(PKG) + @golangci-lint fmt lint: - @golangci-lint run -c .golangci.yml + @golangci-lint version + @golangci-lint config verify + @golangci-lint run test: @go test -v ./... diff --git a/README.md b/README.md index 4bc41b0..898453c 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,6 @@ Limitations * Migrations filename mask: `YYYYY-MM-DDD-.sql` / `YYYYY-MM-DD--NONTR.sql` * Migration types: UP only * Algorithm: applies sorted migrations files that are in the folder and fit the file mask, except for what is already applied in the database -* The program works only with the configuration file located in the folder with migrations. - - FAQ -- Q: Why only up migrations?
@@ -89,6 +86,7 @@ Run Flags: -c, --config string configuration file (default "pgmigrator.toml") + -d, --dir string path to migrations directory -h, --help help for pgmigrator -v, --version version for pgmigrator @@ -97,7 +95,7 @@ Run Any command supports an argument in the form of a number. For `last` it is the number of last migrations (default is 5). For all others - the number of the file from `plan` to which to apply migrations. If no argument is passed, there are no restrictions (or default ones are used). The base directory for migrations is the one where the configuration file is located. -That is, you can call `pgmigrator --config docs/patches/pgmigrator.toml plan` and it will take all migrations from the `docs/patches` folder. +You can override it with `--dir` flag: `pgmigrator --config pgmigrator.toml --dir docs/patches plan`. ## Commands @@ -208,6 +206,14 @@ It is recommended to have a different migrations table for each schema. * transactional - transactional flag (false для NONTR migrations) * md5sum - md5 hash of migration file -### Docker images +### Install + +#### Homebrew +```bash +brew tap vmkteam/tap +brew install pgmigrator +``` + +#### Docker images - [Docker Hub](https://hub.docker.com/r/vmkteam/pgmigrator) - Packages Tab in this repo diff --git a/README.ru.md b/README.ru.md index 28c4481..d29523e 100644 --- a/README.ru.md +++ b/README.ru.md @@ -17,9 +17,6 @@ pgmigrator очень простая утилита, предназначенн * Маска файлов миграций: `YYYY-MM-DDD-.sql` / `YYYY-MM-DD--NONTR.sql` * Типы миграций: только UP * Алгоритм: применяем к базе данных отсортированные файлы с миграциями, которые есть в папке и подходят по маске файла, кроме тех, которые уже применены к базе -* Программа работает только с конфигурационным файлом, расположенным в папке с миграциями. - - FAQ -- Q: Почему только up миграции?
@@ -91,6 +88,7 @@ A: Цель - простая утилита, которая работает с Flags: -c, --config string configuration file (default "pgmigrator.toml") + -d, --dir string path to migrations directory -h, --help help for pgmigrator -v, --version version for pgmigrator @@ -99,7 +97,7 @@ A: Цель - простая утилита, которая работает с Любая команда поддерживает аргумент в виде числа. Для `last` - это количество последних миграций (по умолчанию 5). Для всех остальных – номер файла из `plan`, до которого применять миграции. Если аргумент не передан, то ограничений нет (или используется значение по умолчанию). Базовая директория для миграций - та, в которой расположен файл конфигурации. -То есть можно вызвать `pgmigrator --config docs/patches/pgmigrator.toml plan` - и он возьмет все миграции из папки `docs/patches`. +Можно переопределить через флаг `--dir`: `pgmigrator --config pgmigrator.toml --dir docs/patches plan`. ### Plan @@ -224,6 +222,14 @@ A: Цель - простая утилита, которая работает с При обновлении из гита можно вызвать `pgmigrator plan` из `docs/patches` и посмотреть новые патчи. Внедрять можно на любой стадии проекта. -### Docker образы +### Установка + +#### Homebrew +```bash +brew tap vmkteam/tap +brew install pgmigrator +``` + +#### Docker образы - [Docker Hub](https://hub.docker.com/vmkteam/pgmigrator) - Вкладка Packages в этом репозитории diff --git a/go.mod b/go.mod index 1156def..4f312a2 100644 --- a/go.mod +++ b/go.mod @@ -1,32 +1,33 @@ module github.com/vmkteam/pgmigrator -go 1.20 +go 1.25.0 require ( - github.com/BurntSushi/toml v1.3.2 - github.com/fatih/color v1.15.0 - github.com/go-pg/pg/v10 v10.11.1 - github.com/rodaine/table v1.1.0 - github.com/smartystreets/goconvey v1.8.1 - github.com/spf13/cobra v1.7.0 + github.com/BurntSushi/toml v1.6.0 + github.com/fatih/color v1.19.0 + github.com/go-pg/pg/v10 v10.15.0 + github.com/rodaine/table v1.3.1 + github.com/spf13/cobra v1.10.2 + github.com/stretchr/testify v1.11.1 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/go-pg/zerochecker v0.2.0 // indirect - github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect - github.com/jtolds/gls v4.20.0+incompatible // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect - github.com/smarty/assertions v1.15.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/vmihailenco/bufpool v0.1.11 // indirect - github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser v0.1.2 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - golang.org/x/crypto v0.1.0 // indirect - golang.org/x/sys v0.6.0 // indirect - mellium.im/sasl v0.3.1 // indirect + golang.org/x/crypto v0.49.0 // indirect + golang.org/x/sys v0.42.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + mellium.im/sasl v0.3.2 // indirect ) diff --git a/go.sum b/go.sum index 3d1a0d3..f09a7eb 100644 --- a/go.sum +++ b/go.sum @@ -1,204 +1,90 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= +github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY= +github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 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/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.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/go-pg/pg/v10 v10.11.1 h1:vYwbFpqoMpTDphnzIPshPPepdy3VpzD8qo29OFKp4vo= -github.com/go-pg/pg/v10 v10.11.1/go.mod h1:ExJWndhDNNftBdw1Ow83xqpSf4WMSJK8urmXD5VXS1I= +github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/go-pg/pg/v10 v10.15.0 h1:6DQwbaxJz/e4wvgzbxBkBLiL/Uuk87MGgHhkURtzx24= +github.com/go-pg/pg/v10 v10.15.0/go.mod h1:FIn/x04hahOf9ywQ1p68rXqaDVbTRLYlu4MQR0lhoB8= github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU= github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -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.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -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.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -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.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.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 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/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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w= +github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M= github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rodaine/table v1.1.0 h1:/fUlCSdjamMY8VifdQRIu3VWZXYLY7QHFkVorS8NTr4= -github.com/rodaine/table v1.1.0/go.mod h1:Qu3q5wi1jTQD6B6HsP6szie/S4w1QUQ8pq22pz9iL8g= +github.com/rodaine/table v1.3.1 h1:jBVgg1bEu5EzEdYSrwUUlQpayDtkvtTmgFS0FPAxOq8= +github.com/rodaine/table v1.3.1/go.mod h1:VYCJRCHa2DpD25uFALcB6hi5ECF3eEJQVhCXRjHgXc4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -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/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= -github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +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.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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94= github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ= -github.com/vmihailenco/msgpack/v5 v5.3.4 h1:qMKAwOV+meBw2Y8k9cVwAy7qErtYCwBzZ2ellBfvnqc= -github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc= github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -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-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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-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-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.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.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -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-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.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.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 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-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 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.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -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= -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= -mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= -mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw= +mellium.im/sasl v0.3.2 h1:PT6Xp7ccn9XaXAnJ03FcEjmAn7kK1x7aoXV6F+Vmrl0= +mellium.im/sasl v0.3.2/go.mod h1:NKXDi1zkr+BlMHLQjY3ofYuU4KSPFxknb8mfEu6SveY= diff --git a/main.go b/main.go index a58671b..4b1da5a 100644 --- a/main.go +++ b/main.go @@ -7,19 +7,24 @@ import ( "path/filepath" "runtime/debug" + "github.com/vmkteam/pgmigrator/pkg/app" + "github.com/vmkteam/pgmigrator/pkg/migrator" + "github.com/BurntSushi/toml" "github.com/go-pg/pg/v10" "github.com/spf13/cobra" - "github.com/vmkteam/pgmigrator/pkg/app" - "github.com/vmkteam/pgmigrator/pkg/migrator" ) -var cfgFile string +var ( + cfgFile string + migrationsDir string +) func main() { log.SetFlags(0) rootCmd := newRootCmd() rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", app.DefaultConfigFile, "configuration file") + rootCmd.PersistentFlags().StringVarP(&migrationsDir, "dir", "d", "", "path to migrations directory") rootCmd.InitDefaultVersionFlag() rootCmd.InitDefaultHelpFlag() exitOnErr(rootCmd.ParseFlags(os.Args)) @@ -41,7 +46,13 @@ func main() { cfg.ConfigFile, err = filepath.Abs(cfgFile) exitOnErr(err) - mg = migrator.NewMigrator(pg.Connect(cfg.Database), cfg.App, filepath.Dir(cfg.ConfigFile)) + rootDir := filepath.Dir(cfg.ConfigFile) + if migrationsDir != "" { + rootDir, err = filepath.Abs(migrationsDir) + exitOnErr(err) + } + + mg = migrator.NewMigrator(pg.Connect(cfg.Database), cfg.App, rootDir) } // create app and run diff --git a/pkg/app/app.go b/pkg/app/app.go index 8800175..3a2a1ef 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -11,13 +11,13 @@ import ( "sync" "time" + "github.com/vmkteam/pgmigrator/pkg/migrator" + "github.com/BurntSushi/toml" "github.com/fatih/color" "github.com/go-pg/pg/v10" "github.com/rodaine/table" "github.com/spf13/cobra" - - "github.com/vmkteam/pgmigrator/pkg/migrator" ) const ( @@ -206,7 +206,7 @@ If applied, applies only migrations from plan. By default: 5`, // calculate count cnt, err := count(args) if err != nil { - log.Fatal("invalid argument") + return errors.New("invalid argument") } else if cnt > len(mm) { cnt = len(mm) } @@ -245,7 +245,7 @@ If applied, runs only migrations. By default: 5`, // calculate count cnt, err := count(args) if err != nil { - log.Fatal("invalid argument") + return errors.New("invalid argument") } else if cnt > len(mm) { cnt = len(mm) } @@ -285,7 +285,7 @@ If applied, marks only first migrations displayed in plan. Defau // calculate count cnt, err := count(args) if err != nil { - log.Fatal("invalid argument") + return errors.New("invalid argument") } else if cnt > len(mm) { cnt = len(mm) } diff --git a/pkg/migrator/migrator.go b/pkg/migrator/migrator.go index df2384f..2f52081 100644 --- a/pkg/migrator/migrator.go +++ b/pkg/migrator/migrator.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + "path/filepath" "regexp" "sort" "strings" @@ -15,16 +16,18 @@ import ( ) type Migrator struct { - db *pg.DB - cfg Config - rootDir string // patches + db *pg.DB + cfg Config + rootDir string // patches + fileMask *regexp.Regexp } func NewMigrator(db *pg.DB, cfg Config, rootDir string) *Migrator { m := &Migrator{ - db: db, - cfg: cfg, - rootDir: rootDir, + db: db, + cfg: cfg, + rootDir: rootDir, + fileMask: regexp.MustCompile(cfg.FileMask), } if db != nil { @@ -61,9 +64,8 @@ func (m *Migrator) readAllFiles() ([]string, error) { } var filenames []string - var namesToExecRegex = regexp.MustCompile(m.cfg.FileMask) for _, f := range files { - if f.IsDir() || !namesToExecRegex.MatchString(f.Name()) { + if f.IsDir() || !m.fileMask.MatchString(f.Name()) { continue } else if strings.HasSuffix(f.Name(), "MANUAL.sql") { // skip manual migrations @@ -176,7 +178,7 @@ func finishTxOnErr(tx *pg.Tx, err error) error { } if er != nil { - err = fmt.Errorf("failed to finish transation er=%v: %w", er, err) + err = fmt.Errorf("failed to finish transaction er=%w: %w", er, err) } return err @@ -350,7 +352,7 @@ func (m *Migrator) skipMigrations(ctx context.Context, mm Migrations, chCurrentF func alwaysRollbackTx(tx *pg.Tx, err error) error { if er := tx.Rollback(); er != nil { - err = fmt.Errorf("failed to finish transation er=%v: %w", er, err) + err = fmt.Errorf("failed to finish transaction er=%w: %w", er, err) } return err @@ -429,13 +431,13 @@ func (m *Migrator) Redo(ctx context.Context, chCurrentFile chan string) (*PgMigr var pm PgMigration if err := m.db.ModelContext(ctx, &pm).Order(`id desc`).Limit(1).Select(); err != nil { if errors.Is(err, pg.ErrNoRows) { - return nil, fmt.Errorf(`applied migrations were not found`) + return nil, errors.New(`applied migrations were not found`) } return nil, fmt.Errorf(`fetch last migration failed: %w`, err) } - // create and check if exists - if _, err := NewMigration(m.rootDir, pm.Filename); err != nil { + // check if migration file exists + if _, err := os.Stat(filepath.Join(m.rootDir, pm.Filename)); err != nil { return nil, fmt.Errorf(`find migration file "%s" failed: %w`, pm.Filename, err) } diff --git a/pkg/migrator/migrator_test.go b/pkg/migrator/migrator_test.go index 0824827..53d05bd 100644 --- a/pkg/migrator/migrator_test.go +++ b/pkg/migrator/migrator_test.go @@ -6,11 +6,12 @@ import ( "testing" "github.com/go-pg/pg/v10" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var ( - testDb *pg.DB + testDB *pg.DB testConfig Config testMigrator *Migrator dbConn = env("DB_CONN", "postgres://postgres:postgres@localhost:5432/pgmigrator?sslmode=disable") @@ -33,76 +34,69 @@ func NewTestDB() *pg.DB { } func TestMain(m *testing.M) { - testDb = NewTestDB() + testDB = NewTestDB() testConfig = NewDefaultConfig() - testMigrator = NewMigrator(testDb, testConfig, "testdata") + testMigrator = NewMigrator(testDB, testConfig, "testdata") os.Exit(m.Run()) } func TestMigrator_Plan(t *testing.T) { ctx := context.Background() - Convey("TestMigrator_Plan", t, func() { - err := recreateSchema() - So(err, ShouldBeNil) + err := recreateSchema() + require.NoError(t, err) - want := []string{ - "2022-12-12-01-create-table-statuses.sql", - "2022-12-12-02-create-table-news.sql", - "2022-12-12-03-add-comments-news-NONTR.sql", - "2022-12-13-01-create-categories-table.sql", - "2022-12-13-02-create-tags-table.sql", - } - got, err := testMigrator.Plan(ctx) - So(err, ShouldBeNil) - So(got, ShouldResemble, want) - }) + want := []string{ + "2022-12-12-01-create-table-statuses.sql", + "2022-12-12-02-create-table-news.sql", + "2022-12-12-03-add-comments-news-NONTR.sql", + "2022-12-13-01-create-categories-table.sql", + "2022-12-13-02-create-tags-table.sql", + } + got, err := testMigrator.Plan(ctx) + require.NoError(t, err) + assert.Equal(t, want, got) } func TestMigrator_readFiles(t *testing.T) { - Convey("TestMigrator_readFiles", t, func() { - want := []string{ - "2022-12-12-01-create-table-statuses.sql", - "2022-12-12-02-create-table-news.sql", - "2022-12-12-03-add-comments-news-NONTR.sql", - "2022-12-13-01-create-categories-table.sql", - "2022-12-13-02-create-tags-table.sql", - } + want := []string{ + "2022-12-12-01-create-table-statuses.sql", + "2022-12-12-02-create-table-news.sql", + "2022-12-12-03-add-comments-news-NONTR.sql", + "2022-12-13-01-create-categories-table.sql", + "2022-12-13-02-create-tags-table.sql", + } - filenames, err := testMigrator.readAllFiles() - So(err, ShouldBeNil) - So(filenames, ShouldResemble, want) - }) + filenames, err := testMigrator.readAllFiles() + require.NoError(t, err) + assert.Equal(t, want, filenames) } func TestMigrator_compareFilenames(t *testing.T) { - Convey("TestMigrator_compareFilenames", t, func() { - dirFiles := []string{ - "2022-12-12-01-create-table-statuses.sql", - "2022-12-12-02-create-table-news.sql", - "2022-12-12-03-add-comments-news-NONTR.sql", - "2022-12-13-01-create-categories-table.sql", - "2022-12-13-02-create-tags-table.sql", - } - completedFiles := []string{"2022-12-12-01-create-table-statuses.sql"} - want := []string{ - "2022-12-12-02-create-table-news.sql", - "2022-12-12-03-add-comments-news-NONTR.sql", - "2022-12-13-01-create-categories-table.sql", - "2022-12-13-02-create-tags-table.sql", - } - res := testMigrator.removeCompleted(dirFiles, completedFiles) - So(res, ShouldResemble, want) - }) + dirFiles := []string{ + "2022-12-12-01-create-table-statuses.sql", + "2022-12-12-02-create-table-news.sql", + "2022-12-12-03-add-comments-news-NONTR.sql", + "2022-12-13-01-create-categories-table.sql", + "2022-12-13-02-create-tags-table.sql", + } + completedFiles := []string{"2022-12-12-01-create-table-statuses.sql"} + want := []string{ + "2022-12-12-02-create-table-news.sql", + "2022-12-12-03-add-comments-news-NONTR.sql", + "2022-12-13-01-create-categories-table.sql", + "2022-12-13-02-create-tags-table.sql", + } + res := testMigrator.removeCompleted(dirFiles, completedFiles) + assert.Equal(t, want, res) } func TestNewMigration(t *testing.T) { - Convey("TestMigrator_compareFilenames", t, func() { - res, err := NewMigration(testMigrator.rootDir, "2022-12-12-01-create-table-statuses.sql") - So(err, ShouldBeNil) - So(res, ShouldResemble, Migration{ - Filename: "2022-12-12-01-create-table-statuses.sql", - Data: []byte(`CREATE TABLE "statuses" + res, err := NewMigration(testMigrator.rootDir, "2022-12-12-01-create-table-statuses.sql") + require.NoError(t, err) + assert.Equal(t, Migration{ + Filename: "2022-12-12-01-create-table-statuses.sql", + Data: []byte(`CREATE TABLE "statuses" ( "statusId" SERIAL NOT NULL, "title" varchar(255) NOT NULL, @@ -111,106 +105,83 @@ func TestNewMigration(t *testing.T) { CONSTRAINT "statuses_alias_key" UNIQUE ("alias") ); `), - Md5Sum: "463fe73a85e13dd55fe210904ec19d7c", - Transactional: true, - }) - }) - + Md5Sum: "463fe73a85e13dd55fe210904ec19d7c", + Transactional: true, + }, res) } func TestMigrator_prepareMigrationsToRun(t *testing.T) { - Convey("TestMigrator_prepareMigrationsToRun", t, func() { - dirFiles := []string{ - "2022-12-12-01-create-table-statuses.sql", - "2022-12-12-02-create-table-news.sql", - "2022-12-12-03-add-comments-news-NONTR.sql", - "2022-12-13-01-create-categories-table.sql", - "2022-12-13-02-create-tags-table.sql", - } - want := []Migration{ - { - Filename: "2022-12-12-01-create-table-statuses.sql", - Transactional: true, - }, - { - Filename: "2022-12-12-02-create-table-news.sql", - Transactional: true, - }, - { - Filename: "2022-12-12-03-add-comments-news-NONTR.sql", - Transactional: false, - }, - { - Filename: "2022-12-13-01-create-categories-table.sql", - Transactional: true, - }, - { - Filename: "2022-12-13-02-create-tags-table.sql", - Transactional: true, - }, - } + dirFiles := []string{ + "2022-12-12-01-create-table-statuses.sql", + "2022-12-12-02-create-table-news.sql", + "2022-12-12-03-add-comments-news-NONTR.sql", + "2022-12-13-01-create-categories-table.sql", + "2022-12-13-02-create-tags-table.sql", + } + want := []Migration{ + {Filename: "2022-12-12-01-create-table-statuses.sql", Transactional: true}, + {Filename: "2022-12-12-02-create-table-news.sql", Transactional: true}, + {Filename: "2022-12-12-03-add-comments-news-NONTR.sql", Transactional: false}, + {Filename: "2022-12-13-01-create-categories-table.sql", Transactional: true}, + {Filename: "2022-12-13-02-create-tags-table.sql", Transactional: true}, + } - res, err := testMigrator.newMigrations(dirFiles) - So(err, ShouldBeNil) - So(res, ShouldHaveLength, len(want)) + res, err := testMigrator.newMigrations(dirFiles) + require.NoError(t, err) + require.Len(t, res, len(want)) - for i, m := range res { - So(m.Filename, ShouldEqual, want[i].Filename) - So(m.Transactional, ShouldEqual, want[i].Transactional) - } - }) + for i, m := range res { + assert.Equal(t, want[i].Filename, m.Filename) + assert.Equal(t, want[i].Transactional, m.Transactional) + } } func TestMigrator_applyNonTransactionalMigration(t *testing.T) { t.Skip() ctx := context.Background() - Convey("TestMigrator_applyNonTransactionalMigration", t, func() { - err := recreateSchema() - So(err, ShouldBeNil) - _, err = testMigrator.db.ExecContext(ctx, `create table news (id text);`) - So(err, ShouldBeNil) - mg, err := NewMigration(testMigrator.rootDir, "2022-12-12-03-add-comments-news-NONTR.sql") - So(err, ShouldBeNil) + err := recreateSchema() + require.NoError(t, err) - err = testMigrator.applyNonTransactionalMigration(ctx, mg) - So(err, ShouldBeNil) + _, err = testMigrator.db.ExecContext(ctx, `create table news (id text);`) + require.NoError(t, err) - var pm PgMigration - err = testMigrator.db.ModelContext(ctx, &pm).Where(`"filename" = ?`, mg.Filename).Select() - So(err, ShouldBeNil) - So(pm, ShouldNotBeNil) - So(pm.FinishedAt, ShouldNotBeEmpty) - }) + mg, err := NewMigration(testMigrator.rootDir, "2022-12-12-03-add-comments-news-NONTR.sql") + require.NoError(t, err) + + err = testMigrator.applyNonTransactionalMigration(ctx, mg) + require.NoError(t, err) + + var pm PgMigration + err = testMigrator.db.ModelContext(ctx, &pm).Where(`"filename" = ?`, mg.Filename).Select() + require.NoError(t, err) + assert.NotEmpty(t, pm.FinishedAt) } func TestMigrator_applyMigration(t *testing.T) { t.Skip() ctx := context.Background() - Convey("TestMigrator_applyMigration", t, func() { - mg, err := NewMigration(testMigrator.rootDir, "2022-12-12-01-create-table-statuses.sql") - So(err, ShouldBeNil) - err = testMigrator.applyMigration(ctx, mg) - So(err, ShouldBeNil) + mg, err := NewMigration(testMigrator.rootDir, "2022-12-12-01-create-table-statuses.sql") + require.NoError(t, err) - var pm PgMigration - err = testMigrator.db.ModelContext(ctx, &pm).Where(`"filename" = ?`, mg.Filename).Select() - So(err, ShouldBeNil) - So(pm, ShouldNotBeNil) - So(pm.FinishedAt, ShouldNotBeEmpty) - }) + err = testMigrator.applyMigration(ctx, mg) + require.NoError(t, err) + + var pm PgMigration + err = testMigrator.db.ModelContext(ctx, &pm).Where(`"filename" = ?`, mg.Filename).Select() + require.NoError(t, err) + assert.NotEmpty(t, pm.FinishedAt) } func TestMigrator_Run(t *testing.T) { ctx := context.Background() - Convey("TestMigrator_Run", t, func() { - err := recreateSchema() - So(err, ShouldBeNil) - err = execRun(ctx, t) - So(err, ShouldBeNil) - }) + err := recreateSchema() + require.NoError(t, err) + + err = execRun(ctx, t) + require.NoError(t, err) } func execRun(ctx context.Context, t *testing.T) error { @@ -227,269 +198,257 @@ func execRun(ctx context.Context, t *testing.T) error { func TestMigrator_Last(t *testing.T) { ctx := context.Background() - Convey("TestMigrator_Last", t, func() { - Convey("check empty list", func() { - err := recreateSchema() - So(err, ShouldBeNil) - - list, err := testMigrator.Last(ctx, 5) - So(err, ShouldBeNil) - So(list, ShouldHaveLength, 0) - }) - Convey("check all applied list", func() { - err := recreateSchema() - So(err, ShouldBeNil) - err = execRun(ctx, t) - So(err, ShouldBeNil) - - list, err := testMigrator.Last(ctx, 5) - So(err, ShouldBeNil) - So(list, ShouldHaveLength, 5) - }) - Convey("check 3 last migrations", func() { - list, err := testMigrator.Last(ctx, 3) - So(err, ShouldBeNil) - So(list, ShouldHaveLength, 3) - }) + + t.Run("empty list", func(t *testing.T) { + err := recreateSchema() + require.NoError(t, err) + + list, err := testMigrator.Last(ctx, 5) + require.NoError(t, err) + assert.Empty(t, list) + }) + + t.Run("all applied", func(t *testing.T) { + err := recreateSchema() + require.NoError(t, err) + err = execRun(ctx, t) + require.NoError(t, err) + + list, err := testMigrator.Last(ctx, 5) + require.NoError(t, err) + assert.Len(t, list, 5) + }) + + t.Run("3 last migrations", func(t *testing.T) { + list, err := testMigrator.Last(ctx, 3) + require.NoError(t, err) + assert.Len(t, list, 3) }) } func TestMigrator_Redo(t *testing.T) { ctx := context.Background() - Convey("TestMigrator_Redo", t, func() { - Convey("check if migrations wasn't applied", func() { - err := recreateSchema() - So(err, ShouldBeNil) - - ch := make(chan string) - go readFromCh(ch, t) - - pm, err := testMigrator.Redo(ctx, ch) - So(err.Error(), ShouldEqual, "applied migrations were not found") - So(pm, ShouldBeNil) - }) - Convey("redo last applied", func() { - err := recreateSchema() - So(err, ShouldBeNil) - - err = execRun(ctx, t) - So(err, ShouldBeNil) - - _, err = testDb.Exec("DROP TABLE tags CASCADE;") - So(err, ShouldBeNil) - - ch := make(chan string) - go readFromCh(ch, t) - - pm, err := testMigrator.Redo(ctx, ch) - So(err, ShouldBeNil) - So(pm, ShouldResemble, &PgMigration{ - ID: pm.ID, - Filename: "2022-12-13-02-create-tags-table.sql", - StartedAt: pm.StartedAt, - FinishedAt: pm.FinishedAt, - Transactional: true, - Md5sum: "d10bca7f78e847d3d4e71003b31a54a6", - }) - }) + + t.Run("no applied migrations", func(t *testing.T) { + err := recreateSchema() + require.NoError(t, err) + + ch := make(chan string) + go readFromCh(ch, t) + + pm, err := testMigrator.Redo(ctx, ch) + require.EqualError(t, err, "applied migrations were not found") + assert.Nil(t, pm) + }) + + t.Run("redo last applied", func(t *testing.T) { + err := recreateSchema() + require.NoError(t, err) + + err = execRun(ctx, t) + require.NoError(t, err) + + _, err = testDB.Exec("DROP TABLE tags CASCADE;") + require.NoError(t, err) + + ch := make(chan string) + go readFromCh(ch, t) + + pm, err := testMigrator.Redo(ctx, ch) + require.NoError(t, err) + assert.Equal(t, &PgMigration{ + ID: pm.ID, + Filename: "2022-12-13-02-create-tags-table.sql", + StartedAt: pm.StartedAt, + FinishedAt: pm.FinishedAt, + Transactional: true, + Md5sum: "d10bca7f78e847d3d4e71003b31a54a6", + }, pm) }) } func TestMigrator_dryRunMigrations(t *testing.T) { ctx := context.Background() - Convey("TestMigrator_prepareMigrationsToDryRun", t, func() { + + err := recreateSchema() + require.NoError(t, err) + err = testMigrator.createMigratorTable(ctx) + require.NoError(t, err) + + dirFiles := []string{ + "2022-12-12-01-create-table-statuses.sql", + "2022-12-12-02-create-table-news.sql", + } + mm, err := testMigrator.newMigrations(dirFiles) + require.NoError(t, err) + + ch := make(chan string) + go readFromCh(ch, t) + err = testMigrator.dryRunMigrations(ctx, mm, ch) + require.NoError(t, err) +} + +func TestMigrator_DryRun(t *testing.T) { + ctx := context.Background() + + t.Run("transactional migrations", func(t *testing.T) { err := recreateSchema() - So(err, ShouldBeNil) - err = testMigrator.createMigratorTable(ctx) - So(err, ShouldBeNil) + require.NoError(t, err) dirFiles := []string{ "2022-12-12-01-create-table-statuses.sql", "2022-12-12-02-create-table-news.sql", } - mm, err := testMigrator.newMigrations(dirFiles) - So(err, ShouldBeNil) ch := make(chan string) go readFromCh(ch, t) - err = testMigrator.dryRunMigrations(ctx, mm, ch) - So(err, ShouldBeNil) - }) -} -func TestMigrator_DryRun(t *testing.T) { - ctx := context.Background() - Convey("TestMigrator_DryRun", t, func() { - Convey("check transactional migrations to dry run", func() { - err := recreateSchema() - So(err, ShouldBeNil) - - dirFiles := []string{ - "2022-12-12-01-create-table-statuses.sql", - "2022-12-12-02-create-table-news.sql", - } - - ch := make(chan string) - go readFromCh(ch, t) - - err = testMigrator.DryRun(ctx, dirFiles, ch) - So(err, ShouldBeNil) - }) - - Convey("check with non transactional migrations to dry run", func() { - err := recreateSchema() - So(err, ShouldBeNil) - dirFiles := []string{ - "2022-12-12-01-create-table-statuses.sql", - "2022-12-12-02-create-table-news.sql", - "2022-12-12-03-add-comments-news-NONTR.sql", - "2022-12-13-01-create-categories-table.sql", - "2022-12-13-02-create-tags-table.sql", - } - - ch := make(chan string) - go readFromCh(ch, t) - - err = testMigrator.DryRun(ctx, dirFiles, ch) - So(err.Error(), ShouldEqual, `non transactional migration found "2022-12-12-03-add-comments-news-NONTR.sql", run all migrations before it, please`) - }) + err = testMigrator.DryRun(ctx, dirFiles, ch) + require.NoError(t, err) }) -} -func TestMigrator_skipMigrations(t *testing.T) { - ctx := context.Background() - Convey("TestMigrator_skipMigrations", t, func() { + t.Run("with non transactional migrations", func(t *testing.T) { err := recreateSchema() - So(err, ShouldBeNil) - err = testMigrator.createMigratorTable(ctx) - So(err, ShouldBeNil) + require.NoError(t, err) dirFiles := []string{ "2022-12-12-01-create-table-statuses.sql", "2022-12-12-02-create-table-news.sql", + "2022-12-12-03-add-comments-news-NONTR.sql", + "2022-12-13-01-create-categories-table.sql", + "2022-12-13-02-create-tags-table.sql", } - mm, err := testMigrator.newMigrations(dirFiles) - So(err, ShouldBeNil) ch := make(chan string) go readFromCh(ch, t) - err = testMigrator.skipMigrations(ctx, mm, ch) - So(err, ShouldBeNil) - - for _, mg := range mm { - var pm PgMigration - err = testMigrator.db.ModelContext(ctx, &pm).Where(`"filename" = ?`, mg.Filename).Select() - So(err, ShouldBeNil) - So(pm, ShouldNotBeNil) - So(pm.FinishedAt, ShouldNotBeEmpty) - } + + err = testMigrator.DryRun(ctx, dirFiles, ch) + assert.EqualError(t, err, `non transactional migration found "2022-12-12-03-add-comments-news-NONTR.sql", run all migrations before it, please`) }) } +func TestMigrator_skipMigrations(t *testing.T) { + ctx := context.Background() + + err := recreateSchema() + require.NoError(t, err) + err = testMigrator.createMigratorTable(ctx) + require.NoError(t, err) + + dirFiles := []string{ + "2022-12-12-01-create-table-statuses.sql", + "2022-12-12-02-create-table-news.sql", + } + mm, err := testMigrator.newMigrations(dirFiles) + require.NoError(t, err) + + ch := make(chan string) + go readFromCh(ch, t) + err = testMigrator.skipMigrations(ctx, mm, ch) + require.NoError(t, err) + + for _, mg := range mm { + var pm PgMigration + err = testMigrator.db.ModelContext(ctx, &pm).Where(`"filename" = ?`, mg.Filename).Select() + require.NoError(t, err) + assert.NotEmpty(t, pm.FinishedAt) + } +} + func TestMigrator_Skip(t *testing.T) { ctx := context.Background() - Convey("TestMigrator_Skip", t, func() { - err := recreateSchema() - So(err, ShouldBeNil) - filenames, err := testMigrator.Plan(ctx) - So(err, ShouldBeNil) - ch := make(chan string) - go readFromCh(ch, t) - err = testMigrator.Skip(ctx, filenames, ch) - So(err, ShouldBeNil) - }) + + err := recreateSchema() + require.NoError(t, err) + + filenames, err := testMigrator.Plan(ctx) + require.NoError(t, err) + + ch := make(chan string) + go readFromCh(ch, t) + err = testMigrator.Skip(ctx, filenames, ch) + require.NoError(t, err) } func TestMigrator_compareMD5Sum(t *testing.T) { - Convey("TestMigrator_compareMD5Sum", t, func() { - Convey("check correct", func() { - filenames := []string{ - "2022-12-12-01-create-table-statuses.sql", - "2022-12-12-02-create-table-news.sql", - } - mm, err := testMigrator.newMigrations(filenames) - So(err, ShouldBeNil) - fileMigrations := mm.ToDB() - dbMigrations := []PgMigration{ - { - Filename: "2022-12-12-01-create-table-statuses.sql", - Md5sum: "463fe73a85e13dd55fe210904ec19d7c", - }, - { - Filename: "2022-12-12-02-create-table-news.sql", - Md5sum: "6158555b3ceb1a216b0cb365cb97fc71", - }, - } - - invalid := testMigrator.compareMD5Sum(fileMigrations, dbMigrations) - So(invalid, ShouldHaveLength, 0) - }) - Convey("check invalid", func() { - filenames := []string{ - "2022-12-12-01-create-table-statuses.sql", - "2022-12-12-02-create-table-news.sql", - } - mm, err := testMigrator.newMigrations(filenames) - So(err, ShouldBeNil) - fileMigrations := mm.ToDB() - - dbMigrations := []PgMigration{ - { - Filename: "2022-12-12-01-create-table-statuses.sql", - Md5sum: "463fe73a85e13dd55fe210904ec19d7c", - }, - { - Filename: "2022-12-12-02-create-table-news.sql", - Md5sum: "invalid!!!", - }, - } - - invalid := testMigrator.compareMD5Sum(fileMigrations, dbMigrations) - So(invalid, ShouldHaveLength, 1) - So(invalid[0].Filename, ShouldEqual, "2022-12-12-02-create-table-news.sql") - }) + t.Run("correct checksums", func(t *testing.T) { + filenames := []string{ + "2022-12-12-01-create-table-statuses.sql", + "2022-12-12-02-create-table-news.sql", + } + mm, err := testMigrator.newMigrations(filenames) + require.NoError(t, err) + + fileMigrations := mm.ToDB() + dbMigrations := []PgMigration{ + {Filename: "2022-12-12-01-create-table-statuses.sql", Md5sum: "463fe73a85e13dd55fe210904ec19d7c"}, + {Filename: "2022-12-12-02-create-table-news.sql", Md5sum: "6158555b3ceb1a216b0cb365cb97fc71"}, + } + + invalid := testMigrator.compareMD5Sum(fileMigrations, dbMigrations) + assert.Empty(t, invalid) + }) + + t.Run("invalid checksum", func(t *testing.T) { + filenames := []string{ + "2022-12-12-01-create-table-statuses.sql", + "2022-12-12-02-create-table-news.sql", + } + mm, err := testMigrator.newMigrations(filenames) + require.NoError(t, err) + + fileMigrations := mm.ToDB() + dbMigrations := []PgMigration{ + {Filename: "2022-12-12-01-create-table-statuses.sql", Md5sum: "463fe73a85e13dd55fe210904ec19d7c"}, + {Filename: "2022-12-12-02-create-table-news.sql", Md5sum: "invalid!!!"}, + } + + invalid := testMigrator.compareMD5Sum(fileMigrations, dbMigrations) + require.Len(t, invalid, 1) + assert.Equal(t, "2022-12-12-02-create-table-news.sql", invalid[0].Filename) }) } func TestMigrator_Verify(t *testing.T) { ctx := context.Background() - Convey("TestMigrator_Verify", t, func() { - Convey("check from empty table", func() { - err := recreateSchema() - So(err, ShouldBeNil) - - invalid, err := testMigrator.Verify(ctx) - So(err, ShouldBeNil) - So(invalid, ShouldHaveLength, 0) - }) - Convey("check from correct migrations", func() { - err := recreateSchema() - So(err, ShouldBeNil) - - err = execRun(ctx, t) - So(err, ShouldBeNil) - - invalid, err := testMigrator.Verify(ctx) - So(err, ShouldBeNil) - So(invalid, ShouldHaveLength, 0) - }) - Convey("check from invalid migrations", func() { - err := recreateSchema() - So(err, ShouldBeNil) - - err = execRun(ctx, t) - So(err, ShouldBeNil) - - invalidFilename := "2022-12-13-01-create-categories-table.sql" - pm := PgMigration{Md5sum: "invalid!!!"} - _, err = testMigrator.db.ModelContext(ctx, &pm).Column("md5sum").Where(`"filename" = ?`, invalidFilename).Update() - So(err, ShouldBeNil) - - invalid, err := testMigrator.Verify(ctx) - So(err, ShouldBeNil) - So(invalid, ShouldHaveLength, 1) - So(invalid[0].Filename, ShouldEqual, invalidFilename) - }) + + t.Run("empty table", func(t *testing.T) { + err := recreateSchema() + require.NoError(t, err) + + invalid, err := testMigrator.Verify(ctx) + require.NoError(t, err) + assert.Empty(t, invalid) + }) + + t.Run("correct migrations", func(t *testing.T) { + err := recreateSchema() + require.NoError(t, err) + + err = execRun(ctx, t) + require.NoError(t, err) + + invalid, err := testMigrator.Verify(ctx) + require.NoError(t, err) + assert.Empty(t, invalid) + }) + + t.Run("invalid migrations", func(t *testing.T) { + err := recreateSchema() + require.NoError(t, err) + + err = execRun(ctx, t) + require.NoError(t, err) + + invalidFilename := "2022-12-13-01-create-categories-table.sql" + pm := PgMigration{Md5sum: "invalid!!!"} + _, err = testMigrator.db.ModelContext(ctx, &pm).Column("md5sum").Where(`"filename" = ?`, invalidFilename).Update() + require.NoError(t, err) + + invalid, err := testMigrator.Verify(ctx) + require.NoError(t, err) + require.Len(t, invalid, 1) + assert.Equal(t, invalidFilename, invalid[0].Filename) }) } @@ -500,6 +459,6 @@ func readFromCh(ch chan string, t *testing.T) { } func recreateSchema() error { - _, err := testDb.Exec("DROP SCHEMA public CASCADE; CREATE SCHEMA PUBLIC;") + _, err := testDB.Exec("DROP SCHEMA public CASCADE; CREATE SCHEMA PUBLIC;") return err } diff --git a/pkg/migrator/model.go b/pkg/migrator/model.go index 994b7e9..49bf416 100644 --- a/pkg/migrator/model.go +++ b/pkg/migrator/model.go @@ -42,14 +42,6 @@ type Migration struct { Transactional bool } -func (m *Migration) ToDB() *PgMigration { - return &PgMigration{ - Filename: m.Filename, - Transactional: m.Transactional, - Md5sum: m.Md5Sum, - } -} - func NewMigration(rootDir, filename string) (Migration, error) { f, err := os.ReadFile(filepath.Join(rootDir, filename)) if err != nil { @@ -66,6 +58,14 @@ func NewMigration(rootDir, filename string) (Migration, error) { return m, nil } +func (m *Migration) ToDB() *PgMigration { + return &PgMigration{ + Filename: m.Filename, + Transactional: m.Transactional, + Md5sum: m.Md5Sum, + } +} + type Migrations []Migration func (mm Migrations) FirstNonTransactional() (*Migration, bool) {