├── .github └── workflows │ ├── codeql.yml │ ├── main.yml │ └── release.yml ├── .gitignore ├── .goreleaser.yml ├── LICENSE ├── README.md ├── cmd ├── completion.go ├── create.go ├── down.go ├── init.go ├── list.go ├── root.go └── up.go ├── go.mod ├── go.sum ├── main.go ├── metana.png ├── pkg ├── cmd │ ├── create.go │ ├── down.go │ ├── helpers.go │ ├── init.go │ ├── list.go │ └── up.go ├── core │ ├── gen │ │ └── file.go │ ├── migrate │ │ └── updown.go │ └── tpl │ │ └── tpl.go ├── list.go ├── mock.go ├── store │ ├── file.go │ ├── mongo.go │ ├── postgres.go │ └── store.go └── types │ └── type.go └── tests ├── tests_create.sh ├── tests_down.sh ├── tests_init.sh ├── tests_list.sh └── tests_up.sh /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths: 7 | - 'pkg/**' 8 | - 'cmd/**' 9 | pull_request: 10 | # The branches below must be a subset of the branches above 11 | branches: [ main ] 12 | 13 | jobs: 14 | analyze: 15 | name: Analyze 16 | runs-on: ubuntu-latest 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | language: [ 'go' ] 22 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 23 | # Learn more: 24 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v2 29 | 30 | # Initializes the CodeQL tools for scanning. 31 | - name: Initialize CodeQL 32 | uses: github/codeql-action/init@v1 33 | with: 34 | languages: ${{ matrix.language }} 35 | # If you wish to specify custom queries, you can do so here or in a config gen. 36 | # By default, queries listed here will override any specified in a config gen. 37 | # Prefix the list here with "+" to use these queries and those in the config gen. 38 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 39 | 40 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 41 | # If this step fails, then you should remove it and run the build manually (see below) 42 | - name: Autobuild 43 | uses: github/codeql-action/autobuild@v1 44 | 45 | # ℹ️ Command-line programs to run using the OS shell. 46 | # 📚 https://git.io/JvXDl 47 | 48 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 49 | # and modify them (or add more) to build your code if your project 50 | # uses a compiled language 51 | 52 | #- run: | 53 | # make bootstrap 54 | # make release 55 | 56 | - name: Perform CodeQL Analysis 57 | uses: github/codeql-action/analyze@v1 -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Go CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths: 7 | - 'cmd/**' 8 | - 'pkg/**' 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build-and-test: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v3 19 | 20 | - name: Set up Go 21 | uses: actions/setup-go@v4 22 | with: 23 | go-version: '1.21' 24 | 25 | - name: Cache Go modules 26 | uses: actions/cache@v3 27 | with: 28 | path: | 29 | ~/.cache/go-build 30 | ~/go/pkg/mod 31 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 32 | restore-keys: | 33 | ${{ runner.os }}-go- 34 | 35 | - name: Install dependencies 36 | run: go mod download 37 | 38 | - name: Install Metana binary 39 | run: go install ./... 40 | 41 | - name: Make test scripts executable 42 | run: chmod +x tests/*.sh 43 | 44 | - name: Run shell test scripts 45 | env: 46 | POSTGRES_TEST_URL: ${{ secrets.POSTGRES_TEST_URL }} 47 | MONGO_TEST_URL: ${{ secrets.MONGO_TEST_URL }} 48 | run: | 49 | ./tests/tests_init.sh 50 | ./tests/tests_create.sh 51 | ./tests/tests_list.sh 52 | 53 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: # Manual trigger via GitHub UI 5 | inputs: 6 | release_tag: 7 | description: 'Release tag (e.g., v1.0.0)' 8 | required: true 9 | type: string 10 | 11 | permissions: 12 | contents: write 13 | 14 | jobs: 15 | goreleaser: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 0 23 | 24 | - name: Set up Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: '1.21' 28 | 29 | - name: Cache Go modules 30 | uses: actions/cache@v3 31 | with: 32 | path: | 33 | ~/.cache/go-build 34 | ~/go/pkg/mod 35 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 36 | restore-keys: | 37 | ${{ runner.os }}-go- 38 | 39 | - name: Set Git Tag 40 | run: | 41 | git config user.name "github-actions[bot]" 42 | git config user.email "github-actions[bot]@users.noreply.github.com" 43 | git tag ${{ github.event.inputs.release_tag }} 44 | git push origin ${{ github.event.inputs.release_tag }} 45 | 46 | - name: Run GoReleaser 47 | uses: goreleaser/goreleaser-action@v6 48 | with: 49 | distribution: goreleaser 50 | version: latest 51 | args: release --clean 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GH_GORELEASER_TOKEN }} 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | migrations 8 | demo.yml 9 | schema-mig 10 | schema 11 | # Test binary, built with `go test -c` 12 | *.test 13 | tmpl.go 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | .env 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | .idea 20 | .vscode -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | project_name: metana 3 | 4 | release: 5 | prerelease: auto 6 | draft: true 7 | name_template: Metana {{ .Version }} 8 | github: 9 | owner: g14a 10 | name: metana 11 | 12 | builds: 13 | - env: [CGO_ENABLED=0] 14 | ldflags: 15 | - -s -w 16 | goos: 17 | - linux 18 | - darwin 19 | goarch: 20 | - amd64 21 | - arm64 22 | 23 | brews: 24 | - name: metana 25 | homepage: https://github.com/g14a/metana 26 | description: An abstract migration tool written in Go for Go services. 27 | license: "Apache-2.0" 28 | repository: 29 | owner: g14a 30 | name: homebrew-metana 31 | branch: main 32 | directory: Formula 33 | install: | 34 | bin.install "metana" 35 | test: | 36 | system "#{bin}/metana", "--help" 37 | commit_author: 38 | name: goreleaserbot 39 | email: bot@goreleaser.com 40 | 41 | nfpms: 42 | - maintainer: Gowtham Munukutla 43 | description: Abstract task migration tool written in Go for Golang services. Database and non database migration management brought to your CLI. 44 | homepage: https://github.com/g14a/metana 45 | license: Apache License 2.0 46 | formats: 47 | - deb 48 | - rpm 49 | - apk 50 | - termux.deb 51 | - archlinux -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](metana.png) 2 | 3 | # Metana 4 | 5 | ![OpenSource](https://img.shields.io/badge/Open%20Source-000000?style=for-the-badge&logo=github) 6 | ![go](https://img.shields.io/badge/-Written%20In%20Go-00add8?style=for-the-badge&logo=Go&logoColor=ffffff) 7 | ![cli](https://img.shields.io/badge/-Build%20for%20CLI-000000?style=for-the-badge&logo=Powershell&logoColor=ffffff) 8 | [![made-with-Go](https://img.shields.io/badge/Made%20with-Go-1f425f.svg)](http://golang.org) 9 | [![GitHub go.mod Go version of a Go module](https://img.shields.io/github/go-mod/go-version/g14a/metana.svg)](https://github.com/g14a/metana) 10 | [![Go Report Card](https://goreportcard.com/badge/github.com/g14a/metana)](https://goreportcard.com/report/github.com/g14a/metana) 11 | [![Go Workflow Status](https://github.com/g14a/metana/workflows/Go/badge.svg)](https://github.com/g14a/metana/workflows/Go/badge.svg) 12 | 13 | An abstract task migration tool written in Go for Go services. Database and non database migrations management brought to your CLI. 14 | 15 | # Table of Contents 16 | 17 | * [Use case](https://github.com/g14a/metana#use-case) 18 | * [Installation](https://github.com/g14a/metana#installation) 19 | * [Using Go](https://github.com/g14a/metana#using-go) 20 | * [macOS](https://github.com/g14a/metana#mac) 21 | * [Linux](https://github.com/g14a/metana#linux) 22 | * [Building from Source](https://github.com/g14a/metana#building-from-source) 23 | * [Usage](https://github.com/g14a/metana#usage) 24 | * [Init](https://github.com/g14a/metana#init) ✅ 25 | * [Create](https://github.com/g14a/metana#create) 👌 26 | * [Up](https://github.com/g14a/metana#up) ⬆️ 27 | * [Down](https://github.com/g14a/metana#down) ⬇️ 28 | * [List](https://github.com/g14a/metana#list) 29 | * [Features](https://github.com/g14a/metana#features) 30 | * [Run migrations until a certain point](https://github.com/g14a/metana#run-a-migration-until-a-certain-point) 31 | * [Store and Track your migrations in your favourite database](https://github.com/g14a/metana#store-and-track-your-migrations-in-your-favourite-database) 32 | * [Dry Run Migrations](https://github.com/g14a/metana#dry-run-migrations) 33 | * [Automatic Rollback on Migration Failure](https://github.com/g14a/metana#automatic-rollback-on-migration-failure) 34 | 35 | # Use case 36 | 37 | The motivation behind creating this tool, is to abstract away the database part. If your task can be completed with Pure Go or via a Go driver of your service, then this is for you. Since it makes use of the Go runtime, you can even perform database migrations like PostgreSQL, Mongo, Redis, Elasticsearch, GCP Buckets etc. You just need to be able to interact with your data store or complete your task using Go. 38 | 39 | The main use case is when you won't be able to do everything with SQL or No-SQL syntax. There might be some tasks where you need to aggregate data, iterate over them, and do business related computation. All you need to know is Go syntax and write a Go program. 40 | 41 | # Installation 42 | 43 | ## Using Go 44 | ```shell 45 | go get github.com/g14a/metana 46 | ``` 47 | 48 | ## **Mac** 49 | 50 | ```shell 51 | brew tap g14a/homebrew-metana 52 | brew install metana 53 | ``` 54 | ## **Linux** 55 | 56 | Checkout the releases page and download your platform's binaries to install them. 57 | 58 | [Releases Page](https://github.com/g14a/metana/releases) 59 | 60 | ## **Building from source** 61 | 62 | Prerequisites: 63 | 64 | * Git 65 | * Go 1.13 or newer. Go modules are needed. Better if its the latest version. 66 | 67 | ```shell 68 | git clone https://github.com/g14a/metana 69 | cd metana 70 | go install 71 | ``` 72 | 73 | # Usage 74 | 75 | After installation, let's just hit metana on the terminal. 76 | 77 | ```shell 78 | $ metana 79 | An abstract migration tool for Go services 80 | 81 | Usage: 82 | metana [flags] 83 | metana [command] 84 | 85 | Available Commands: 86 | completion Generate shell completion script 87 | config Manage your local metana config in .metana.yml 88 | create Create a migration in Go 89 | down Run downward migrations 90 | help Help about any command 91 | init Initialize a migrations directory 92 | list List existing migrations 93 | up Run upward migrations 94 | 95 | Flags: 96 | --config string config gen (default is $HOME/.metana.yaml) 97 | -h, --help help for metana 98 | -t, --toggle Help message for toggle 99 | 100 | Use "metana [command] --help" for more information about a command. 101 | ``` 102 | 103 | ## **`Init`** 104 | 105 | `init` initializes a boilerplate migrations directory in your current path. 106 | 107 | ```shell 108 | $ metana init 109 | Successfully initialized migration setup in migrations 110 | ``` 111 | 112 | ## **`Create`** 113 | 114 | `create` creates a migration script with two functions `Up()` and `Down()` denoting the upward and downward migration of the same. 115 | 116 | ```shell 117 | $ metana create initSchema 118 | ✓ Created migrations/scripts/1746334029_initSchema.go 119 | ``` 120 | 121 | Head over to your `1745742878_initSchema.go` to edit your script. Remember to not change any function signature. 122 | 123 | ## **`Up`** 124 | 125 | `up` runs all the upward migrations in the migrations directory in order of their creation time. 126 | 127 | ```shell 128 | $ metana up 129 | 130 | InitSchema up 131 | __COMPLETE__[up]: 1745742878_initSchema.go 132 | InitSchema2 up 133 | __COMPLETE__[up]: 1745742917_initSchema2.go 134 | >>> migration : complete 135 | ``` 136 | 137 | ## **`Down`** 138 | 139 | `down` runs the downward migrations in the reverse order of creation time because we're trying to undo the upward migrations. 140 | 141 | ```shell 142 | $ metana down 143 | 144 | InitSchema down 145 | __COMPLETE__[down]: 1745742878_initSchema.go 146 | InitSchema2 down 147 | __COMPLETE__[down]: 1745742917_initSchema2.go 148 | >>> migration : complete 149 | ``` 150 | 151 | ## **`List`** 152 | 153 | `list` lists all the migrations present in your migrations folder along with the last executed time. 154 | 155 | ```shell 156 | $ metana list 157 | +---------------------------+------------------+ 158 | | MIGRATION | EXECUTED AT | 159 | +---------------------------+------------------+ 160 | | 1745742878_initSchema.go | 27-04-2025 14:06 | 161 | | 1745742917_initSchema2.go | 27-04-2025 14:06 | 162 | +---------------------------+------------------+ 163 | ``` 164 | 165 | # Features 166 | 167 | ## **Run a migration until a certain point** 168 | 169 | Run upward and downward migrations until(and including) a certain migration with the `--until` flag. 170 | 171 | ```shell 172 | 173 | $ metana create initSchema 174 | ✓ Created migrations/scripts/1746334029_initSchema.go 175 | 176 | $ Create more migration scripts... 177 | 178 | $ metana list 179 | 180 | +---------------------------+------------------+ 181 | | MIGRATION | EXECUTED AT | 182 | +---------------------------+------------------+ 183 | | 1745743242_initSchema.go | | 184 | | 1745743245_initSchema2.go | | 185 | | 1745743247_initSchema3.go | | 186 | +---------------------------+------------------+ 187 | 188 | $ metana up --until initSchema2 189 | InitSchema up 190 | __COMPLETE__[up]: 1745743242_initSchema.go 191 | InitSchema2 up 192 | __COMPLETE__[up]: 1745743245_initSchema2.go 193 | >>> Reached --until: initSchema2. Stopping further migrations. 194 | >>> migration : complete 195 | ``` 196 | 197 | ## **Store and track your migrations in your favourite database** 198 | 199 | Store and track your migrations in your favourite database by passing the `--store` flag. 200 | 201 | ```shell 202 | metana up --store 203 | ``` 204 | 205 | If your connection URL is store in an environment variable you can pass it as `--store @MONGO_URL` and it will automatically be picked up from your environment. 206 | 207 | Right now, PostgreSQL(which means even CockroachDB URLs) and MongoDB are supported to store migrations. 208 | 209 | If no `--store` flag is passed, migrations will be stored in a default `migrate.json` file in the migrations directory. 210 | 211 | ## **Dry run migrations** 212 | 213 | Dry run your migrations using the `--dry` flag. 214 | 215 | You can dry run your migrations using the explicit `--dry` option. This option doesn't track any migrations, doesn't create a default `migrate.json` file. It literally just dry runs. However your tasks are run. This helps when you're incrementally writing, testing and running your functions instead of manually deleting states in your store. 216 | 217 | ```shell 218 | $ metana up --dry 219 | 220 | InitSchema up 221 | __COMPLETE__[up]: 1745743242_initSchema.go 222 | InitSchema2 up 223 | __COMPLETE__[up]: 1745743245_initSchema2.go 224 | InitSchema3 up 225 | __COMPLETE__[up]: 1745743247_initSchema3.go 226 | >>> dry run migration : complete 227 | ``` 228 | 229 | ```shell 230 | $ metana down --dry 231 | 232 | InitSchema down 233 | __COMPLETE__[down]: 1745743242_initSchema.go 234 | InitSchema2 down 235 | __COMPLETE__[down]: 1745743245_initSchema2.go 236 | InitSchema3 down 237 | __COMPLETE__[down]: 1745743247_initSchema3.go 238 | >>> dry run migration : complete 239 | ``` 240 | 241 | All the other options like `--dir` and `--until` work along with `--dry`. 242 | 243 | ## **Automatic Rollback on Migration Failure** 244 | 245 | Metana automatically handles rollback during upward migrations (`metana up`) 246 | 247 | If an **upward migration** (`up`) fails while being run, Metana will immediately **trigger the downward migration** (`down`) of that **same migration file** to rollback the changes and restore consistency. 248 | 249 | You don't have to manually clean up — rollback is automatic. But you still have implement the logic of the downward migration. 250 | 251 | **Example:** 252 | 253 | ```shell 254 | $ metana up 255 | 256 | InitSchema up 257 | __COMPLETE__[up]: 1745748076_initSchema.go 258 | Migration 1745748078_initSchema2.go failed, attempting rollback... 259 | InitSchema2 down 260 | __COMPLETE__[down]: 1745748078_initSchema2.go 261 | >>> migration : complete 262 | 2025/04/27 15:32:05 migration 1745748078_initSchema2.go failed: execution error: exit status 1 263 | error: simulated error 264 | goroutine 1 [running]: 265 | runtime/debug.Stack() 266 | /Users/gowtham.munukutla/.gvm/gos/go1.21/src/runtime/debug/stack.go:24 +0x64 267 | runtime/debug.PrintStack() 268 | /Users/gowtham.munukutla/.gvm/gos/go1.21/src/runtime/debug/stack.go:16 +0x1c 269 | main.main() 270 | /Users/gowtham.munukutla/metana/migrations/scripts/1745748078_initSchema2.go:48 +0x1d8 271 | exit status 1 272 | ``` -------------------------------------------------------------------------------- /cmd/completion.go: -------------------------------------------------------------------------------- 1 | // Package cmd /* 2 | package cmd 3 | 4 | import ( 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | // completionCmd represents the completion command 11 | var completionCmd = &cobra.Command{ 12 | Use: "completion [bash|zsh|fish|powershell]", 13 | Short: "Generate shell completion script", 14 | Long: `To load completions: 15 | 16 | Bash: 17 | 18 | $ source <(metana completion bash) 19 | 20 | # To load completions for each session, execute once: 21 | 22 | # Linux: 23 | $ metana completion bash > /etc/bash_completion.d/metana 24 | 25 | # macOS: 26 | $ metana completion bash > /usr/local/etc/bash_completion.d/metana 27 | 28 | Zsh: 29 | 30 | # If shell completion is not already enabled in your environment, 31 | # you will need to enable it. You can execute the following once: 32 | 33 | $ echo "autoload -U compinit; compinit" >> ~/.zshrc 34 | 35 | # To load completions for each session, execute once: 36 | $ metana completion zsh > "${fpath[1]}/_metana" 37 | 38 | # You will need to start a new shell for this setup to take effect. 39 | 40 | fish: 41 | 42 | $ metana completion fish | source 43 | 44 | # To load completions for each session, execute once: 45 | $ metana completion fish > ~/.config/fish/completions/metana.fish 46 | 47 | PowerShell: 48 | 49 | PS> metana completion powershell | Out-String | Invoke-Expression 50 | 51 | # To load completions for every new session, run: 52 | PS> metana completion powershell > metana.ps1 53 | # and source this file from your PowerShell profile. 54 | `, 55 | DisableFlagsInUseLine: true, 56 | ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, 57 | Args: cobra.ExactValidArgs(1), 58 | Run: func(cmd *cobra.Command, args []string) { 59 | switch args[0] { 60 | case "bash": 61 | cmd.Root().GenBashCompletion(os.Stdout) 62 | case "zsh": 63 | cmd.Root().GenZshCompletion(os.Stdout) 64 | case "fish": 65 | cmd.Root().GenFishCompletion(os.Stdout, true) 66 | case "powershell": 67 | cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) 68 | } 69 | }, 70 | } 71 | 72 | func init() { 73 | rootCmd.AddCommand(completionCmd) 74 | 75 | // Here you will define your flags and configuration settings. 76 | 77 | // Cobra supports Persistent Flags which will work for this command 78 | // and all subcommands, e.g.: 79 | // completionCmd.PersistentFlags().String("foo", "", "A help for foo") 80 | 81 | // Cobra supports local flags which will only run when this command 82 | // is called directly, e.g.: 83 | // completionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 84 | } 85 | -------------------------------------------------------------------------------- /cmd/create.go: -------------------------------------------------------------------------------- 1 | // Package cmd /* 2 | package cmd 3 | 4 | import ( 5 | "os" 6 | 7 | cmd2 "github.com/g14a/metana/pkg/cmd" 8 | "github.com/spf13/afero" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // createCmd represents the create command 13 | var createCmd = &cobra.Command{ 14 | Use: "create", 15 | Short: "Create a migration in Go", 16 | Long: ``, 17 | Args: cobra.ExactArgs(1), 18 | RunE: func(cmd *cobra.Command, args []string) error { 19 | FS := afero.NewOsFs() 20 | wd, _ := os.Getwd() 21 | 22 | err := cmd2.RunCreate(cmd, args, FS, wd) 23 | if err != nil { 24 | return err 25 | } 26 | return nil 27 | }, 28 | } 29 | 30 | func init() { 31 | rootCmd.AddCommand(createCmd) 32 | 33 | // Here you will define your flags and configuration settings. 34 | 35 | // Cobra supports Persistent Flags which will work for this command 36 | // and all subcommands, e.g.: 37 | // createCmd.PersistentFlags().String("foo", "", "A help for foo") 38 | 39 | // Cobra supports local flags which will only run when this command 40 | // is called directly, e.g.: 41 | // createCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 42 | } 43 | -------------------------------------------------------------------------------- /cmd/down.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "strings" 7 | 8 | "github.com/g14a/metana/pkg" 9 | cmd2 "github.com/g14a/metana/pkg/cmd" 10 | "github.com/spf13/afero" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | // downCmd represents the down command 15 | var downCmd = &cobra.Command{ 16 | Use: "down", 17 | Short: "Run downward migrations", 18 | Long: ``, 19 | RunE: func(cmd *cobra.Command, args []string) error { 20 | FS := afero.NewOsFs() 21 | wd, _ := os.Getwd() 22 | 23 | return cmd2.RunDown(cmd, args, FS, wd) 24 | }, 25 | } 26 | 27 | func init() { 28 | rootCmd.AddCommand(downCmd) 29 | 30 | downCmd.Flags().StringP("until", "u", "", "Migrate down until a specific point") 31 | downCmd.Flags().StringP("store", "s", "", "Specify a connection URL to track migrations") 32 | downCmd.Flags().Bool("dry", false, "Specify if the downward migration is a dry run {true | false}") 33 | downCmd.Flags().StringP("env-file", "e", ".env", "Specify env file containing keys") 34 | 35 | downCmd.RegisterFlagCompletionFunc("until", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 36 | FS := afero.NewOsFs() 37 | wd, err := os.Getwd() 38 | if err != nil { 39 | return nil, cobra.ShellCompDirectiveError 40 | } 41 | 42 | finalDir := "migrations" 43 | 44 | migrations, err := pkg.GetMigrations(filepath.Join(wd, finalDir), FS) 45 | if err != nil { 46 | return nil, cobra.ShellCompDirectiveError 47 | } 48 | 49 | var names []string 50 | for _, m := range migrations { 51 | name := strings.TrimSuffix(m.Name, ".go") 52 | parts := strings.SplitN(name, "_", 2) 53 | if len(parts) == 2 { 54 | names = append(names, parts[1]) 55 | } 56 | } 57 | 58 | return names, cobra.ShellCompDirectiveDefault 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /cmd/init.go: -------------------------------------------------------------------------------- 1 | // Package cmd /* 2 | package cmd 3 | 4 | import ( 5 | "log" 6 | "os" 7 | 8 | cmd2 "github.com/g14a/metana/pkg/cmd" 9 | "github.com/spf13/afero" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | // InitCmd represents the init command 14 | var initCmd = &cobra.Command{ 15 | Use: "init", 16 | Short: "Initialize a migrations directory", 17 | Long: ``, 18 | Run: func(cmd *cobra.Command, args []string) { 19 | FS := afero.NewOsFs() 20 | wd, _ := os.Getwd() 21 | 22 | err := cmd2.RunInit(cmd, FS, wd) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | }, 27 | } 28 | 29 | func init() { 30 | rootCmd.AddCommand(initCmd) 31 | 32 | // Here you will define your flags and configuration settings. 33 | 34 | // Cobra supports Persistent Flags which will work for this command 35 | // and all subcommands, e.g.: 36 | // initCmd.PersistentFlags().String("foo", "", "A help for foo") 37 | 38 | // Cobra supports local flags which will only run when this command 39 | // is called directly, e.g.: 40 | // initCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 41 | } 42 | -------------------------------------------------------------------------------- /cmd/list.go: -------------------------------------------------------------------------------- 1 | // Package cmd /* 2 | package cmd 3 | 4 | import ( 5 | "log" 6 | "os" 7 | 8 | cmd2 "github.com/g14a/metana/pkg/cmd" 9 | "github.com/spf13/afero" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | // listCmd represents the list command 14 | var listCmd = &cobra.Command{ 15 | Use: "list", 16 | Short: "List existing migrations", 17 | Long: ``, 18 | RunE: func(cmd *cobra.Command, args []string) error { 19 | FS := afero.NewOsFs() 20 | wd, err := os.Getwd() 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | return cmd2.RunList(cmd, wd, FS) 25 | }, 26 | } 27 | 28 | func init() { 29 | listCmd.Flags().StringP("store", "s", "", "Specify a connection URL to track migrations") 30 | 31 | rootCmd.AddCommand(listCmd) 32 | 33 | // Here you will define your flags and configuration settings. 34 | 35 | // Cobra supports Persistent Flags which will work for this command 36 | // and all subcommands, e.g.: 37 | // listCmd.PersistentFlags().String("foo", "", "A help for foo") 38 | 39 | // Cobra supports local flags which will only run when this command 40 | // is called directly, e.g.: 41 | // listCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 42 | } 43 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | // Package cmd /* 2 | package cmd 3 | 4 | import ( 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/spf13/cobra" 10 | 11 | "github.com/mitchellh/go-homedir" 12 | "github.com/spf13/viper" 13 | ) 14 | 15 | var cfgFile string 16 | 17 | // rootCmd represents the base command when called without any subcommands 18 | var rootCmd = &cobra.Command{ 19 | Use: "metana", 20 | Short: "A brief description of your application", 21 | Long: `An abstract migration tool for Go services`, 22 | // Uncomment the following line if your bare application 23 | // has an action associated with it: 24 | Run: func(cmd *cobra.Command, args []string) { 25 | if len(args) == 0 { 26 | cmd.Help() 27 | } 28 | }, 29 | } 30 | 31 | // Execute adds all child commands to the root command and sets flags appropriately. 32 | // This is called by main.main(). It only needs to happen once to the rootCmd. 33 | func Execute() { 34 | if err := rootCmd.Execute(); err != nil { 35 | log.Fatal(err) 36 | os.Exit(1) 37 | } 38 | } 39 | 40 | func init() { 41 | cobra.OnInitialize(initConfig) 42 | 43 | // Here you will define your flags and configuration settings. 44 | // Cobra supports persistent flags, which, if defined here, 45 | // will be global for your application. 46 | 47 | rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config gen (default is $HOME/.metana.yaml)") 48 | // Cobra also supports local flags, which will only run 49 | // when this action is called directly. 50 | rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 51 | } 52 | 53 | // initConfig reads in config gen and ENV variables if set. 54 | func initConfig() { 55 | if cfgFile != "" { 56 | // Use config gen from the flag. 57 | viper.SetConfigFile(cfgFile) 58 | } else { 59 | // Find home directory. 60 | home, err := homedir.Dir() 61 | if err != nil { 62 | fmt.Println(err) 63 | os.Exit(1) 64 | } 65 | 66 | // Search config in home directory with name ".metana" (without extension). 67 | viper.AddConfigPath(home) 68 | viper.SetConfigName(".metana") 69 | } 70 | 71 | viper.AutomaticEnv() // read in environment variables that match 72 | 73 | // If a config gen is found, read it in. 74 | if err := viper.ReadInConfig(); err == nil { 75 | fmt.Println("Using config gen:", viper.ConfigFileUsed()) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /cmd/up.go: -------------------------------------------------------------------------------- 1 | // Package cmd /* 2 | package cmd 3 | 4 | import ( 5 | "os" 6 | "path/filepath" 7 | "strings" 8 | 9 | "github.com/g14a/metana/pkg" 10 | 11 | cmd2 "github.com/g14a/metana/pkg/cmd" 12 | "github.com/spf13/afero" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | // upCmd represents the up command 17 | var upCmd = &cobra.Command{ 18 | Use: "up", 19 | Short: "Run upward migrations", 20 | Long: ``, 21 | RunE: func(cmd *cobra.Command, args []string) error { 22 | FS := afero.NewOsFs() 23 | wd, _ := os.Getwd() 24 | 25 | err := cmd2.RunUp(cmd, args, FS, wd) 26 | if err != nil { 27 | // Prevent Cobra from printing help on execution errors 28 | cmd.SilenceUsage = true 29 | cmd.SilenceErrors = true 30 | } 31 | return err 32 | }, 33 | } 34 | 35 | func init() { 36 | rootCmd.AddCommand(upCmd) 37 | upCmd.Flags().StringP("until", "u", "", "Migrate up until a specific point\n") 38 | upCmd.Flags().StringP("store", "s", "", "Specify a connection url to track migrations") 39 | upCmd.Flags().Bool("dry", false, "Specify if the upward migration is a dry run {true | false}") 40 | 41 | upCmd.RegisterFlagCompletionFunc("until", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 42 | FS := afero.NewOsFs() 43 | 44 | wd, err := os.Getwd() 45 | if err != nil { 46 | return nil, 0 47 | } 48 | 49 | finalDir := "migrations" 50 | 51 | migrations, err := pkg.GetMigrations(filepath.Join(wd, finalDir), FS) 52 | if err != nil { 53 | return nil, cobra.ShellCompDirectiveError 54 | } 55 | 56 | var names []string 57 | 58 | for _, m := range migrations { 59 | name := strings.TrimSuffix(m.Name, ".go") 60 | names = append(names, strings.Split(name, "-")[1]) 61 | } 62 | 63 | return names, cobra.ShellCompDirectiveDefault 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/g14a/metana 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/fatih/color v1.7.0 7 | github.com/go-pg/pg/v10 v10.9.1 8 | github.com/iancoleman/strcase v0.1.3 9 | github.com/mitchellh/go-homedir v1.1.0 10 | github.com/olekukonko/tablewriter v0.0.5 11 | github.com/spf13/afero v1.6.0 12 | github.com/spf13/cobra v1.1.3 13 | github.com/spf13/viper v1.7.1 14 | go.mongodb.org/mongo-driver v1.5.2 15 | ) 16 | 17 | require ( 18 | github.com/aws/aws-sdk-go v1.34.28 // indirect 19 | github.com/fsnotify/fsnotify v1.4.9 // indirect 20 | github.com/go-pg/zerochecker v0.2.0 // indirect 21 | github.com/go-stack/stack v1.8.0 // indirect 22 | github.com/golang/snappy v0.0.1 // indirect 23 | github.com/hashicorp/hcl v1.0.0 // indirect 24 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 25 | github.com/jinzhu/inflection v1.0.0 // indirect 26 | github.com/jmespath/go-jmespath v0.4.0 // indirect 27 | github.com/klauspost/compress v1.9.5 // indirect 28 | github.com/magiconair/properties v1.8.1 // indirect 29 | github.com/mattn/go-colorable v0.1.2 // indirect 30 | github.com/mattn/go-isatty v0.0.12 // indirect 31 | github.com/mattn/go-runewidth v0.0.9 // indirect 32 | github.com/mitchellh/mapstructure v1.1.2 // indirect 33 | github.com/pelletier/go-toml v1.7.0 // indirect 34 | github.com/pkg/errors v0.9.1 // indirect 35 | github.com/spf13/cast v1.3.0 // indirect 36 | github.com/spf13/jwalterweatherman v1.0.0 // indirect 37 | github.com/spf13/pflag v1.0.5 // indirect 38 | github.com/subosito/gotenv v1.2.0 // indirect 39 | github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect 40 | github.com/vmihailenco/bufpool v0.1.11 // indirect 41 | github.com/vmihailenco/msgpack/v5 v5.3.0 // indirect 42 | github.com/vmihailenco/tagparser v0.1.2 // indirect 43 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 44 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 45 | github.com/xdg-go/scram v1.0.2 // indirect 46 | github.com/xdg-go/stringprep v1.0.2 // indirect 47 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect 48 | go.opentelemetry.io/otel v0.19.0 // indirect 49 | go.opentelemetry.io/otel/metric v0.19.0 // indirect 50 | go.opentelemetry.io/otel/trace v0.19.0 // indirect 51 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect 52 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect 53 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 // indirect 54 | golang.org/x/text v0.3.5 // indirect 55 | gopkg.in/ini.v1 v1.51.0 // indirect 56 | gopkg.in/yaml.v2 v2.4.0 // indirect 57 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 58 | mellium.im/sasl v0.2.1 // indirect 59 | ) 60 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 9 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 10 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 11 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 12 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 13 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 14 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 15 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 16 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 17 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 18 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 19 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 20 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 21 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 22 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 23 | github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk= 24 | github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= 25 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 26 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 27 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 28 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= 29 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 30 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 31 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 32 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 33 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 34 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 35 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 36 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 37 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 38 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 39 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 40 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 41 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 42 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 43 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 44 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 45 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= 46 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 47 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 48 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 49 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 50 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 51 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 52 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 53 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 54 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 55 | github.com/go-pg/pg/v10 v10.9.1 h1:kU4t84zWGGaU0Qsu49FbNtToUVrlSTkNOngW8aQmwvk= 56 | github.com/go-pg/pg/v10 v10.9.1/go.mod h1:rgmTPgHgl5EN2CNKKoMwC7QT62t8BqsdpEkUQuiZMQs= 57 | github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU= 58 | github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo= 59 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 60 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= 61 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 62 | github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= 63 | github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= 64 | github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= 65 | github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= 66 | github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= 67 | github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= 68 | github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= 69 | github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= 70 | github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= 71 | github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= 72 | github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= 73 | github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= 74 | github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= 75 | github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= 76 | github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= 77 | github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= 78 | github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= 79 | github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= 80 | github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= 81 | github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= 82 | github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= 83 | github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= 84 | github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= 85 | github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= 86 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 87 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 88 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 89 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 90 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 91 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 92 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 93 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 94 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 95 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 96 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 97 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 98 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 99 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 100 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 101 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 102 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 103 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 104 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= 105 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 106 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 107 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 108 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 109 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 110 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 111 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 112 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 113 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 114 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 115 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 116 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 117 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 118 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 119 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 120 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 121 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 122 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 123 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 124 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 125 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 126 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 127 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 128 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 129 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 130 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 131 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 132 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 133 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 134 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 135 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 136 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 137 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 138 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 139 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 140 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 141 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 142 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 143 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 144 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 145 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 146 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 147 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 148 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 149 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 150 | github.com/iancoleman/strcase v0.1.3 h1:dJBk1m2/qjL1twPLf68JND55vvivMupZ4wIzE8CTdBw= 151 | github.com/iancoleman/strcase v0.1.3/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= 152 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 153 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 154 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 155 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 156 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 157 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 158 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 159 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 160 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 161 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 162 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 163 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 164 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 165 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 166 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 167 | github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= 168 | github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= 169 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 170 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 171 | github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= 172 | github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 173 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 174 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 175 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 176 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 177 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 178 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 179 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 180 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 181 | github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= 182 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 183 | github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= 184 | github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= 185 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 186 | github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= 187 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 188 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 189 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 190 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 191 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 192 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= 193 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 194 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 195 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 196 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 197 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 198 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 199 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 200 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 201 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 202 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 203 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 204 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 205 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 206 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 207 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 208 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= 209 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 210 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 211 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 212 | github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= 213 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 214 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 215 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 216 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 217 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 218 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 219 | github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M= 220 | github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 221 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 222 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 223 | github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= 224 | github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= 225 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 226 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 227 | github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI= 228 | github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= 229 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 230 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 231 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 232 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 233 | github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= 234 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 235 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 236 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 237 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 238 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 239 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 240 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 241 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 242 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 243 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 244 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 245 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 246 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 247 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 248 | github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 249 | github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 250 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 251 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 252 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 253 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 254 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 255 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 256 | github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 257 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 258 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 259 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= 260 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 261 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= 262 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 263 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 264 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 265 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 266 | github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= 267 | github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= 268 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= 269 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 270 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 271 | github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= 272 | github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= 273 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= 274 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 275 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 276 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 277 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 278 | github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= 279 | github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= 280 | github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= 281 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 282 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 283 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 284 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 285 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 286 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 287 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 288 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 289 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 290 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= 291 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 292 | github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= 293 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 294 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 295 | github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= 296 | github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= 297 | github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94= 298 | github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ= 299 | github.com/vmihailenco/msgpack/v5 v5.3.0 h1:8G3at/kelmBKeHY6d6cKnGsYO3BLn+uubitdOtOhyNI= 300 | github.com/vmihailenco/msgpack/v5 v5.3.0/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= 301 | github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc= 302 | github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= 303 | github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= 304 | github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= 305 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= 306 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 307 | github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w= 308 | github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= 309 | github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc= 310 | github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= 311 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 312 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= 313 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= 314 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 315 | go.mongodb.org/mongo-driver v1.5.2 h1:AsxOLoJTgP6YNM0fXWw4OjdluYmWzQYp+lFJL7xu9fU= 316 | go.mongodb.org/mongo-driver v1.5.2/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw= 317 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 318 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 319 | go.opentelemetry.io/otel v0.19.0 h1:Lenfy7QHRXPZVsw/12CWpxX6d/JkrX8wrx2vO8G80Ng= 320 | go.opentelemetry.io/otel v0.19.0/go.mod h1:j9bF567N9EfomkSidSfmMwIwIBuP37AMAIzVW85OxSg= 321 | go.opentelemetry.io/otel/metric v0.19.0 h1:dtZ1Ju44gkJkYvo+3qGqVXmf88tc+a42edOywypengg= 322 | go.opentelemetry.io/otel/metric v0.19.0/go.mod h1:8f9fglJPRnXuskQmKpnad31lcLJ2VmNNqIsx/uIwBSc= 323 | go.opentelemetry.io/otel/oteltest v0.19.0 h1:YVfA0ByROYqTwOxqHVZYZExzEpfZor+MU1rU+ip2v9Q= 324 | go.opentelemetry.io/otel/oteltest v0.19.0/go.mod h1:tI4yxwh8U21v7JD6R3BcA/2+RBoTKFexE/PJ/nSO7IA= 325 | go.opentelemetry.io/otel/trace v0.19.0 h1:1ucYlenXIDA1OlHVLDZKX0ObXV5RLaq06DtUKz5e5zc= 326 | go.opentelemetry.io/otel/trace v0.19.0/go.mod h1:4IXiNextNOpPnRlI4ryK69mn5iC84bjBWZQA5DXz/qg= 327 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 328 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 329 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 330 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 331 | golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 332 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 333 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 334 | golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 335 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 336 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 337 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 338 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 339 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 340 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= 341 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 342 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 343 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 344 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 345 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 346 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 347 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 348 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 349 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 350 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 351 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 352 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 353 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 354 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 355 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 356 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 357 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 358 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 359 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 360 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 361 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 362 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 363 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 364 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 365 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 366 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 367 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 368 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 369 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 370 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 371 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 372 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 373 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 374 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 375 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 376 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 377 | golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 378 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= 379 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 380 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 381 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 382 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 383 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 384 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 385 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 386 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 387 | golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 388 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 389 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= 390 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 391 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 392 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 393 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 394 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 395 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 396 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 397 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 398 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 399 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 400 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 401 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 402 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 403 | golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 404 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 405 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 406 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 407 | golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 408 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 409 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 410 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 411 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 412 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 413 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 414 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 415 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 416 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 417 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 418 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 h1:EZ2mChiOa8udjfp6rRmswTbtZN/QzUQp4ptM4rnjHvc= 419 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 420 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 421 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 422 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 423 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 424 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 425 | golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= 426 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 427 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 428 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 429 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 430 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 431 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 432 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 433 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 434 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 435 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 436 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 437 | golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 438 | golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 439 | golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 440 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 441 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 442 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 443 | golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 444 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 445 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 446 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 447 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 448 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 449 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 450 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 451 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 452 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 453 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 454 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 455 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 456 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 457 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 458 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 459 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 460 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 461 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 462 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 463 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 464 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 465 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 466 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 467 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 468 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 469 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 470 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 471 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 472 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 473 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 474 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 475 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 476 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 477 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 478 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 479 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 480 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 481 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 482 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 483 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 484 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 485 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 486 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 487 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 488 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 489 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 490 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 491 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 492 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 493 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 494 | gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= 495 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 496 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 497 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 498 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 499 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 500 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 501 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 502 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 503 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 504 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 505 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 506 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 507 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 508 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 509 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 510 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 511 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 512 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 513 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 514 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 515 | mellium.im/sasl v0.2.1 h1:nspKSRg7/SyO0cRGY71OkfHab8tf9kCts6a6oTDut0w= 516 | mellium.im/sasl v0.2.1/go.mod h1:ROaEDLQNuf9vjKqE1SrAfnsobm2YKXT1gnN1uDp1PjQ= 517 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 518 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 NAME HERE 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this gen except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package main 17 | 18 | import "github.com/g14a/metana/cmd" 19 | 20 | func main() { 21 | cmd.Execute() 22 | } 23 | -------------------------------------------------------------------------------- /metana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g14a/metana/fa95b47596f7d3aaf5c2c278f7cc92b33cc53781/metana.png -------------------------------------------------------------------------------- /pkg/cmd/create.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/fatih/color" 8 | "github.com/g14a/metana/pkg/core/gen" 9 | "github.com/spf13/afero" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func RunCreate(cmd *cobra.Command, args []string, fs afero.Fs, wd string) error { 14 | time.Sleep(1 * time.Second) 15 | 16 | if len(args) == 0 { 17 | return fmt.Errorf("missing migration name") 18 | } 19 | 20 | finalDir := resolveDir() 21 | 22 | if exists, _ := gen.MigrationExists(wd, finalDir, args[0], fs); exists { 23 | color.Yellow("Migration already exists") 24 | return nil 25 | } 26 | 27 | createdFile, err := gen.CreateMigrationFile(gen.CreateMigrationOpts{ 28 | Wd: wd, 29 | MigrationsDir: finalDir, 30 | File: args[0], 31 | FS: fs, 32 | }) 33 | if err != nil { 34 | return fmt.Errorf("failed to create migration: %w\nTry initializing with `metana init`", err) 35 | } 36 | 37 | fmt.Fprintf(cmd.OutOrStdout(), color.GreenString(" ✓ Created %s\n", createdFile)) 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /pkg/cmd/down.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/fatih/color" 7 | "github.com/g14a/metana/pkg/core/migrate" 8 | "github.com/g14a/metana/pkg/store" 9 | "github.com/g14a/metana/pkg/types" 10 | "github.com/spf13/afero" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func RunDown(cmd *cobra.Command, args []string, fs afero.Fs, wd string) error { 15 | storeFlag, _ := cmd.Flags().GetString("store") 16 | until, _ := cmd.Flags().GetString("until") 17 | dryRun, _ := cmd.Flags().GetBool("dry") 18 | 19 | finalDir := resolveDir() 20 | finalStore := resolveStore(storeFlag) 21 | 22 | finalDir = cleanFinalDir(wd, finalDir) 23 | 24 | var track types.Track 25 | var storeHouse store.Store 26 | 27 | if !dryRun { 28 | sh, err := store.GetStoreViaConn(finalStore, finalDir, fs, wd) 29 | if err != nil { 30 | return err 31 | } 32 | track, err = sh.Load(fs) 33 | if err != nil { 34 | return err 35 | } 36 | storeHouse = sh 37 | } 38 | 39 | if len(track.Migrations) == 0 && !dryRun { 40 | fmt.Fprintf(cmd.OutOrStdout(), color.YellowString("at least one upward migration needed\n")) 41 | return nil 42 | } 43 | 44 | opts := migrate.MigrationOptions{ 45 | Until: until, 46 | MigrationsDir: finalDir, 47 | Wd: wd, 48 | Up: false, 49 | StoreConn: finalStore, 50 | DryRun: dryRun, 51 | } 52 | 53 | output, err := migrate.Run(opts) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | if dryRun { 59 | fmt.Fprintf(cmd.OutOrStdout(), color.WhiteString(" >>> dry run migration : complete\n")) 60 | return nil 61 | } 62 | 63 | _, num := store.ProcessLogs(output) 64 | newTrack := store.TrackToSetDown(track, num) 65 | 66 | if err := storeHouse.Set(newTrack, fs); err != nil { 67 | return err 68 | } 69 | 70 | fmt.Fprintf(cmd.OutOrStdout(), color.GreenString(" >>> migration : complete\n")) 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /pkg/cmd/helpers.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "path/filepath" 5 | 6 | "github.com/spf13/afero" 7 | ) 8 | 9 | // resolveDir simply returns the CLI flag if set, otherwise default "migrations" 10 | func resolveDir() string { 11 | return "migrations" 12 | } 13 | 14 | // resolveStore simply returns the CLI store flag if set, otherwise empty 15 | func resolveStore(storeFlag string) string { 16 | if storeFlag != "" { 17 | return storeFlag 18 | } 19 | return "" 20 | } 21 | 22 | // cleanFinalDir normalizes finalDir to be relative to wd 23 | func cleanFinalDir(wd, dir string) string { 24 | if filepath.IsAbs(dir) { 25 | if rel, err := filepath.Rel(wd, dir); err == nil { 26 | return rel 27 | } 28 | } 29 | return dir 30 | } 31 | 32 | // mkdirScripts creates the scripts/ folder safely 33 | func mkdirScripts(fs afero.Fs, baseDir string) error { 34 | return fs.MkdirAll(filepath.Join(baseDir, "scripts"), 0755) 35 | } 36 | -------------------------------------------------------------------------------- /pkg/cmd/init.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/fatih/color" 5 | "github.com/spf13/afero" 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | func RunInit(cmd *cobra.Command, fs afero.Fs, wd string) error { 10 | finalDir := resolveDir() 11 | 12 | if err := mkdirScripts(fs, finalDir); err != nil { 13 | return err 14 | } 15 | 16 | color.Green("Successfully initialized migration setup in '%s' folder", finalDir) 17 | return nil 18 | } 19 | -------------------------------------------------------------------------------- /pkg/cmd/list.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/g14a/metana/pkg" 7 | "github.com/g14a/metana/pkg/store" 8 | "github.com/spf13/afero" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func RunList(cmd *cobra.Command, wd string, fs afero.Fs) error { 13 | storeFlag, _ := cmd.Flags().GetString("store") 14 | 15 | finalDir := resolveDir() 16 | finalStore := resolveStore(storeFlag) 17 | 18 | st, err := store.GetStoreViaConn(finalStore, finalDir, fs, wd) 19 | if err != nil { 20 | fmt.Println("⚠️ Warning: store could not be initialized:", err) 21 | st = nil 22 | } 23 | 24 | return pkg.ListMigrations(cmd, finalDir, fs, st) 25 | } 26 | -------------------------------------------------------------------------------- /pkg/cmd/up.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | 7 | "github.com/fatih/color" 8 | "github.com/g14a/metana/pkg/core/migrate" 9 | "github.com/g14a/metana/pkg/store" 10 | "github.com/g14a/metana/pkg/types" 11 | "github.com/spf13/afero" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | func RunUp(cmd *cobra.Command, args []string, fs afero.Fs, wd string) error { 16 | storeFlag, _ := cmd.Flags().GetString("store") 17 | until, _ := cmd.Flags().GetString("until") 18 | dryRun, _ := cmd.Flags().GetBool("dry") 19 | 20 | finalDir := resolveDir() 21 | finalStore := resolveStore(storeFlag) 22 | 23 | finalDir = cleanFinalDir(wd, finalDir) 24 | 25 | opts := migrate.MigrationOptions{ 26 | Until: until, 27 | MigrationsDir: finalDir, 28 | Wd: wd, 29 | Up: true, 30 | StoreConn: finalStore, 31 | DryRun: dryRun, 32 | } 33 | 34 | output, err := migrate.Run(opts) 35 | track, _ := store.ProcessLogs(output) 36 | 37 | if !dryRun && len(track.Migrations) > 0 { 38 | sh, err := store.GetStoreViaConn(finalStore, finalDir, fs, wd) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | existingTrack, err := sh.Load(fs) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | existingMap := make(map[string]types.Migration) 49 | for _, m := range existingTrack.Migrations { 50 | existingMap[m.Title] = m 51 | } 52 | for _, m := range track.Migrations { 53 | existingMap[m.Title] = m 54 | } 55 | 56 | var merged []types.Migration 57 | for _, m := range existingMap { 58 | merged = append(merged, m) 59 | } 60 | sort.Slice(merged, func(i, j int) bool { 61 | return merged[i].Title < merged[j].Title 62 | }) 63 | 64 | existingTrack.Migrations = merged 65 | existingTrack.LastRun = track.LastRun 66 | 67 | if err := sh.Set(existingTrack, fs); err != nil { 68 | return err 69 | } 70 | } 71 | 72 | if dryRun { 73 | fmt.Fprintf(cmd.OutOrStdout(), color.WhiteString(" >>> dry run migration : complete\n")) 74 | } else { 75 | fmt.Fprintf(cmd.OutOrStdout(), color.GreenString(" >>> migration : complete\n")) 76 | } 77 | 78 | return err 79 | } 80 | -------------------------------------------------------------------------------- /pkg/core/gen/file.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/format" 7 | "path/filepath" 8 | "strconv" 9 | "strings" 10 | "text/template" 11 | "time" 12 | 13 | "github.com/g14a/metana/pkg" 14 | "github.com/g14a/metana/pkg/core/tpl" 15 | "github.com/iancoleman/strcase" 16 | "github.com/spf13/afero" 17 | ) 18 | 19 | func CreateMigrationFile(opts CreateMigrationOpts) (string, error) { 20 | timestamp := strconv.Itoa(int(time.Now().Unix())) 21 | migrationName := strcase.ToCamel(opts.File) 22 | 23 | relativeFilePath := filepath.Join(opts.MigrationsDir, "scripts", fmt.Sprintf("%s_%s.go", timestamp, opts.File)) 24 | 25 | // Use standalone template 26 | mainTemplate := template.Must( 27 | template.New("standalone"). 28 | Parse(string(tpl.StandaloneMigrationTemplate())), 29 | ) 30 | 31 | templateData := map[string]string{ 32 | "MigrationName": migrationName, 33 | "Timestamp": timestamp, 34 | "Filename": fmt.Sprintf("%s_%s.go", timestamp, opts.File), 35 | } 36 | 37 | var buff bytes.Buffer 38 | if err := mainTemplate.Execute(&buff, templateData); err != nil { 39 | return "", err 40 | } 41 | 42 | fmtBytes, err := format.Source(buff.Bytes()) 43 | if err != nil { 44 | return "", err 45 | } 46 | 47 | if err := afero.WriteFile(opts.FS, relativeFilePath, fmtBytes, 0644); err != nil { 48 | return "", err 49 | } 50 | 51 | return relativeFilePath, nil 52 | } 53 | 54 | func MigrationExists(wd, migrationsDir, migrationName string, FS afero.Fs) (bool, error) { 55 | camelCaseMigration := strcase.ToCamel(migrationName) 56 | 57 | migrations, err := pkg.GetMigrations(migrationsDir, FS) 58 | if err != nil { 59 | return false, err 60 | } 61 | 62 | for _, m := range migrations { 63 | mig := strings.TrimSuffix(m.Name, ".go") 64 | mig = strings.TrimLeftFunc(mig, func(r rune) bool { 65 | return r >= 48 && r <= 57 || r == '-' 66 | }) 67 | if camelCaseMigration == mig { 68 | return true, nil 69 | } 70 | } 71 | 72 | return false, nil 73 | } 74 | 75 | type CreateMigrationOpts struct { 76 | Wd string 77 | MigrationsDir string 78 | File string 79 | FS afero.Fs 80 | } 81 | -------------------------------------------------------------------------------- /pkg/core/migrate/updown.go: -------------------------------------------------------------------------------- 1 | package migrate 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "os/exec" 8 | "path/filepath" 9 | "sort" 10 | "strings" 11 | 12 | "github.com/fatih/color" 13 | "github.com/g14a/metana/pkg/store" 14 | "github.com/spf13/afero" 15 | ) 16 | 17 | func Run(opts MigrationOptions) (string, error) { 18 | var scriptsDir string 19 | if filepath.IsAbs(opts.MigrationsDir) { 20 | scriptsDir = filepath.Join(opts.MigrationsDir, "scripts") 21 | } else { 22 | scriptsDir = filepath.Join(opts.Wd, opts.MigrationsDir, "scripts") 23 | } 24 | 25 | files, err := filepath.Glob(filepath.Join(scriptsDir, "*.go")) 26 | if err != nil { 27 | return "", err 28 | } 29 | 30 | if len(files) == 0 { 31 | return "", fmt.Errorf("no migrations found in %s", scriptsDir) 32 | } 33 | 34 | sort.Strings(files) 35 | 36 | executed := make(map[string]bool) 37 | if !opts.DryRun { 38 | sh, err := store.GetStoreViaConn(opts.StoreConn, opts.MigrationsDir, afero.NewOsFs(), opts.Wd) 39 | if err == nil { 40 | track, err := sh.Load(afero.NewOsFs()) 41 | if err == nil { 42 | for _, m := range track.Migrations { 43 | executed[m.Title] = true 44 | } 45 | } 46 | } 47 | } 48 | 49 | var allOutput strings.Builder 50 | 51 | for _, file := range files { 52 | base := filepath.Base(file) 53 | migrationName := strings.TrimSuffix(strings.SplitN(base, "_", 2)[1], ".go") 54 | 55 | // In non-dry run mode, skip migrations based on idempotency 56 | if !opts.DryRun { 57 | if opts.Up && executed[base] { 58 | continue 59 | } 60 | if !opts.Up && !executed[base] { 61 | continue 62 | } 63 | } 64 | 65 | runMigration := func(mode string) error { 66 | args := []string{"run", file, "-mode", mode} 67 | cmd := exec.Command("go", args...) 68 | cmd.Dir = opts.Wd 69 | 70 | var stderr bytes.Buffer 71 | cmd.Stderr = &stderr 72 | 73 | stdoutPipe, err := cmd.StdoutPipe() 74 | if err != nil { 75 | return fmt.Errorf("stdout error: %w", err) 76 | } 77 | 78 | if err := cmd.Start(); err != nil { 79 | return fmt.Errorf("start error: %w", err) 80 | } 81 | 82 | scanner := bufio.NewScanner(stdoutPipe) 83 | for scanner.Scan() { 84 | line := scanner.Text() 85 | color.Cyan("%s", line) 86 | allOutput.WriteString(line + "\n") 87 | } 88 | 89 | if err := cmd.Wait(); err != nil { 90 | return fmt.Errorf("execution error: %v\n%s", err, stderr.String()) 91 | } 92 | return nil 93 | } 94 | 95 | if opts.Up { 96 | upErr := runMigration("up") 97 | if upErr != nil { 98 | color.Red("Migration %s failed, attempting rollback...\n", base) 99 | downErr := runMigration("down") 100 | if downErr != nil { 101 | return allOutput.String(), fmt.Errorf("rollback of %s also failed: %v", base, downErr) 102 | } 103 | return allOutput.String(), fmt.Errorf("migration %s failed: %v", base, upErr) 104 | } 105 | } else { 106 | if err := runMigration("down"); err != nil { 107 | return allOutput.String(), fmt.Errorf("migration %s failed: %v", base, err) 108 | } 109 | } 110 | 111 | if opts.Until != "" && migrationName == opts.Until { 112 | color.Yellow(" >>> Reached --until: %s. Stopping further migrations.\n", opts.Until) 113 | break 114 | } 115 | } 116 | 117 | return allOutput.String(), nil 118 | } 119 | 120 | type MigrationOptions struct { 121 | Until string 122 | MigrationsDir string 123 | Wd string 124 | LastRunTS int 125 | Up bool 126 | StoreConn string 127 | DryRun bool 128 | } 129 | -------------------------------------------------------------------------------- /pkg/core/tpl/tpl.go: -------------------------------------------------------------------------------- 1 | package tpl 2 | 3 | // StandaloneMigrationTemplate returns the Go source code for a standalone migration file. 4 | // This file is compiled and executed during migrations using `go run`. 5 | // The output must include "__COMPLETE__: " on success for Metana to track it. 6 | func StandaloneMigrationTemplate() []byte { 7 | upBody := `fmt.Println("{{ .MigrationName }} up")` 8 | downBody := `fmt.Println("{{ .MigrationName }} down")` 9 | 10 | return []byte(`//go:build ignore 11 | // +build ignore 12 | 13 | // ⚠️ AUTO-GENERATED FILE. DO NOT IMPORT THIS INTO YOUR MAIN APPLICATION. 14 | // ⚠️ ONLY modify the 'up()' and 'down()' functions to write your migration logic. 15 | 16 | package main 17 | 18 | import ( 19 | "flag" 20 | "fmt" 21 | "os" 22 | "runtime/debug" 23 | ) 24 | 25 | // ✅ UP migration logic. 26 | // If this returns an error, Metana will auto-trigger the DOWN rollback for this migration. 27 | func up() error { 28 | ` + upBody + ` 29 | return nil 30 | } 31 | 32 | // ✅ DOWN (rollback) logic. 33 | // This will be triggered if 'up()' fails during execution. 34 | func down() error { 35 | ` + downBody + ` 36 | return nil 37 | } 38 | 39 | // 🚫 DO NOT MODIFY. 40 | // Handles flag parsing, error propagation, and execution tracking. 41 | func main() { 42 | mode := flag.String("mode", "up", "migration mode: up or down") 43 | flag.Parse() 44 | 45 | var err error 46 | switch *mode { 47 | case "up": 48 | err = up() 49 | case "down": 50 | err = down() 51 | default: 52 | fmt.Fprintln(os.Stderr, "invalid mode: must be 'up' or 'down'") 53 | os.Exit(1) 54 | } 55 | 56 | if err != nil { 57 | fmt.Fprintf(os.Stderr, "error: %v\n", err) 58 | debug.PrintStack() 59 | os.Exit(1) 60 | } 61 | 62 | fmt.Printf("__COMPLETE__[%s]: %s\n", *mode, "{{ .Filename }}") 63 | } 64 | `) 65 | } 66 | -------------------------------------------------------------------------------- /pkg/list.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "path/filepath" 5 | 6 | "github.com/fatih/color" 7 | "github.com/g14a/metana/pkg/store" 8 | "github.com/olekukonko/tablewriter" 9 | "github.com/spf13/afero" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func ListMigrations(cmd *cobra.Command, migrationsDir string, fs afero.Fs, st store.Store) error { 14 | migrations, err := GetMigrations(migrationsDir, fs) 15 | if err != nil { 16 | return err 17 | } 18 | 19 | executed := map[string]string{} 20 | if st != nil { 21 | track, err := st.Load(fs) 22 | if err != nil { 23 | color.Yellow("Warning: could not load migration store: %v", err) 24 | } else { 25 | for _, m := range track.Migrations { 26 | executed[m.Title] = m.ExecutedAt 27 | } 28 | } 29 | } 30 | 31 | var data [][]string 32 | for _, f := range migrations { 33 | execAt := "" 34 | if val, ok := executed[f.Name]; ok { 35 | execAt = val 36 | } 37 | data = append(data, []string{f.Name, execAt}) 38 | } 39 | 40 | if len(data) > 0 { 41 | table := tablewriter.NewWriter(cmd.OutOrStdout()) 42 | table.SetHeader([]string{"Migration", "Executed At"}) 43 | for _, v := range data { 44 | table.Append(v) 45 | } 46 | table.Render() 47 | } else { 48 | color.Yellow("%s", "No migrations found") 49 | } 50 | 51 | return nil 52 | } 53 | 54 | func GetMigrations(migrationsDir string, FS afero.Fs) ([]Migration, error) { 55 | 56 | matches, err := afero.Glob(FS, filepath.Join(migrationsDir, "scripts", "*.go")) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | var migrations []Migration 62 | for _, f := range matches { 63 | migrations = append(migrations, Migration{ 64 | Name: filepath.Base(f), 65 | }) 66 | } 67 | 68 | return migrations, nil 69 | } 70 | 71 | type Migration struct { 72 | Name string 73 | ModTime string 74 | } 75 | -------------------------------------------------------------------------------- /pkg/mock.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "github.com/g14a/metana/pkg/types" 5 | "github.com/spf13/afero" 6 | ) 7 | 8 | type MockStore struct { 9 | Data map[string]string 10 | } 11 | 12 | func (m *MockStore) Load(FS afero.Fs) (types.Track, error) { 13 | var migrations []types.Migration 14 | for title, ts := range m.Data { 15 | migrations = append(migrations, types.Migration{ 16 | Title: title, 17 | ExecutedAt: ts, 18 | }) 19 | } 20 | return types.Track{Migrations: migrations}, nil 21 | } 22 | 23 | func (m *MockStore) Set(track types.Track, FS afero.Fs) error { 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /pkg/store/file.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/g14a/metana/pkg/types" 7 | "github.com/spf13/afero" 8 | ) 9 | 10 | type File struct { 11 | file afero.File 12 | } 13 | 14 | func (f File) Set(track types.Track, FS afero.Fs) error { 15 | bytes, err := json.MarshalIndent(track, "", " ") 16 | if err != nil { 17 | return err 18 | } 19 | 20 | err = afero.WriteFile(FS, f.file.Name(), bytes, 0644) 21 | if err != nil { 22 | return err 23 | } 24 | return nil 25 | } 26 | 27 | func (f File) Load(FS afero.Fs) (types.Track, error) { 28 | track, err := afero.ReadFile(FS, f.file.Name()) 29 | if err != nil { 30 | return types.Track{}, err 31 | } 32 | 33 | t := types.Track{} 34 | 35 | if len(track) > 0 { 36 | err = json.Unmarshal(track, &t) 37 | if err != nil { 38 | return types.Track{}, err 39 | } 40 | } 41 | 42 | return t, nil 43 | } 44 | 45 | func (f File) Wipe(FS afero.Fs) error { 46 | err := afero.WriteFile(FS, f.file.Name(), nil, 0644) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /pkg/store/mongo.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/spf13/afero" 7 | 8 | "github.com/g14a/metana/pkg/types" 9 | "go.mongodb.org/mongo-driver/bson" 10 | "go.mongodb.org/mongo-driver/mongo" 11 | ) 12 | 13 | type MongoDb struct { 14 | coll mongo.Collection 15 | ctx context.Context 16 | } 17 | 18 | func (m MongoDb) Set(track types.Track, FS afero.Fs) error { 19 | _, err := m.coll.DeleteMany(m.ctx, bson.M{}) 20 | if err != nil { 21 | return err 22 | } 23 | _, err = m.coll.InsertOne(m.ctx, track) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | return nil 29 | } 30 | 31 | func (m MongoDb) Load(FS afero.Fs) (types.Track, error) { 32 | var track types.Track 33 | 34 | err := m.coll.FindOne(m.ctx, bson.M{}).Decode(&track) 35 | if err == mongo.ErrNoDocuments { 36 | return types.Track{}, nil 37 | } 38 | 39 | return track, nil 40 | } 41 | -------------------------------------------------------------------------------- /pkg/store/postgres.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "github.com/g14a/metana/pkg/types" 5 | "github.com/go-pg/pg/v10" 6 | "github.com/go-pg/pg/v10/orm" 7 | "github.com/spf13/afero" 8 | ) 9 | 10 | type PGDB struct { 11 | db *pg.DB 12 | } 13 | 14 | func (p PGDB) Set(track types.Track, FS afero.Fs) error { 15 | err := p.CreateTable() 16 | if err != nil { 17 | return err 18 | } 19 | _, err = p.db.Model(&track).Exec(`TRUNCATE migrations`) 20 | if err != nil { 21 | return err 22 | } 23 | 24 | _, err = p.db.Model(&track).Insert(&track) 25 | if err != nil { 26 | return err 27 | } 28 | return nil 29 | } 30 | 31 | func (p PGDB) Load(FS afero.Fs) (types.Track, error) { 32 | var track types.Track 33 | 34 | err := p.db.Model(&track).Select(&track) 35 | if err == pg.ErrNoRows { 36 | return types.Track{}, nil 37 | } 38 | 39 | return track, nil 40 | } 41 | 42 | func (p PGDB) CreateTable() error { 43 | err := p.db.Model(&types.Track{}).CreateTable(&orm.CreateTableOptions{ 44 | IfNotExists: true, 45 | }) 46 | 47 | if err != nil { 48 | return err 49 | } 50 | 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /pkg/store/store.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | fp "path/filepath" 8 | "strings" 9 | "time" 10 | 11 | "github.com/g14a/metana/pkg/types" 12 | "github.com/go-pg/pg/v10" 13 | "github.com/spf13/afero" 14 | "go.mongodb.org/mongo-driver/mongo" 15 | "go.mongodb.org/mongo-driver/mongo/options" 16 | mconnString "go.mongodb.org/mongo-driver/x/mongo/driver/connstring" 17 | ) 18 | 19 | type Store interface { 20 | Set(track types.Track, FS afero.Fs) error 21 | Load(FS afero.Fs) (types.Track, error) 22 | } 23 | 24 | func GetStoreViaConn(connStr, dir string, fs afero.Fs, wd string) (Store, error) { 25 | connStr = resolveEnvVar(connStr) 26 | 27 | switch { 28 | case strings.Contains(connStr, "postgres://"): 29 | return setupPostgres(connStr) 30 | case strings.Contains(connStr, "mongodb"): 31 | return setupMongo(connStr) 32 | default: 33 | return setupFileStore(fs, fp.Join(wd, dir, "migrate.json")) 34 | } 35 | } 36 | 37 | func resolveEnvVar(conn string) string { 38 | if strings.HasPrefix(conn, "@") { 39 | return os.Getenv(strings.TrimPrefix(conn, "@")) 40 | } 41 | return conn 42 | } 43 | 44 | func setupPostgres(connStr string) (Store, error) { 45 | opts, err := pg.ParseURL(connStr) 46 | if err != nil { 47 | return nil, fmt.Errorf("invalid postgres URL: %w", err) 48 | } 49 | db := pg.Connect(opts) 50 | if _, err := db.Exec("SELECT 1"); err != nil { 51 | return nil, fmt.Errorf("postgres connection failed: %w", err) 52 | } 53 | p := PGDB{db: db} 54 | if err := p.CreateTable(); err != nil { 55 | return nil, fmt.Errorf("failed to create migrations table: %w", err) 56 | } 57 | return p, nil 58 | } 59 | 60 | func setupMongo(connStr string) (Store, error) { 61 | cs, err := mconnString.ParseAndValidate(connStr) 62 | if err != nil { 63 | return nil, err 64 | } 65 | client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(connStr)) 66 | if err != nil { 67 | return nil, fmt.Errorf("mongo connection failed: %w", err) 68 | } 69 | if err := client.Ping(context.TODO(), nil); err != nil { 70 | return nil, err 71 | } 72 | return MongoDb{coll: *client.Database(cs.Database).Collection("migrations")}, nil 73 | } 74 | 75 | func setupFileStore(fs afero.Fs, path string) (Store, error) { 76 | file, err := fs.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 77 | if err != nil { 78 | return nil, err 79 | } 80 | return File{file: file}, nil 81 | } 82 | 83 | func TrackToSetDown(track types.Track, num int) types.Track { 84 | if len(track.Migrations) < num || num <= 0 { 85 | return types.Track{} 86 | } 87 | track.Migrations = track.Migrations[:len(track.Migrations)-num] 88 | if len(track.Migrations) == 0 { 89 | return types.Track{} 90 | } 91 | track.LastRun = track.Migrations[len(track.Migrations)-1].Title 92 | return track 93 | } 94 | 95 | func ProcessLogs(logs string) (types.Track, int) { 96 | track := types.Track{} 97 | count := 0 98 | 99 | for _, line := range strings.Split(logs, "\n") { 100 | line = strings.TrimSpace(line) 101 | if !strings.HasPrefix(line, "__COMPLETE__[up]:") { 102 | continue 103 | } 104 | 105 | filename := strings.TrimPrefix(line, "__COMPLETE__[up]:") 106 | m := types.Migration{ 107 | Title: strings.TrimSpace(filename), 108 | ExecutedAt: time.Now().Format("02-01-2006 15:04"), 109 | } 110 | track.Migrations = append(track.Migrations, m) 111 | track.LastRun = m.Title 112 | count++ 113 | } 114 | return track, count 115 | } 116 | -------------------------------------------------------------------------------- /pkg/types/type.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type Migration struct { 4 | Title string `json:"title"` 5 | ExecutedAt string `json:"executed_at,omitempty"` 6 | } 7 | 8 | type Track struct { 9 | tableName struct{} `pg:"migrations"` 10 | LastRun string `pg:"last_run" json:"LastRun"` 11 | Migrations []Migration `pg:"migrations" json:"Migrations"` 12 | } 13 | 14 | type Migrator interface { 15 | Up() error 16 | Down() error 17 | } 18 | -------------------------------------------------------------------------------- /tests/tests_create.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "🔵 Starting INIT + CREATE migration tests..." 6 | 7 | function check_metana_binary() { 8 | if ! command -v metana &> /dev/null; then 9 | echo "❌ metana binary not found in PATH. Please install it first with: go install ./..." 10 | exit 1 11 | fi 12 | echo "✅ metana binary found at $(which metana)" 13 | } 14 | 15 | function create_temp_dir() { 16 | TEMP_DIR=$(mktemp -d) 17 | echo "✅ Created temp directory: $TEMP_DIR" 18 | cd $TEMP_DIR 19 | } 20 | 21 | function run_metana_init_default() { 22 | echo "🚀 Running metana init..." 23 | metana init 24 | } 25 | 26 | function validate_migrations_structure() { 27 | echo "🔍 Validating default migrations structure..." 28 | 29 | if [ ! -d "$TEMP_DIR/migrations/scripts" ]; then 30 | echo "❌ migrations/scripts directory not created!" 31 | exit 1 32 | fi 33 | 34 | echo "✅ migrations/scripts present." 35 | } 36 | 37 | function run_metana_create_default_dir() { 38 | echo "🚀 Running metana create initSchema (default dir)..." 39 | 40 | (cd "$TEMP_DIR" && metana create initSchema) 41 | 42 | CREATED_FILE=$(find "$TEMP_DIR/migrations/scripts" -type f -name "*_initSchema.go" || true) 43 | 44 | if [ -z "$CREATED_FILE" ]; then 45 | echo "❌ Migration file for initSchema not created in default migrations dir!" 46 | exit 1 47 | fi 48 | 49 | echo "✅ Migration created successfully in default migrations dir: $CREATED_FILE" 50 | } 51 | 52 | function main() { 53 | check_metana_binary 54 | create_temp_dir 55 | 56 | echo "===============================" 57 | echo "▶️ Testing metana init..." 58 | run_metana_init_default 59 | validate_migrations_structure 60 | 61 | echo "===============================" 62 | echo "▶️ Testing metana create in default dir..." 63 | run_metana_create_default_dir 64 | 65 | echo "===============================" 66 | echo "🎉 INIT + CREATE tests passed successfully!" 67 | } 68 | 69 | main "$@" 70 | -------------------------------------------------------------------------------- /tests/tests_down.sh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g14a/metana/fa95b47596f7d3aaf5c2c278f7cc92b33cc53781/tests/tests_down.sh -------------------------------------------------------------------------------- /tests/tests_init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "🔵 Starting INIT migration tests..." 6 | 7 | function check_metana_binary() { 8 | if ! command -v metana &> /dev/null; then 9 | echo "❌ metana binary not found in PATH. Please install it first with: go install ./..." 10 | exit 1 11 | fi 12 | echo "✅ metana binary found at $(which metana)" 13 | } 14 | 15 | function create_temp_dir() { 16 | TEMP_DIR=$(mktemp -d) 17 | echo "✅ Created temp directory: $TEMP_DIR" 18 | cd $TEMP_DIR 19 | } 20 | 21 | function run_metana_init_default() { 22 | echo "🚀 Running metana init..." 23 | metana init 24 | } 25 | 26 | function validate_default_migrations_structure() { 27 | echo "🔍 Validating default migrations structure..." 28 | 29 | if [ ! -d "$TEMP_DIR/migrations/scripts" ]; then 30 | echo "❌ migrations/scripts directory not created!" 31 | exit 1 32 | fi 33 | 34 | echo "✅ Default migrations/scripts present." 35 | } 36 | 37 | function main() { 38 | check_metana_binary 39 | create_temp_dir 40 | 41 | echo "===============================" 42 | echo "▶️ Testing default init..." 43 | run_metana_init_default 44 | validate_default_migrations_structure 45 | 46 | echo "===============================" 47 | echo "🎉 ALL INIT tests passed successfully!" 48 | } 49 | 50 | main "$@" 51 | -------------------------------------------------------------------------------- /tests/tests_list.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "🔵 Starting LIST migration tests..." 6 | 7 | function check_metana_binary() { 8 | if ! command -v metana &> /dev/null; then 9 | echo "❌ metana binary not found in PATH. Please install it first with: go install ./..." 10 | exit 1 11 | fi 12 | echo "✅ metana binary found at $(which metana)" 13 | } 14 | 15 | function create_temp_dir() { 16 | TEMP_DIR=$(mktemp -d) 17 | echo "✅ Created temp directory: $TEMP_DIR" 18 | } 19 | 20 | function run_metana_init() { 21 | echo "🚀 Running metana init..." 22 | (cd "$TEMP_DIR" && metana init) # 👈 cd into temp dir, init normally 23 | } 24 | 25 | function create_migrations() { 26 | echo "🚀 Creating migration files..." 27 | 28 | (cd "$TEMP_DIR" && metana create initSchema) 29 | sleep 1 30 | (cd "$TEMP_DIR" && metana create initSchema2) 31 | 32 | echo "✅ Created two migration scripts." 33 | } 34 | 35 | function run_metana_list_and_validate_empty_executed() { 36 | echo "📋 Running metana list (before running migrations)..." 37 | 38 | LIST_OUTPUT=$(cd "$TEMP_DIR" && metana list) 39 | 40 | if echo "$LIST_OUTPUT" | grep -q "initSchema" && echo "$LIST_OUTPUT" | grep -q "initSchema2"; then 41 | echo "✅ Migration entries found in list." 42 | else 43 | echo "❌ Migration entries not found in list." 44 | exit 1 45 | fi 46 | 47 | if echo "$LIST_OUTPUT" | grep -q "| |"; then 48 | echo "✅ Executed At is EMPTY before running migrations (expected)." 49 | else 50 | echo "❌ Executed At is NOT empty before running migrations!" 51 | exit 1 52 | fi 53 | } 54 | 55 | function run_metana_up() { 56 | echo "⬆️ Running metana up..." 57 | (cd "$TEMP_DIR" && metana up) 58 | echo "✅ Up migrations executed." 59 | } 60 | 61 | function run_metana_list_and_validate_executed_at() { 62 | echo "📋 Running metana list (after running migrations)..." 63 | 64 | LIST_OUTPUT=$(cd "$TEMP_DIR" && metana list) 65 | 66 | echo "$LIST_OUTPUT" 67 | 68 | if echo "$LIST_OUTPUT" | grep -q "[0-9][0-9]-[0-9][0-9]-[0-9][0-9][0-9][0-9]"; then 69 | echo "✅ Executed At timestamp is present after running migrations." 70 | else 71 | echo "❌ Executed At timestamp NOT present after running migrations!" 72 | exit 1 73 | fi 74 | } 75 | 76 | function main() { 77 | check_metana_binary 78 | create_temp_dir 79 | run_metana_init 80 | create_migrations 81 | run_metana_list_and_validate_empty_executed 82 | run_metana_up 83 | run_metana_list_and_validate_executed_at 84 | 85 | echo "🎉 LIST migration tests passed successfully!" 86 | } 87 | 88 | main "$@" 89 | -------------------------------------------------------------------------------- /tests/tests_up.sh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g14a/metana/fa95b47596f7d3aaf5c2c278f7cc92b33cc53781/tests/tests_up.sh --------------------------------------------------------------------------------