├── .github └── workflows │ ├── release.yml │ └── tests.yml ├── .gitignore ├── .goreleaser.yml ├── LICENSE ├── Makefile ├── README.md ├── aws_utils ├── common.go ├── output.go ├── print.go ├── route53.go ├── route53_test.go ├── types.go ├── urls.go └── urls_test.go ├── cmd └── root.go ├── dnsu └── dig.go ├── docs ├── logo-s.png └── logo-xl.png ├── go.mod ├── go.sum ├── main.go └── sdk ├── output.go └── search.go /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - 'v*.*.*' 6 | - 'v*.*.*-rc*' 7 | jobs: 8 | goreleaser: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@main 13 | - name: Set up Go 14 | uses: actions/setup-go@v2 15 | with: 16 | go-version: 1.16 17 | - name: GoReleaser 18 | uses: goreleaser/goreleaser-action@v2 19 | with: 20 | args: release --rm-dist 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | GORELEASER_GITHUB_TOKEN: ${{ secrets.GORELEASER_GITHUB_TOKEN }} 24 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: 1.16 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | 24 | - name: Test 25 | run: go test -v ./... -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .vscode/* 3 | !.vscode/settings.json 4 | !.vscode/tasks.json 5 | !.vscode/launch.json 6 | !.vscode/extensions.json 7 | *.code-workspace 8 | 9 | # Local History for Visual Studio Code 10 | .history/ 11 | 12 | # Binaries for programs and plugins 13 | *.exe 14 | *.exe~ 15 | *.dll 16 | *.so 17 | *.dylib 18 | 19 | # Test binary, built with `go test -c` 20 | *.test 21 | 22 | # Output of the go coverage tool, specifically when used with LiteIDE 23 | *.out 24 | 25 | # Dependency directories (remove the comment below to include it) 26 | # vendor/ 27 | 28 | out -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # .goreleaser.yml 2 | builds: 3 | - 4 | binary: r53 5 | brews: 6 | - 7 | name: r53 8 | # GitHub/GitLab repository to push the formula to 9 | # Gitea is not supported yet, but the support coming 10 | tap: 11 | owner: isan-rivkin 12 | name: homebrew-toolbox 13 | token: "{{ .Env.GORELEASER_GITHUB_TOKEN }}" 14 | 15 | # Template for the url which is determined by the given Token (github or gitlab) 16 | # Default for github is "https://github.com///releases/download/{{ .Tag }}/{{ .ArtifactName }}" 17 | # url_template: "http://github.mycompany.com/foo/bar/releases/{{ .Tag }}/{{ .ArtifactName }}" 18 | 19 | # Allows you to set a custom download strategy. Note that you'll need 20 | # to implement the strategy and add it to your tap repository. 21 | # Example: https://docs.brew.sh/Formula-Cookbook#specifying-the-download-strategy-explicitly 22 | # Default is empty. 23 | #download_strategy: CurlDownloadStrategy. 24 | 25 | # Allows you to add a custom require_relative at the top of the formula template 26 | # Default is empty 27 | #custom_require: custom_download_strategy 28 | 29 | # Git author used to commit to the repository. 30 | # Defaults are shown. 31 | # commit_author: 32 | # name: goreleaserbot 33 | # email: goreleaser@carlosbecker.com 34 | 35 | # Folder inside the repository to put the formula. 36 | # Default is the root folder. 37 | #folder: Formula 38 | 39 | # Caveats for the user of your binary. 40 | # Default is empty. 41 | #caveats: "How to use this binary" 42 | 43 | # Your app's homepage. 44 | # Default is empty. 45 | homepage: "https://github.com/Isan-Rivkin/route53-cli" 46 | 47 | # Your app's description. 48 | # Default is empty. 49 | description: "Cli tool for Route53 to quickly query values of records" 50 | 51 | # Setting this will prevent goreleaser to actually try to commit the updated 52 | # formula - instead, the formula file will be stored on the dist folder only, 53 | # leaving the responsibility of publishing it to the user. 54 | # If set to auto, the release will not be uploaded to the homebrew tap 55 | # in case there is an indicator for prerelease in the tag e.g. v1.0.0-rc1 56 | # Default is false. 57 | #skip_upload: true 58 | 59 | # Custom block for brew. 60 | # Can be used to specify alternate downloads for devel or head releases. 61 | # Default is empty. 62 | # custom_block: | 63 | # head "https://github.com/some/package.git" 64 | # ... 65 | 66 | # Packages your package depends on. 67 | dependencies: 68 | - name: git 69 | # - name: zsh 70 | # type: optional 71 | 72 | # Packages that conflict with your package. 73 | # conflicts: 74 | # - svn 75 | # - bash 76 | 77 | # Specify for packages that run as a service. 78 | # Default is empty. 79 | # plist: | 80 | # 81 | # ... 82 | 83 | # So you can `brew test` your formula. 84 | # Default is empty. 85 | # test: | 86 | # system "#{bin}/program --version" 87 | # ... 88 | 89 | # Custom install script for brew. 90 | # Default is 'bin.install "program"'. 91 | # install: | 92 | # bin.install "program" 93 | # ... -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | RUN_CMD=go run main.go 2 | GOCMD=go 3 | GOFMT=$(GOCMD)fmt 4 | BIN_DIR='/usr/local/bin/r53' 5 | install: 6 | go build main.go 7 | mv main r53 8 | mv r53 $(BIN_DIR) 9 | echo "Installed r53 to" $(BIN_DIR) 10 | run: 11 | ${RUN_CMD} 12 | debug: 13 | ${RUN_CMD} -debug 14 | test: 15 | go test -count=1 ./... 16 | test-verbose: 17 | go test -v -count=1 ./... 18 | 19 | fmt: ## Validate go format 20 | @echo checking gofmt... 21 | @res=$$($(GOFMT) -d -e -s $$(find . -type d \( -path ./src/vendor \) -prune -o -name '*.go' -print)); \ 22 | if [ -n "$${res}" ]; then \ 23 | echo checking gofmt fail... ; \ 24 | echo "$${res}"; \ 25 | exit 1; \ 26 | else \ 27 | echo Your code formating is according gofmt standards; \ 28 | fi 29 | lint: 30 | golangci-lint run 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Route53 Query 2 | 3 | # PLEASE USE SURF (INCLUDES R53) CLI FOR A MUCH RICHER EXPERIENCE: 4 | 5 | Visit [surf cli repo](https://github.com/Isan-Rivkin/surf). 6 | 7 | 8 | ![image info](./docs/logo-s.png) 9 | 10 | Move fast with custom CLI for Route53. 11 | 12 | Get info about your Route53 records from the Command line - quickly! 13 | 14 | * Direct link to resource on AWS Console. 15 | * Automatic verification of Nameservers against the real world. 16 | * Recursive search for all results via `-R` flag. 17 | * Supports any `~/.aws/credentials` profile. 18 | * Works directly against AWS API. 19 | 20 | 21 | ## The Problem 22 | 23 | Ever wondered "what records are behind this R53 record I have?" 24 | 25 | i.e Where does `app.foo.goo.website.com` points to in R53? 26 | 27 | ## Without `route53-cli`: 28 | 29 | Go to browser -> AWS console -> login -> Route53 -> find the hosted zone -> find the record 30 | 31 | **Even if you find the record in the AWS console you can't be sure this is the source of truth since the record could be defined in multiple route 53 hosted zones of the organization. The only way to verify is by comparing the Nameservers in the real world.** 32 | 33 | ## The solution 34 | 35 | - The CLI will use the default AWS login methods and query the Route53 API in a smart way to find the record. 36 | - By default the nameservers will be verified against the real world via Dig implementation (turn off via `--skip-ns`). 37 | - Search recursivly and expand all the records until the "leaf". 38 | - Direct Web URL to the resource from the terminal. 39 | - SDK support for programatic access and JSON output. 40 | 41 | ## With `route53-cli`: 42 | 43 | **Input** 44 | 45 | ```bash 46 | r53 -r 'app.foo.goo.website.com' 47 | ``` 48 | 49 | **Output** 50 | 51 | ```bash 52 | 53 | ┌─────────────────┬────────────────────────────┬───────────────┬─────────┐ 54 | │ HOSTED ZONE │ ID │ TOTAL RECORDS │ PRIVATE │ 55 | ├─────────────────┼────────────────────────────┼───────────────┼─────────┤ 56 | │ website.com. │ /hostedzone/ABFDCEWQSFDSFD │ 127 │ false │ 57 | └─────────────────┴────────────────────────────┴───────────────┴─────────┘ 58 | 59 | +---+-----------------------|------------------------|--------------------------------------|---------+ 60 | | # | Record | Target | Console Link | TYPE | 61 | +---+-----------------------|------------------------|--------------------------------------|---------+ 62 | | 1 | *.foo.goo.website.com | r-re1.website.com. | | A | 63 | +---+-----------------------|------------------------|--------------------------------------|---------+ 64 | | 2 | *.foo.goo.website.com | r-re2.website.com. | | A | 65 | +---+-----------------------|------------------------|--------------------------------------|---------+ 66 | | 3 | r-re1.website.com. | lb1.elb.amazonaws.com | https://console.aws.amazon.com/alb-1 | A | 67 | +---+-----------------------|------------------------|--------------------------------------|---------+ 68 | | 4 | r-re2.website.com. | lb2.elb.amazonaws.com | https://console.aws.amazon.com/alb-2 | A | 69 | +---+-----------------------|------------------------|------------------------------------------------+ 70 | 71 | ``` 72 | 73 | ## Programatic Access 74 | 75 | Use the output with another program: 76 | 77 | ```bash 78 | r53 -r 'my.domain.com' --output-json --output-file output.json 79 | ``` 80 | 81 | Use the code in SDK mode (another `go` project): 82 | 83 | ```go 84 | package main 85 | 86 | import ( 87 | "fmt" 88 | 89 | "github.com/isan-rivkin/route53-cli/sdk" 90 | ) 91 | 92 | func main() { 93 | input, _ := sdk.NewInput("my.company.com", "my-aws-conf-profile", false, false, false, true, 3) 94 | result, _ := sdk.SearchR53(input) 95 | jsonOut, _ := sdk.ToJSONOutput(result, true) 96 | fmt.Println(string(jsonOut)) 97 | } 98 | 99 | ``` 100 | 101 | # Install 102 | 103 | ### Brew 104 | 105 | MacOS (and ubuntu supported) installation via Brew: 106 | 107 | ```bash 108 | brew tap isan-rivkin/toolbox 109 | brew install r53 110 | ``` 111 | 112 | ### Download Binary 113 | 114 | 1. [from releases](https://github.com/Isan-Rivkin/route53-cli/releases) 115 | 116 | 2. Move the binary to global dir and change name to `r53`: 117 | 118 | ```bash 119 | cd 120 | mv r53 /usr/local/bin 121 | ``` 122 | 123 | ### Install from Source 124 | 125 | ```bash 126 | git clone 127 | cd route53-cli 128 | make install BIN_DIR='/path/to/target/bin/dir' 129 | ``` 130 | 131 | ### Version Check 132 | 133 | The CLI will query anonymously a remote version server to check if the current version of the CLI is updated. 134 | If the current client version indeed outdated the server will return instructions for update. 135 | 136 | The server will add the request to a hit counter stored internaly for usage metrics. 137 | 138 | **None of the user query are passed to the server, only OS type and version.** 139 | 140 | **The route53 querys themselves are done directly via the AWS Api.** 141 | 142 | This behaviour is on by default and can be optouted out via setting the envrionment variable `R53_VERSION_CHECK=false`. 143 | 144 | ### How it works 145 | 146 | Example pseudocode: 147 | 148 | ```python 149 | # i.e https://example.com/p/a?ok=11¬=23 150 | # into example.com 151 | record = '*...' 152 | record = strip_non_domain(record) 153 | record = split(record) 154 | 155 | hasWildCard = record[0] == '*' 156 | len == record.length 157 | # a 158 | if len == 1 and not hasWildCard: 159 | lookup(1) 160 | # *.a 161 | if len == 2 and hasWildCard: 162 | lookup(1) 163 | # a.b 164 | if len == 2 and not hasWildCard: 165 | lookup(2) 166 | # *.a.b 167 | if len == 3 and hasWildCard: 168 | lookup(2) 169 | # a.b.c 170 | if len == 3 and hasWildCard: 171 | if not lookup(2) 172 | lookup(3) 173 | # *.a.b.c.d.e 174 | if len > 3: 175 | for chunksNum in (2, len): 176 | if (chunksNum == len and hasWildCard): 177 | return None 178 | if res = lookup(chunksNum) 179 | return res 180 | 181 | def lookup(dnsChunk): 182 | zoneIds = getZoneIds(dnsChunk) 183 | for zoneId in zoneIds: 184 | if zoneId name == dnsChunk: 185 | aliasTargets = getAliasTargets(record, zoneId) 186 | return aliasTargetes 187 | ``` 188 | -------------------------------------------------------------------------------- /aws_utils/common.go: -------------------------------------------------------------------------------- 1 | package aws_utils 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go/aws/session" 5 | ) 6 | 7 | func GetEnvSession(profile string) *session.Session { 8 | opts := session.Options{ 9 | SharedConfigState: session.SharedConfigEnable, 10 | } 11 | 12 | if profile != "" { 13 | opts.Profile = profile 14 | } 15 | 16 | sess := session.Must(session.NewSessionWithOptions(opts)) 17 | return sess 18 | } 19 | -------------------------------------------------------------------------------- /aws_utils/output.go: -------------------------------------------------------------------------------- 1 | package aws_utils 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/aws/aws-sdk-go/aws" 8 | "github.com/aws/aws-sdk-go/service/route53" 9 | ) 10 | 11 | type RoutingPolicy string 12 | 13 | const ( 14 | GeoLocationRP RoutingPolicy = "Geolocation" 15 | WeightedRP RoutingPolicy = "Weighted" 16 | FailoverRP RoutingPolicy = "Failover" 17 | OtherRP RoutingPolicy = "-" 18 | ) 19 | 20 | type RecordAccessor interface { 21 | GetHostedZoneID() string 22 | // "Record", "Type", "TTL", "Country", "Alias", "Resources" 23 | GetTTL() (int64, bool) 24 | // The record value in r53 25 | GetRecord() (string, bool) 26 | GetRegion() (string, bool) 27 | GetRecordType() (string, bool) 28 | // for GEO dns configurations 29 | GetCountryCode() (string, bool) 30 | // alias value is for A records, typical to ELB 31 | GetAliasValue() (string, bool) 32 | // resources are non alias values, typical for TXT, NS, MX, SOA records 33 | GetResources() ([]string, bool) 34 | // web URL to see the target Alias if exist 35 | GetWebURL() (string, bool) 36 | // DNS record feature types 37 | // Record has Routing policy and differentiator (i.e weighted-50 or geo-us, or failover-prinmary) 38 | // Failover, Simple, Geolocation, etc https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-policy.html 39 | GetRoutingPolicy() (RoutingPolicy, bool) 40 | GetRoutingPolicyDifferentiator() (string, bool) 41 | IsGeoDNS() bool 42 | IsWeightedDNS() bool 43 | IsFailoverRoutingPolicy() bool 44 | GetFailoverDifferentiator() (string, bool) 45 | } 46 | 47 | type RecordResultAccessor struct { 48 | raw *route53.ResourceRecordSet 49 | // from record set 50 | hostedZone *route53.HostedZone 51 | region string 52 | } 53 | 54 | func FlatRecordResultToAccessors(results []*GetRecordAliasesResult) []RecordAccessor { 55 | var accessors []RecordAccessor 56 | for _, r := range results { 57 | accessors = append(accessors, r.GetRecordsAccessors()...) 58 | } 59 | return accessors 60 | } 61 | 62 | func (r *GetRecordAliasesResult) GetRecordsAccessors() []RecordAccessor { 63 | var accessors []RecordAccessor 64 | for _, recSet := range r.Records { 65 | ra := NewRecordResultAccessor(recSet, r.HostedZone, r.Region) 66 | accessors = append(accessors, ra) 67 | } 68 | return accessors 69 | } 70 | 71 | func NewRecordResultAccessor(raw *route53.ResourceRecordSet, hostedZone *route53.HostedZone, region string) RecordAccessor { 72 | return &RecordResultAccessor{ 73 | raw: raw, 74 | hostedZone: hostedZone, 75 | region: region, 76 | } 77 | } 78 | 79 | func (a *RecordResultAccessor) GetHostedZoneID() string { 80 | return aws.StringValue(a.hostedZone.Id) 81 | } 82 | 83 | func (a *RecordResultAccessor) IsGeoDNS() bool { 84 | return a.raw.GeoLocation != nil 85 | } 86 | 87 | func (a *RecordResultAccessor) IsWeightedDNS() bool { 88 | return a.raw.Weight != nil 89 | } 90 | 91 | // RoutingPolicy Failover records 92 | func (a *RecordResultAccessor) IsFailoverRoutingPolicy() bool { 93 | return aws.StringValue(a.raw.Failover) != "" 94 | } 95 | 96 | // RoutingPolicy Failover records: return PRIMARY / SECONDARY 97 | func (a *RecordResultAccessor) GetFailoverDifferentiator() (string, bool) { 98 | return aws.StringValue(a.raw.Failover), a.IsFailoverRoutingPolicy() 99 | } 100 | 101 | func (a *RecordResultAccessor) GetRoutingPolicyDifferentiator() (string, bool) { 102 | 103 | if a.IsFailoverRoutingPolicy() { 104 | return a.GetFailoverDifferentiator() 105 | } 106 | 107 | if a.IsGeoDNS() { 108 | return aws.StringValue(a.raw.GeoLocation.SubdivisionCode), true 109 | } 110 | 111 | if a.IsWeightedDNS() { 112 | w := aws.Int64Value(a.raw.Weight) 113 | return fmt.Sprintf("%v", w), true 114 | } 115 | 116 | return "", true 117 | } 118 | 119 | func (a *RecordResultAccessor) GetRoutingPolicy() (RoutingPolicy, bool) { 120 | 121 | // differentiator contains primary/secondary etc values 122 | if a.IsFailoverRoutingPolicy() { 123 | return FailoverRP, true 124 | } 125 | // differentiator contains country 126 | if a.IsGeoDNS() { 127 | return GeoLocationRP, true 128 | } 129 | // differentiator contains weight number 130 | if a.IsWeightedDNS() { 131 | return WeightedRP, true 132 | } 133 | return OtherRP, false 134 | } 135 | 136 | func (a *RecordResultAccessor) GetTTL() (int64, bool) { 137 | return aws.Int64Value(a.raw.TTL), a.raw.TTL != nil 138 | } 139 | 140 | func (a *RecordResultAccessor) GetRegion() (string, bool) { 141 | var r string 142 | 143 | if a.raw.Region == nil { 144 | r = a.region 145 | } else { 146 | r = aws.StringValue(a.raw.Region) 147 | } 148 | 149 | return r, r != "" 150 | } 151 | 152 | func (a *RecordResultAccessor) GetRecord() (string, bool) { 153 | recordStr := aws.StringValue(a.raw.Name) 154 | if strings.HasPrefix(recordStr, WildCard) { 155 | recordStr = strings.Replace(recordStr, WildCard, "*", 1) 156 | } 157 | return recordStr, recordStr != "" 158 | } 159 | 160 | func (a *RecordResultAccessor) GetRecordType() (string, bool) { 161 | return aws.StringValue(a.raw.Type), a.raw.Type != nil 162 | } 163 | 164 | func (a *RecordResultAccessor) GetCountryCode() (string, bool) { 165 | countryCode := "" 166 | if a.raw.GeoLocation != nil { 167 | countryCode = aws.StringValue(a.raw.GeoLocation.SubdivisionCode) 168 | } 169 | 170 | return countryCode, countryCode != "" 171 | } 172 | 173 | func (a *RecordResultAccessor) GetAliasValue() (string, bool) { 174 | dnsName := "" 175 | if a.raw.AliasTarget != nil { 176 | dnsName = aws.StringValue(a.raw.AliasTarget.DNSName) 177 | } 178 | return dnsName, dnsName != "" 179 | } 180 | func (a *RecordResultAccessor) GetResources() ([]string, bool) { 181 | var resources []string 182 | for _, r := range a.raw.ResourceRecords { 183 | if r != nil { 184 | resources = append(resources, aws.StringValue(r.Value)) 185 | } 186 | } 187 | return resources, len(resources) > 0 188 | } 189 | 190 | func (a *RecordResultAccessor) GetWebURL() (string, bool) { 191 | r, _ := a.GetRegion() 192 | a.raw.Region = aws.String(r) 193 | url, err := GenerateWebURL(a.raw) 194 | return url, err == nil 195 | } 196 | -------------------------------------------------------------------------------- /aws_utils/print.go: -------------------------------------------------------------------------------- 1 | package aws_utils 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/aws/aws-sdk-go/aws" 9 | "github.com/jedib0t/go-pretty/v6/table" 10 | ) 11 | 12 | type PrintOptions struct { 13 | WebURL bool 14 | } 15 | 16 | func (r *GetRecordAliasesResult) PrintTable(opts *PrintOptions) { 17 | r.printHostedzoneTable(opts) 18 | r.printRecordsTable(opts) 19 | } 20 | 21 | func (r *GetRecordAliasesResult) printRecordsTable(opts *PrintOptions) { 22 | rowConfigAutoMerge := table.RowConfig{AutoMerge: true} 23 | t := table.NewWriter() 24 | t.AppendHeader(table.Row{"Record", "Type", "TTL", "Country", "Alias", "Resources"}, rowConfigAutoMerge) 25 | accessors := r.GetRecordsAccessors() 26 | 27 | for _, rec := range accessors { 28 | countryCode, _ := rec.GetCountryCode() 29 | dnsName, _ := rec.GetAliasValue() 30 | ttl, _ := rec.GetTTL() 31 | resources, _ := rec.GetResources() 32 | resourcesRow := strings.Join(resources, "\n") 33 | recordStr, _ := rec.GetRecord() 34 | if opts != nil && opts.WebURL { 35 | url, _ := rec.GetWebURL() 36 | dnsName += fmt.Sprintf("\n\n%s\n", url) 37 | } 38 | recType, _ := rec.GetRecordType() 39 | t.AppendRow(table.Row{recordStr, recType, ttl, countryCode, dnsName, resourcesRow}, rowConfigAutoMerge) 40 | t.AppendSeparator() 41 | } 42 | 43 | t.SetAutoIndex(true) 44 | t.SetColumnConfigs([]table.ColumnConfig{ 45 | {Number: 1, AutoMerge: true}, 46 | {Number: 2, AutoMerge: false}, 47 | {Number: 3, AutoMerge: false}, 48 | {Number: 4, AutoMerge: false}, 49 | {Number: 5, AutoMerge: false}, 50 | }) 51 | t.SetStyle(table.StyleLight) 52 | t.Style().Options.SeparateRows = true 53 | t.SetOutputMirror(os.Stdout) 54 | t.Render() 55 | } 56 | 57 | func (r *GetRecordAliasesResult) printHostedzoneTable(opts *PrintOptions) { 58 | rowConfigAutoMerge := table.RowConfig{AutoMerge: true} 59 | 60 | t := table.NewWriter() 61 | 62 | hzIdDisplay := aws.StringValue(r.HostedZone.Id) 63 | r53Url := GenerateRoute53HostedZoneWebURL(hzIdDisplay) 64 | if r53Url != "" { 65 | hzIdDisplay = fmt.Sprintf("%s\n\n%s\n", hzIdDisplay, r53Url) 66 | } 67 | t.AppendHeader(table.Row{"Hosted Zone", "Id", "Total records", "Private"}, rowConfigAutoMerge) 68 | t.AppendRow(table.Row{*r.HostedZone.Name, hzIdDisplay, *r.HostedZone.ResourceRecordSetCount, *r.HostedZone.Config.PrivateZone}, rowConfigAutoMerge) 69 | t.SetColumnConfigs([]table.ColumnConfig{ 70 | {Number: 1, AutoMerge: true}, 71 | {Number: 2, AutoMerge: true}, 72 | {Number: 3, AutoMerge: true}, 73 | {Number: 4, AutoMerge: true}, 74 | }) 75 | t.SetStyle(table.StyleLight) 76 | t.Style().Options.SeparateRows = true 77 | t.SetOutputMirror(os.Stdout) 78 | t.Render() 79 | } 80 | -------------------------------------------------------------------------------- /aws_utils/route53.go: -------------------------------------------------------------------------------- 1 | package aws_utils 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/url" 7 | "strings" 8 | 9 | dnsu "github.com/isan-rivkin/route53-cli/dnsu" 10 | 11 | "github.com/aws/aws-sdk-go/aws" 12 | route53 "github.com/aws/aws-sdk-go/service/route53" 13 | log "github.com/sirupsen/logrus" 14 | ) 15 | 16 | func NewRoute53Api(profile string) Route53Api { 17 | return &Route53Manager{ 18 | session: GetEnvSession(profile), 19 | nameservers: map[string][]string{}, 20 | } 21 | } 22 | func NewRecordName(rawQuery string) (RecordStream, error) { 23 | splitted, err := StripRecord(rawQuery) 24 | if err != nil { 25 | return nil, err 26 | } 27 | log.WithField("parsedRecord", splitted).Debug("record value after strip") 28 | if splitted[len(splitted)-1] == "" { 29 | splitted = splitted[:len(splitted)-1] 30 | } 31 | return &RecordName{ 32 | rawURL: rawQuery, 33 | parsedURL: strings.Join(splitted, "."), 34 | splittedURL: splitted, 35 | hasWildCard: splitted[0] == WildCard, 36 | }, nil 37 | } 38 | 39 | func (r *RecordName) HasWildCard() bool { 40 | return r.hasWildCard 41 | } 42 | func (r *RecordName) GetParsedURL() string { 43 | return r.parsedURL 44 | } 45 | func (r *RecordName) GetWithWildCard() (string, error) { 46 | if len(r.splittedURL) == 0 { 47 | return "", errors.New("InvalidDNSWildCardTest") 48 | } 49 | withWc := strings.Replace(r.parsedURL, r.splittedURL[0], WildCard, 1) 50 | return withWc, nil 51 | } 52 | func (r *RecordName) IsEqual(other string) bool { 53 | 54 | if other == r.parsedURL+"." || other == r.parsedURL { 55 | return true 56 | } 57 | if !r.hasWildCard && strings.HasPrefix(other, WildCard) { 58 | 59 | otherWithNoWc := strings.Replace(other, WildCard, r.splittedURL[0], 1) 60 | 61 | if otherWithNoWc == r.parsedURL+"." || otherWithNoWc == r.parsedURL { 62 | return true 63 | } 64 | } 65 | 66 | return false 67 | } 68 | func (r *RecordName) GetAllOptionsForZoneName() ([]string, error) { 69 | opts := []string{} 70 | size := len(r.splittedURL) 71 | 72 | if size == 1 && r.hasWildCard { 73 | return nil, errors.New("InvalidRecordWildCard") 74 | } 75 | // a -> [a] 76 | if size == 1 { 77 | return append(opts, r.parsedURL), nil 78 | } 79 | // *.a -> [a] 80 | if size == 2 && r.hasWildCard { 81 | return []string{r.splittedURL[size-1]}, nil 82 | } 83 | // a.b -> [a.b, b] 84 | if size == 2 { 85 | opts = append(opts, r.parsedURL, r.splittedURL[1]) 86 | return opts, nil 87 | } 88 | // *.a.b -> [a.b, b] 89 | if size == 3 && r.hasWildCard { 90 | { 91 | } 92 | opts = append(opts, strings.Join(r.splittedURL[1:size], ".")) 93 | opts = append(opts, r.splittedURL[size-1]) 94 | return opts, nil 95 | } 96 | // a.b.c -> [a.b.c, b.c, c ] 97 | if size == 3 { 98 | opts = append(opts, r.parsedURL) 99 | opts = append(opts, strings.Join(r.splittedURL[1:size], ".")) 100 | opts = append(opts, r.splittedURL[size-1]) 101 | return opts, nil 102 | } 103 | // *.a.b.c.d -> [a.b.c.d, ... , d] 104 | // a.b.c.d.e -> [a.b.c.d.e, ..., e] 105 | for idx, _ := range r.splittedURL { 106 | if idx == 0 && r.hasWildCard { 107 | continue 108 | } 109 | testRecord := strings.Join(r.splittedURL[idx:size], ".") 110 | log.WithField("record", testRecord).Debug("potential hosted zone name") 111 | opts = append(opts, testRecord) 112 | } 113 | return opts, nil 114 | } 115 | 116 | func (r53m *Route53Manager) GetRegion() string { 117 | return *r53m.session.Config.Region 118 | } 119 | 120 | func (r53m *Route53Manager) client() *route53.Route53 { 121 | if r53m.r53client == nil { 122 | r53m.r53client = route53.New(r53m.session) 123 | return r53m.r53client 124 | } 125 | return r53m.r53client 126 | } 127 | 128 | // works only for public zones 129 | func (r53 *Route53Manager) TestDNSAnswer(hostedZoneId, recordName, recordType string) (*route53.TestDNSAnswerOutput, error) { 130 | zoneId := strings.TrimLeft(hostedZoneId, "/hostedzone/") 131 | c := r53.client() 132 | input := &route53.TestDNSAnswerInput{ 133 | RecordType: aws.String(recordType), 134 | RecordName: aws.String(recordName), 135 | HostedZoneId: aws.String(zoneId), 136 | } 137 | output, err := c.TestDNSAnswer(input) 138 | if err != nil { 139 | log.WithError(err).Error("failed checking dns anser for record") 140 | } 141 | return output, nil 142 | } 143 | 144 | // stripRecord 145 | // i.e https://example.com/p/a?ok=11¬=23 146 | // into example.com 147 | func StripRecord(fullRecord string) ([]string, error) { 148 | if !strings.HasPrefix(fullRecord, "http://") && !strings.HasPrefix(fullRecord, "https://") { 149 | fullRecord = fmt.Sprintf("http://%s", fullRecord) 150 | } 151 | u, err := url.Parse(fullRecord) 152 | if err != nil { 153 | return nil, err 154 | } 155 | return strings.Split(u.Hostname(), "."), nil 156 | } 157 | 158 | // gets hosted zone nameservers 159 | // gets record name nameservers (nslookup) 160 | // compares them 161 | func (r53m *Route53Manager) isNSMatchRecord(hosedZone *route53.HostedZone, recordName string) (bool, error) { 162 | hns, err := r53m.GetHZNameservers(*hosedZone.Id) 163 | 164 | log.WithField("hostedZoneId", *hosedZone.Id).Debug("evaluating record match for nameservers") 165 | 166 | if err != nil { 167 | log.WithError(err).Error("failed getting hosted zone nameservers, abborting. to skip verification use flag --ns-skip") 168 | return false, err 169 | } 170 | 171 | rns, err := r53m.GetNameservers(recordName) 172 | 173 | if err != nil { 174 | log.WithError(err).Error("failed getting domain address nameservers, abborting. to skip verification use flag --ns-skip") 175 | return false, err 176 | } 177 | 178 | if !r53m.IsNSMatch(hns, rns) { 179 | log.Info("record found in hosted zone but nameserver dont match, continuing search, to skip verification use flag --ns-skip") 180 | return false, errors.New("ErrNoNSMatch") 181 | } 182 | 183 | return true, nil 184 | } 185 | 186 | func (r53m *Route53Manager) getRecordsRecursive(maxDepth int, recordName string, skipNSVerification bool, checkedRecord map[string]bool) ([]*GetRecordAliasesResult, error) { 187 | 188 | lg := log.WithFields(log.Fields{ 189 | "currentDepth": maxDepth, 190 | "rootRecordName": recordName, 191 | }) 192 | 193 | var allResults []*GetRecordAliasesResult 194 | 195 | if maxDepth <= 0 { 196 | return nil, nil 197 | } 198 | 199 | if checkedRecord == nil { 200 | checkedRecord = map[string]bool{} 201 | } 202 | 203 | _, searched := checkedRecord[recordName] 204 | 205 | if searched { 206 | return nil, nil 207 | } 208 | 209 | res, err := r53m.GetRecordSetAliases(recordName, skipNSVerification) 210 | 211 | checkedRecord[recordName] = true 212 | 213 | if err != nil { 214 | return nil, err 215 | } 216 | 217 | allResults = append(allResults, res) 218 | 219 | for _, record := range res.Records { 220 | 221 | l := lg.WithField("calledRecord", *record.Name) 222 | 223 | l.Info("querying record set") 224 | 225 | if record.AliasTarget == nil || record.AliasTarget.DNSName == nil { 226 | continue 227 | } 228 | 229 | a := *record.AliasTarget.DNSName 230 | a = strings.TrimSuffix(a, ".") 231 | 232 | result, err := r53m.getRecordsRecursive(maxDepth-1, a, skipNSVerification, checkedRecord) 233 | 234 | if err != nil { 235 | l.Debugf("stopping recurse record set on alias no results %s", err.Error()) 236 | continue 237 | } 238 | 239 | // stop condition 240 | if result == nil { 241 | break 242 | } 243 | 244 | allResults = append(allResults, result...) 245 | 246 | } 247 | 248 | return allResults, nil 249 | } 250 | 251 | func (r53m *Route53Manager) mergeRecordAliasResultsHZs(results []*GetRecordAliasesResult) []*GetRecordAliasesResult { 252 | seenHostedZone := map[string]bool{} 253 | hzIdx := map[string]int{} 254 | 255 | var merged []*GetRecordAliasesResult 256 | 257 | for idx, r := range results { 258 | hzId := *r.HostedZone.Id 259 | if _, added := seenHostedZone[hzId]; !added { 260 | seenHostedZone[hzId] = true 261 | merged = append(merged, r) 262 | hzIdx[hzId] = idx 263 | } else { 264 | merged[hzIdx[hzId]].Records = append(merged[hzIdx[hzId]].Records, r.Records...) 265 | } 266 | } 267 | return merged 268 | } 269 | func (r53m *Route53Manager) GetRecordSetAliasesRecursive(maxDepth int, recordName string, skipNSVerification bool, checkedRecord map[string]bool) ([]*GetRecordAliasesResult, error) { 270 | 271 | // get results recursivly 272 | results, err := r53m.getRecordsRecursive(maxDepth, recordName, skipNSVerification, checkedRecord) 273 | 274 | if err != nil { 275 | return nil, err 276 | } 277 | 278 | // merge the result sets if the hosted zone is the same s 279 | merged := r53m.mergeRecordAliasResultsHZs(results) 280 | 281 | return merged, nil 282 | 283 | } 284 | 285 | func (r53m *Route53Manager) GetRecordSetAliases(recordName string, skipNSVerification bool) (*GetRecordAliasesResult, error) { 286 | recordStream, err := NewRecordName(recordName) 287 | if err != nil { 288 | panic(err) 289 | } 290 | recordName = recordStream.GetParsedURL() 291 | optionalHostedZone, err := recordStream.GetAllOptionsForZoneName() 292 | if err != nil { 293 | panic(err) 294 | } 295 | log.WithField("possible_hosted_zones", optionalHostedZone).Debug("checking the following hosted zones for the record") 296 | 297 | for _, hzName := range optionalHostedZone { 298 | hosedZone, recordSets, err := r53m.LookupRecord(hzName, recordName, recordStream) 299 | // if record not found in current hosted zone 300 | if err != nil || recordSets == nil { 301 | log.WithField("hostedZoneTested", hzName).Debug("records not found in zone, checking next") 302 | continue 303 | } 304 | // if record set found but have different nameservers uppon nslookup 305 | if !skipNSVerification { 306 | if match, err := r53m.isNSMatchRecord(hosedZone, recordName); err != nil || !match { 307 | continue 308 | } 309 | } 310 | return &GetRecordAliasesResult{Region: r53m.GetRegion(), Records: recordSets, HostedZone: hosedZone, Stream: recordStream}, nil 311 | } 312 | return nil, errors.New("NoRecordMatchFound") 313 | } 314 | 315 | func (r53m *Route53Manager) getRecordsAliasesAndFilter(recordName, zoneId string, recordStream RecordStream) ([]*route53.ResourceRecordSet, error) { 316 | result := []*route53.ResourceRecordSet{} 317 | recordSets, err := r53m.getRecordAliases(recordName, zoneId) 318 | if err != nil { 319 | return nil, err 320 | } 321 | // check for specific records of the query 322 | for _, rs := range recordSets { 323 | log.WithField("dns", *rs.Name).Debug("inspectig record") 324 | if recordStream.IsEqual(*rs.Name) { 325 | result = append(result, rs) 326 | } 327 | } 328 | return result, nil 329 | } 330 | 331 | // LookupRecord query for potential hosted zones 332 | func (r53m *Route53Manager) LookupRecord(hzName, record string, recordName RecordStream) (*route53.HostedZone, []*route53.ResourceRecordSet, error) { 333 | result := []*route53.ResourceRecordSet{} 334 | // get zones 335 | optionalHostedZones, err := r53m.GetHostedZonesFromDns(hzName) 336 | if err != nil || len(optionalHostedZones) == 0 { 337 | return nil, nil, err 338 | } 339 | 340 | // check match in hosted zones 341 | for _, hz := range optionalHostedZones { 342 | if *hz.Name == hzName+"." { 343 | log.WithField("name", *hz.Name).Debug("hosted zone found!") 344 | zoneId := hz.Id 345 | // get records inside hosted zone 346 | 347 | filteredRecords, err := r53m.getRecordsAliasesAndFilter(record, *zoneId, recordName) 348 | if err != nil { 349 | return nil, nil, err 350 | } 351 | if len(filteredRecords) == 0 && !recordName.HasWildCard() { 352 | recWithWc, err := recordName.GetWithWildCard() 353 | if err != nil { 354 | return nil, nil, err 355 | } 356 | filteredRecords, err = r53m.getRecordsAliasesAndFilter(recWithWc, *zoneId, recordName) 357 | if err != nil { 358 | return nil, nil, err 359 | } 360 | } 361 | result = append(result, filteredRecords...) 362 | return hz, result, nil 363 | } 364 | } 365 | return nil, nil, errors.New("LookupNotFoundErr") 366 | } 367 | 368 | // aws route53 list-resource-record-sets --hosted-zone-id --query "ResourceRecordSets[?Type == 'NS']" 369 | // returns records based on type 370 | func (r53m *Route53Manager) getHostedZoneRecords(zoneId, zoneName, recordType string) ([]*route53.ResourceRecordSet, error) { 371 | 372 | var res []*route53.ResourceRecordSet 373 | 374 | c := r53m.client() 375 | 376 | input := &route53.ListResourceRecordSetsInput{ 377 | HostedZoneId: aws.String(zoneId), // Required 378 | StartRecordName: aws.String(zoneName), 379 | StartRecordType: aws.String(recordType), 380 | } 381 | 382 | output, err := c.ListResourceRecordSets(input) 383 | 384 | if err != nil { 385 | return nil, err 386 | } 387 | 388 | for _, r := range output.ResourceRecordSets { 389 | if r.Type != nil && *r.Type == recordType { 390 | res = append(res, r) 391 | } 392 | } 393 | 394 | return res, nil 395 | } 396 | 397 | // getRecordAliases will return all record within a hosted zone that match the record name and also the rest 398 | func (r53m *Route53Manager) getRecordAliases(recordName, zoneId string) ([]*route53.ResourceRecordSet, error) { 399 | log.WithField("recordName", recordName).Debug("listing resource sets in aws r53") 400 | 401 | c := r53m.client() 402 | input := &route53.ListResourceRecordSetsInput{ 403 | HostedZoneId: aws.String(zoneId), // Required 404 | StartRecordName: aws.String(recordName), 405 | } 406 | output, err := c.ListResourceRecordSets(input) 407 | if err != nil { 408 | return nil, err 409 | } 410 | return output.ResourceRecordSets, nil 411 | } 412 | 413 | func (r53m *Route53Manager) GetHostedZonesFromDns(recordName string) ([]*route53.HostedZone, error) { 414 | c := r53m.client() 415 | input := &route53.ListHostedZonesByNameInput{ 416 | DNSName: aws.String(recordName), 417 | } 418 | output, err := c.ListHostedZonesByName(input) 419 | if err != nil { 420 | return nil, err 421 | } 422 | return output.HostedZones, nil 423 | } 424 | 425 | // given a domain address do nslookup 426 | func (r53m *Route53Manager) GetNameservers(recordName string) ([]string, error) { 427 | 428 | logger := log.WithField("recordName", recordName) 429 | 430 | if val, exist := r53m.nameservers[recordName]; exist { 431 | return val, nil 432 | } 433 | 434 | logger.Debug("performing domain address nameserver lookup") 435 | 436 | // lookup dns servers if not in cache 437 | 438 | lookupResult, err := dnsu.DigNSSearch(recordName) 439 | 440 | if err != nil { 441 | return nil, err 442 | } 443 | 444 | var nsResult []string 445 | 446 | for nsName, _ := range lookupResult.Records { 447 | nsResult = append(nsResult, strings.TrimRight(nsName, ".")) 448 | } 449 | 450 | log.WithField("ns", nsResult).Debug("found nameservers for domain address") 451 | return nsResult, nil 452 | } 453 | 454 | // given hosted zone id find the nameservers 455 | func (r53m *Route53Manager) GetHZNameservers(hzId string) ([]string, error) { 456 | var nsResult []string 457 | 458 | hzId = strings.TrimLeft(hzId, "/hostedzone/") 459 | 460 | logger := log.WithField("hostedZoneId", hzId) 461 | 462 | if val, exist := r53m.nameservers[hzId]; exist { 463 | return val, nil 464 | } 465 | 466 | logger.Debug("performing hosted zone nameserver lookup") 467 | 468 | c := r53m.client() 469 | 470 | i := &route53.GetHostedZoneInput{ 471 | Id: aws.String(hzId), 472 | } 473 | 474 | o, err := c.GetHostedZone(i) 475 | 476 | if err != nil { 477 | return nil, err 478 | } 479 | 480 | // possibly private zone, need to lookup for NS records manually 481 | if o == nil { 482 | return nil, errors.New("ErrHostedZonesEmptyResult: " + hzId) 483 | } 484 | // possibly private zone, need to lookup for NS records manually 485 | if o.DelegationSet == nil && *o.HostedZone.Config.PrivateZone { 486 | 487 | nsRecords, err := r53m.getHostedZoneRecords(hzId, *o.HostedZone.Name, "NS") 488 | 489 | if err != nil { 490 | return nil, err 491 | } 492 | 493 | for _, r := range nsRecords { 494 | for _, ns := range r.ResourceRecords { 495 | nsResult = append(nsResult, strings.TrimRight(*ns.Value, ".")) 496 | } 497 | } 498 | 499 | } else { 500 | for _, ns := range o.DelegationSet.NameServers { 501 | nsResult = append(nsResult, strings.TrimRight(*ns, ".")) 502 | } 503 | } 504 | 505 | r53m.nameservers[hzId] = nsResult 506 | 507 | logger.WithField("ns", nsResult).Debug("found nameservers for hosted zone") 508 | 509 | return nsResult, nil 510 | } 511 | 512 | func (r53m *Route53Manager) IsNSMatch(ns1, ns2 []string) bool { 513 | 514 | nsCounter := map[string]int{} 515 | for _, n := range ns1 { 516 | if n != "" { 517 | nsCounter[n] += 1 518 | } 519 | } 520 | for _, n := range ns2 { 521 | if n != "" { 522 | nsCounter[n] += 1 523 | } 524 | } 525 | 526 | for n, c := range nsCounter { 527 | if c > 1 { 528 | log.WithField("ns", n).Debug("found ns match") 529 | return true 530 | } 531 | } 532 | log.WithFields(log.Fields{ 533 | "ns1": ns1, 534 | "ns2": ns2, 535 | }).Warn("ns don't match in comparison") 536 | 537 | return false 538 | } 539 | -------------------------------------------------------------------------------- /aws_utils/route53_test.go: -------------------------------------------------------------------------------- 1 | package aws_utils_test 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | awsu "github.com/isan-rivkin/route53-cli/aws_utils" 8 | ) 9 | 10 | func TestStripRecord(t *testing.T) { 11 | cases := map[string]string{ 12 | "ronen": "ronen", 13 | "https://" + "\052" + ".example.com/query?param1=foo¶m2=bar¶m3=baz": "*.example.com", 14 | "*.example.com/query?param1=foo¶m2=bar¶m3=baz": "*.example.com", 15 | "https://example.com/aaaa": "example.com", 16 | "http://*.example.com/sss": "*.example.com", 17 | "https://a.foo.us-east-1.int.example.io": "a.foo.us-east-1.int.example.io", 18 | "*.a.foo.us-east-1.int.example.io": "*.a.foo.us-east-1.int.example.io", 19 | } 20 | for test, expected := range cases { 21 | strippedList, err := awsu.StripRecord(test) 22 | stripped := strings.Join(strippedList, ".") 23 | if err != nil || stripped != expected { 24 | t.Fatalf("error striping record %s parsed=[%s] expected=[%s]", err, stripped, expected) 25 | } 26 | } 27 | } 28 | 29 | func GetAllOptionsForZoneNameTest(t *testing.T) { 30 | cases := map[string]string{ 31 | "a": "a", 32 | "*.a": "a", 33 | "a.b": "a.b,b", 34 | "*.a.b": "a.b.c,b.c,c", 35 | "*.a.b.c.d": "a.b.c.d,b.c.d,c.d,d", 36 | "a.b.c.d.e": "a.b.c.d.e,b.c.d.e,c.d.e,d.e,e", 37 | "us-east-1.a-b.foo-goo.int.example.io": "us-east-1.a-b.foo-goo.int.example.io,a-b.foo-goo.int.example.io,a-b.foo-goo.int.example.io,foo-goo.int.example.io,int.example.io,example.io,io", 38 | } 39 | for test, expected := range cases { 40 | r, err := awsu.NewRecordName(test) 41 | if err != nil { 42 | t.Fatalf("failed to create record name from test %s", err) 43 | } 44 | strippedList, err := r.GetAllOptionsForZoneName() 45 | stripped := strings.Join(strippedList, ",") 46 | if err != nil || stripped != expected { 47 | t.Fatalf("error striping record %s parsed=[%s] expected=[%s]", err, stripped, expected) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /aws_utils/types.go: -------------------------------------------------------------------------------- 1 | package aws_utils 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go/aws/session" 5 | route53 "github.com/aws/aws-sdk-go/service/route53" 6 | ) 7 | 8 | type DNSRecordType string 9 | 10 | const ( 11 | WildCard string = "\\052" 12 | ) 13 | 14 | // general configuration for handlers 15 | type Conf map[string]interface{} 16 | 17 | // result of a handler is ready 18 | type ResCb func(error, Resource) 19 | 20 | // Specific resource handler (for example: describe ec2 instances) 21 | type Handler interface { 22 | Id() string 23 | Get() Resource 24 | Fetch(Conf, *session.Session, ResCb) 25 | } 26 | 27 | // Resource is a result of each handler 28 | type Resource interface { 29 | Id() string 30 | } 31 | 32 | // Manages all Handlers for a specific group (i.e route53 is a group of many resources) 33 | type GroupManager interface { 34 | Id() string 35 | GetHandler(string) Handler 36 | GetResource(string) Resource 37 | } 38 | 39 | type GetRecordAliasesResult struct { 40 | Records []*route53.ResourceRecordSet 41 | HostedZone *route53.HostedZone 42 | Stream RecordStream 43 | Region string 44 | } 45 | 46 | type Route53Api interface { 47 | TestDNSAnswer(hostedZoneId, recordName, recordType string) (*route53.TestDNSAnswerOutput, error) 48 | GetHostedZonesFromDns(recordName string) ([]*route53.HostedZone, error) 49 | GetRecordSetAliases(recordName string, skipNSVerification bool) (*GetRecordAliasesResult, error) 50 | GetRecordSetAliasesRecursive(maxDepth int, recordName string, skipNSVerification bool, checkedRecord map[string]bool) ([]*GetRecordAliasesResult, error) 51 | GetRegion() string 52 | GetNameservers(recordName string) ([]string, error) 53 | GetHZNameservers(hzId string) ([]string, error) 54 | IsNSMatch(ns1, ns2 []string) bool 55 | } 56 | 57 | type Route53Manager struct { 58 | session *session.Session 59 | r53client *route53.Route53 60 | nameservers map[string][]string 61 | } 62 | 63 | type RecordStream interface { 64 | GetAllOptionsForZoneName() ([]string, error) 65 | IsEqual(other string) bool 66 | HasWildCard() bool 67 | GetWithWildCard() (string, error) 68 | GetParsedURL() string 69 | } 70 | 71 | type RecordName struct { 72 | hasWildCard bool 73 | rawURL string 74 | parsedURL string 75 | splittedURL []string 76 | } 77 | -------------------------------------------------------------------------------- /aws_utils/urls.go: -------------------------------------------------------------------------------- 1 | package aws_utils 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "strings" 8 | 9 | "github.com/aws/aws-sdk-go/aws" 10 | route53 "github.com/aws/aws-sdk-go/service/route53" 11 | ) 12 | 13 | const ( 14 | // .us-east-1.elb.amazonaws.com 15 | ALBDns = "elb.amazonaws.com" 16 | CLBDns = "elb.amazonaws.com" 17 | // .elb.us-east-1.amazonaws.com 18 | NLBDns = "elb." 19 | // .elasticloadbalancing.us-east-2.amazonaws.com 20 | ELBDns = "elasticloadbalancing." 21 | // s3-website.af-south-1.amazonaws.com 22 | S3Suffx = "s3-website." 23 | // .us-east-1.vpce.amazonaws.com 24 | VPCEndpointDns = "vpce.amazonaws.com" 25 | CloudFrontDns = "cloudfront.net" 26 | // .us-west-2.elasticbeanstalk.com 27 | ElasticBeansTalkDns = "elasticbeanstalk.com" 28 | AcceleratorApiDns = "awsglobalaccelerator.com" 29 | ) 30 | 31 | var ( 32 | SupportedTarget = []string{ALBDns, CLBDns, NLBDns, ELBDns, S3Suffx, VPCEndpointDns, CloudFrontDns, ElasticBeansTalkDns, AcceleratorApiDns} 33 | URLGenerators = map[string]func(dnsIdentifier string, r *route53.ResourceRecordSet) string{ 34 | ALBDns: GetLBWebURL, 35 | NLBDns: GetLBWebURL, 36 | CloudFrontDns: GetCloudFrontWebURL, 37 | } 38 | ) 39 | 40 | // checks if the value of a route53 record is an alias to a routable AWS resource, if true will return the resource type from the list 41 | func CheckRoutableAWSTarget(r *route53.ResourceRecordSet) (string, bool) { 42 | if r.AliasTarget == nil || r.AliasTarget.DNSName == nil { 43 | 44 | return "", false 45 | } 46 | 47 | dns := *r.AliasTarget.DNSName 48 | 49 | for _, st := range SupportedTarget { 50 | if strings.Contains(dns, st) { 51 | return st, true 52 | } 53 | } 54 | 55 | return "", false 56 | } 57 | 58 | // id is typicall /hostedzone/1234LZW9ITZ26T and we need only 1234LZW9ITZ26T 59 | func GenerateRoute53HostedZoneWebURL(hzId string) string { 60 | splittedId := strings.Split(hzId, "/") 61 | 62 | if len(splittedId) < 1 { 63 | return "" 64 | } 65 | 66 | id := splittedId[len(splittedId)-1] 67 | r53Url := fmt.Sprintf("https://console.aws.amazon.com/route53/v2/hostedzones#ListRecordSets/%s", id) 68 | return r53Url 69 | } 70 | 71 | func GenerateWebURL(r *route53.ResourceRecordSet) (string, error) { 72 | e := errors.New("ErrNotRoutable") 73 | if dnsType, routable := CheckRoutableAWSTarget(r); routable { 74 | g, found := URLGenerators[dnsType] 75 | if found { 76 | return g(dnsType, r), nil 77 | } 78 | e = errors.New("ErrNotSupportedGenerator") 79 | } 80 | return "", e 81 | } 82 | 83 | // https://console.aws.amazon.com/ec2/v2/home?region=us-east-1#LoadBalancers:search=some-alb-name;sort=loadBalancerName 84 | func GetLBWebURL(dnsIdentifier string, r *route53.ResourceRecordSet) string { 85 | record := aws.StringValue(r.AliasTarget.DNSName) 86 | record = strings.TrimRight(record, ".") 87 | 88 | region := aws.StringValue(r.Region) 89 | searchQuery := record 90 | // parse region 91 | splitted := strings.Split(record, ".") 92 | 93 | if dnsIdentifier == ALBDns || dnsIdentifier == ELBDns { 94 | // .us-east-1.elb.amazonaws.com 95 | if len(splitted) >= 4 { 96 | region = splitted[len(splitted)-4] 97 | } 98 | 99 | } else if dnsIdentifier == NLBDns { 100 | 101 | // .elb.us-east-1.amazonaws.com 102 | if len(splitted) >= 3 { 103 | region = splitted[len(splitted)-3] 104 | } 105 | } 106 | if strings.HasPrefix(searchQuery, "dualstack.") { 107 | searchQuery = strings.TrimLeft(searchQuery, "dualstack.") 108 | } 109 | return fmt.Sprintf("https://console.aws.amazon.com/ec2/v2/home?region=%s#LoadBalancers:search=%s;sort=loadBalancerName", region, searchQuery) 110 | } 111 | 112 | // https://console.aws.amazon.com/cloudfront/home?region=us-west-2# 113 | // todo:: extract the record id and point to specific resource, cant search via url this is the general ui 114 | // the region doesn't matter it's global 115 | func GetCloudFrontWebURL(dnsIdentifier string, r *route53.ResourceRecordSet) string { 116 | return fmt.Sprintf("https://console.aws.amazon.com/cloudfront/home?region=%s#", "us-east-1") 117 | } 118 | -------------------------------------------------------------------------------- /aws_utils/urls_test.go: -------------------------------------------------------------------------------- 1 | package aws_utils_test 2 | 3 | import ( 4 | "testing" 5 | 6 | awsu "github.com/isan-rivkin/route53-cli/aws_utils" 7 | 8 | route53 "github.com/aws/aws-sdk-go/service/route53" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func newRecSet(dns, region string) *route53.ResourceRecordSet { 13 | return &route53.ResourceRecordSet{ 14 | Region: ®ion, 15 | AliasTarget: &route53.AliasTarget{ 16 | DNSName: &dns, 17 | }, 18 | } 19 | } 20 | 21 | func TestGenerateWebURL(t *testing.T) { 22 | //r.AliasTarget.DNSName 23 | cases := []struct { 24 | Record *route53.ResourceRecordSet 25 | ShouldError bool 26 | ExpectedOutput string 27 | }{ 28 | { 29 | Record: newRecSet("", ""), 30 | ShouldError: true, 31 | ExpectedOutput: "", 32 | }, 33 | { 34 | Record: newRecSet("dualstack.my-production-alb.eu-east-1.elb.amazonaws.com.", "eu-east-1"), 35 | ShouldError: false, 36 | ExpectedOutput: "https://console.aws.amazon.com/ec2/v2/home?region=eu-east-1#LoadBalancers:search=my-production-alb.eu-east-1.elb.amazonaws.com;sort=loadBalancerName", 37 | }, 38 | { 39 | Record: newRecSet("some-rec-ok-eu-east-1.similarweb.com.", "eu-east-1"), 40 | ShouldError: true, 41 | ExpectedOutput: "", 42 | }, 43 | } 44 | 45 | for _, c := range cases { 46 | url, err := awsu.GenerateWebURL(c.Record) 47 | 48 | isError := err != nil 49 | 50 | assert.Equal(t, c.ShouldError, isError, "error comparison dont match") 51 | assert.Equal(t, url, c.ExpectedOutput, "url output not as expected") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 Isan Rivkin isanrivkin@gmail.com 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file 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 cmd 17 | 18 | import ( 19 | "fmt" 20 | "io/ioutil" 21 | "os" 22 | 23 | awsu "github.com/isan-rivkin/route53-cli/aws_utils" 24 | "github.com/isan-rivkin/route53-cli/sdk" 25 | 26 | v "github.com/isan-rivkin/cliversioner" 27 | homedir "github.com/mitchellh/go-homedir" 28 | log "github.com/sirupsen/logrus" 29 | "github.com/spf13/cobra" 30 | "github.com/spf13/viper" 31 | ) 32 | 33 | const ( 34 | AppVersion = "0.4.3" 35 | ) 36 | 37 | var cfgFile string 38 | var recordInput string 39 | var awsProfile string 40 | var outputFile string 41 | var recusiveSearchMaxDepth *int 42 | var recursiveSearch *bool 43 | var outputJson bool 44 | var debug bool 45 | var webUrl bool 46 | var skipNSVerification bool 47 | 48 | // rootCmd represents the base command when called without any subcommands 49 | var rootCmd = &cobra.Command{ 50 | Use: "r53 -r '*.some.dns.record.com'\n r53 -r https://my.r53.website.com", 51 | Version: AppVersion, 52 | Short: "Query route53 to get your dns record values", 53 | Long: `Query Route53 to get all sorts of information about a dns record. 54 | r53 will use your default AWS credentials`, 55 | Run: func(cmd *cobra.Command, args []string) { 56 | ExecuteR53() 57 | VersionCheck() 58 | 59 | }, 60 | } 61 | 62 | func VersionCheck() { 63 | var err error 64 | optoutVar := "R53_VERSION_CHECK" 65 | i := v.NewInput("route53-cli", "https://github.com/isan-rivkin", AppVersion, &optoutVar) 66 | out, err := v.CheckVersion(i) 67 | 68 | if err != nil || out == nil { 69 | log.WithError(err).Debug("failed checking latest version from github.com") 70 | return 71 | } 72 | 73 | if out.Outdated { 74 | m := fmt.Sprintf("%s is not latest, %s, upgrade to %s", out.CurrentVersion, out.Message, out.LatestVersion) 75 | log.Warn(m) 76 | } 77 | } 78 | 79 | func ExecuteR53() { 80 | depth := *recusiveSearchMaxDepth 81 | if !*recursiveSearch { 82 | // default recurse 3 depth max 83 | depth = 3 84 | } 85 | 86 | input, err := sdk.NewInput(recordInput, awsProfile, debug, false, skipNSVerification, true, depth) 87 | 88 | if err != nil { 89 | log.WithError(err).Error("failed creating input") 90 | return 91 | } 92 | 93 | results, err := sdk.SearchR53(input) 94 | 95 | if err != nil { 96 | log.WithError(err).Error("failed searching") 97 | return 98 | } 99 | if outputJson || outputFile != "" { 100 | res, err := sdk.ToJSONOutput(results, true) 101 | if err != nil { 102 | log.WithError(err).Error("failed converting output to json") 103 | return 104 | } 105 | 106 | if outputFile != "" { 107 | if err = ioutil.WriteFile(outputFile, res, 0644); err != nil { 108 | log.WithError(err).Error("failed writting result to output file") 109 | return 110 | } 111 | } else { 112 | fmt.Println(string(res)) 113 | } 114 | 115 | } else { 116 | for _, r := range results { 117 | r.PrintTable(&awsu.PrintOptions{WebURL: webUrl}) 118 | } 119 | } 120 | 121 | } 122 | 123 | // Execute adds all child commands to the root command and sets flags appropriately. 124 | // This is called by main.main(). It only needs to happen once to the rootCmd. 125 | func Execute() { 126 | if err := rootCmd.Execute(); err != nil { 127 | fmt.Println(err) 128 | os.Exit(1) 129 | } 130 | } 131 | 132 | func init() { 133 | //cobra.OnInitialize(initConfig) 134 | // Here you will define your flags and configuration settings. 135 | // Cobra supports persistent flags, which, if defined here, 136 | // will be global for your application. 137 | 138 | //rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.r53.yaml)") 139 | rootCmd.PersistentFlags().StringVarP(&recordInput, "record", "r", "", "-r www.foo.app.com") 140 | rootCmd.PersistentFlags().StringVarP(&awsProfile, "profile", "p", "default", "~/.aws/credentials chosen account") 141 | rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "Get verbose output about the process") 142 | // add web urls 143 | rootCmd.PersistentFlags().BoolVar(&webUrl, "url", true, "print url to the aws console that will display the resource") 144 | rootCmd.PersistentFlags().BoolVar(&skipNSVerification, "ns-skip", false, "if set then nameservers will not be verified against the hosted zone result") 145 | R := false 146 | rootCmd.PersistentFlags().BoolVarP(&R, "recurse", "R", false, "if used then the tool will run recursively until all records have resolved") 147 | recursiveSearch = &R 148 | 149 | maxDepth := 3 150 | rootCmd.PersistentFlags().IntVarP(&maxDepth, "max-depth", "d", maxDepth, "if -R is used then specifies when to stop recursive search depth") 151 | recusiveSearchMaxDepth = &maxDepth 152 | // output as json result 153 | rootCmd.PersistentFlags().BoolVar(&outputJson, "output-json", false, "output the result as a json object") 154 | rootCmd.PersistentFlags().StringVarP(&outputFile, "output-file", "f", "", "save output as json to file path") 155 | // Cobra also supports local flags, which will only run 156 | // when this action is called directly. 157 | //rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 158 | } 159 | 160 | // initConfig reads in config file and ENV variables if set. 161 | func initConfig() { 162 | if cfgFile != "" { 163 | // Use config file from the flag. 164 | viper.SetConfigFile(cfgFile) 165 | } else { 166 | // Find home directory. 167 | home, err := homedir.Dir() 168 | if err != nil { 169 | fmt.Println(err) 170 | os.Exit(1) 171 | } 172 | 173 | // Search config in home directory with name ".r53" (without extension). 174 | viper.AddConfigPath(home) 175 | viper.SetConfigName(".r53") 176 | } 177 | 178 | viper.AutomaticEnv() // read in environment variables that match 179 | 180 | // If a config file is found, read it in. 181 | if err := viper.ReadInConfig(); err == nil { 182 | fmt.Println("Using config file:", viper.ConfigFileUsed()) 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /dnsu/dig.go: -------------------------------------------------------------------------------- 1 | package dnsu 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/lixiangzhong/dnsutil" 9 | "github.com/miekg/dns" 10 | log "github.com/sirupsen/logrus" 11 | ) 12 | 13 | type NSRecord struct { 14 | Name string 15 | RR dns.RR 16 | } 17 | 18 | type NSLookupResult struct { 19 | Records map[string]*NSRecord 20 | } 21 | 22 | func (nsr *NSLookupResult) IsRecordExist(record string) bool { 23 | r := record 24 | if !strings.HasSuffix(record, ".") { 25 | r += "." 26 | } 27 | 28 | _, exist := nsr.Records[r] 29 | return exist 30 | } 31 | 32 | func (nsr *NSLookupResult) AppendRecords(other *NSLookupResult) { 33 | 34 | if other != nil && len(other.Records) > 0 { 35 | 36 | if nsr.Records == nil { 37 | nsr.Records = map[string]*NSRecord{} 38 | } 39 | 40 | for name, rec := range other.Records { 41 | nsr.Records[name] = rec 42 | } 43 | } 44 | } 45 | func DigNSSearch(domain string) (*NSLookupResult, error) { 46 | 47 | var dig dnsutil.Dig 48 | 49 | msg, err := dig.GetMsg(dns.TypeNS, domain) 50 | 51 | if err != nil { 52 | fmt.Println(err) 53 | return nil, err 54 | } 55 | 56 | return newNSLookupResultFromDigMsg(msg) 57 | } 58 | 59 | func newNSLookupResultFromDigMsg(m *dns.Msg) (*NSLookupResult, error) { 60 | result := &NSLookupResult{ 61 | Records: map[string]*NSRecord{}, 62 | } 63 | 64 | if ans, err := praseRRFromDigMsg(m.Answer); err == nil { 65 | result.AppendRecords(ans) 66 | } 67 | if extra, err := praseRRFromDigMsg(m.Extra); err == nil { 68 | result.AppendRecords(extra) 69 | } 70 | if ns, err := praseRRFromDigMsg(m.Ns); err == nil { 71 | result.AppendRecords(ns) 72 | } 73 | 74 | if result.Records == nil || len(result.Records) == 0 { 75 | return nil, errors.New("ErrNoNSResultForHost") 76 | } 77 | return result, nil 78 | } 79 | 80 | func praseRRFromDigMsg(rr []dns.RR) (*NSLookupResult, error) { 81 | 82 | result := &NSLookupResult{ 83 | Records: map[string]*NSRecord{}, 84 | } 85 | 86 | if len(rr) == 0 { 87 | return result, errors.New("ErrNoRR") 88 | } 89 | 90 | for _, r := range rr { 91 | if t, ok := r.(*dns.NS); ok { 92 | 93 | log.WithFields(log.Fields{ 94 | "type": "NS", 95 | "result": t, 96 | }).Debug("found NS record") 97 | 98 | result.Records[t.Ns] = &NSRecord{ 99 | Name: t.Ns, 100 | RR: t, 101 | } 102 | // SOA for private hosted zone 103 | } else if t, ok := r.(*dns.SOA); ok { 104 | 105 | log.WithFields(log.Fields{ 106 | "type": "SOA", 107 | "result": t, 108 | }).Debug("found SOA record") 109 | 110 | result.Records[t.Ns] = &NSRecord{ 111 | Name: t.Ns, 112 | RR: t, 113 | } 114 | } 115 | } 116 | return result, nil 117 | } 118 | -------------------------------------------------------------------------------- /docs/logo-s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Isan-Rivkin/route53-cli/ac102e83cec10235afc611c771bb4004f47e5929/docs/logo-s.png -------------------------------------------------------------------------------- /docs/logo-xl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Isan-Rivkin/route53-cli/ac102e83cec10235afc611c771bb4004f47e5929/docs/logo-xl.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/isan-rivkin/route53-cli 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go v1.35.30 7 | github.com/isan-rivkin/cliversioner v0.0.0-20210716125537-d70532e39e77 8 | github.com/jedib0t/go-pretty/v6 v6.0.5 9 | github.com/kr/text v0.2.0 // indirect 10 | github.com/lixiangzhong/dnsutil v1.4.0 11 | github.com/miekg/dns v1.1.42 12 | github.com/mitchellh/go-homedir v1.1.0 13 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect 14 | github.com/sirupsen/logrus v1.8.1 15 | github.com/smartystreets/assertions v1.0.0 // indirect 16 | github.com/spf13/cobra v1.2.1 17 | github.com/spf13/viper v1.8.1 18 | github.com/stretchr/testify v1.7.0 19 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /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 v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 16 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= 17 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= 18 | cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= 19 | cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= 20 | cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= 21 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 22 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 23 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 24 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 25 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 26 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 27 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 28 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 29 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 30 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 31 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 32 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 33 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 34 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 35 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 36 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 37 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 38 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 39 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 40 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 41 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 42 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 43 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 44 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 45 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 46 | github.com/aws/aws-sdk-go v1.35.30 h1:ZT+70Tw1ar5U2bL81ZyIvcLorxlD1UoxoIgjsEkismY= 47 | github.com/aws/aws-sdk-go v1.35.30/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= 48 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 49 | github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= 50 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 51 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 52 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 53 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 54 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 55 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 56 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 57 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 58 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 59 | github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 60 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 61 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 62 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 63 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 64 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 65 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 66 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 67 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 68 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= 69 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 70 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 71 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 72 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 73 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 74 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 75 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 76 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 77 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 78 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 79 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 80 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 81 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 82 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 83 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 84 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 85 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 86 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 87 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 88 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 89 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 90 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 91 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 92 | github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= 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.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 97 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 98 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 99 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 100 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 101 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 102 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 103 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 104 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 105 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 106 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 107 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 108 | github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= 109 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 110 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 111 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 112 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 113 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 114 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 115 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 116 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 117 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 118 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 119 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 120 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 121 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 122 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 123 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 124 | github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= 125 | github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= 126 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 127 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 128 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 129 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 130 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 131 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 132 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 133 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 134 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 135 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 136 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 137 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 138 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 139 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 140 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 141 | github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 142 | github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 143 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 144 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 145 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 146 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 147 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 148 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 149 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 150 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 151 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 152 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 153 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 154 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 155 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 156 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 157 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 158 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 159 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 160 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 161 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 162 | github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw= 163 | github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 164 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 165 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 166 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 167 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 168 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 169 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 170 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 171 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 172 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 173 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 174 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 175 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 176 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 177 | github.com/isan-rivkin/cliversioner v0.0.0-20210716125537-d70532e39e77 h1:Cf724WYTR4UzhwpctBDxazgEWljXYX6tZ3C3j1zLj3s= 178 | github.com/isan-rivkin/cliversioner v0.0.0-20210716125537-d70532e39e77/go.mod h1:Xc6RZYFqieexxPsqiu0opKapsD3WfJxoe9n6ATToc+U= 179 | github.com/jedib0t/go-pretty/v6 v6.0.5 h1:oOo0/jSb3NEYKT6l1hhFXoX2UZnkanMuCE2DVT1mqnE= 180 | github.com/jedib0t/go-pretty/v6 v6.0.5/go.mod h1:MTr6FgcfNdnN5wPVBzJ6mhJeDyiF0yBvS2TMXEV/XSU= 181 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 182 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 183 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 184 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 185 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 186 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 187 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 188 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 189 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 190 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 191 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 192 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 193 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 194 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 195 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 196 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 197 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 198 | github.com/lixiangzhong/dnsutil v1.4.0 h1:S75ND4O8IbNhVdaP/Bn+3YHXPYvt6jpeqy3Yyr+iUNY= 199 | github.com/lixiangzhong/dnsutil v1.4.0/go.mod h1:hQj5Vdv9+/m5GZxu75Hp4SMPeYV3JZUABlUadwNVFmk= 200 | github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= 201 | github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= 202 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 203 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 204 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= 205 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 206 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 207 | github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= 208 | github.com/miekg/dns v1.1.42 h1:gWGe42RGaIqXQZ+r3WUGEKBEtvPHY2SXo4dqixDNxuY= 209 | github.com/miekg/dns v1.1.42/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= 210 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 211 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 212 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 213 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 214 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 215 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 216 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 217 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 218 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 219 | github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= 220 | github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 221 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 222 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 223 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 224 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 225 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 226 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 227 | github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= 228 | github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 229 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 230 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 231 | github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= 232 | github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= 233 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 234 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 235 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 236 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 237 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 238 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 239 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 240 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 241 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 242 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 243 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 244 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 245 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 246 | github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8= 247 | github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= 248 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= 249 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 250 | github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= 251 | github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= 252 | github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= 253 | github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 254 | github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= 255 | github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= 256 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= 257 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 258 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 259 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 260 | github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= 261 | github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= 262 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 263 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 264 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 265 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 266 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 267 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 268 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 269 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 270 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= 271 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 272 | github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e h1:IWllFTiDjjLIf2oeKxpIUmtiDV5sn71VgeQgg6vcE7k= 273 | github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e/go.mod h1:d7u6HkTYKSv5m6MCKkOQlHwaShTMl3HjqSGW3XtVhXM= 274 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 275 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 276 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 277 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 278 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 279 | go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= 280 | go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= 281 | go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= 282 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 283 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 284 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 285 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 286 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 287 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 288 | go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= 289 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 290 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 291 | go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= 292 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 293 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 294 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 295 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 296 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 297 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 298 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 299 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 300 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 301 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 302 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 303 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 304 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 305 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 306 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 307 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 308 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 309 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 310 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 311 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 312 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 313 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 314 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 315 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 316 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 317 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 318 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 319 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 320 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 321 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 322 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 323 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 324 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 325 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 326 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 327 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 328 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 329 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 330 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 331 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 332 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 333 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 334 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 335 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 336 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 337 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 338 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 339 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 340 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 341 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 342 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 343 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 344 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 345 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 346 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 347 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 348 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 349 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 350 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 351 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 352 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 353 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 354 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 355 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 356 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 357 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 358 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 359 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 360 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 361 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 362 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 363 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 364 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 365 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 366 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 367 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 368 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 369 | golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= 370 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 371 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= 372 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 373 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 374 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 375 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 376 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 377 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 378 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 379 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 380 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 381 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 382 | golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 383 | golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 384 | golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 385 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 386 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 387 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 388 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 389 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 390 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 391 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 392 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 393 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 394 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 395 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 396 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 397 | golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 398 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 399 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 400 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 401 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 402 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 403 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 404 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 405 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 406 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 407 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 408 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 409 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 410 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 413 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 414 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 415 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 416 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 417 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 418 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 419 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 420 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 421 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 422 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 423 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 424 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 425 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 426 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 427 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 428 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 429 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 430 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 431 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 432 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 433 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 434 | golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 435 | golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 436 | golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 437 | golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 438 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 439 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 440 | golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 441 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 442 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= 443 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 444 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 445 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 446 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 447 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 448 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 449 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 450 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 451 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 452 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 453 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 454 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 455 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 456 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 457 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 458 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 459 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 460 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 461 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 462 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 463 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 464 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 465 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 466 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 467 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 468 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 469 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 470 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 471 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 472 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 473 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 474 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 475 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 476 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 477 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 478 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 479 | golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 480 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 481 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 482 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 483 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 484 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 485 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 486 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 487 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 488 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 489 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 490 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 491 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 492 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 493 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 494 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 495 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 496 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 497 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 498 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 499 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 500 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 501 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= 502 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 503 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 504 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 505 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 506 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 507 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 508 | golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 509 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 510 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 511 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 512 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 513 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 514 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 515 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 516 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 517 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 518 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 519 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 520 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 521 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 522 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 523 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 524 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 525 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 526 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 527 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 528 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 529 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 530 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= 531 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= 532 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= 533 | google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= 534 | google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= 535 | google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= 536 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 537 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 538 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 539 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 540 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 541 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 542 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 543 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 544 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 545 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 546 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 547 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 548 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 549 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 550 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 551 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 552 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 553 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 554 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 555 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 556 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 557 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 558 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 559 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 560 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 561 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 562 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 563 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 564 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 565 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 566 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 567 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 568 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 569 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 570 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 571 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 572 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 573 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 574 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 575 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 576 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 577 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 578 | google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 579 | google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 580 | google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 581 | google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 582 | google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= 583 | google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= 584 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 585 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 586 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 587 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 588 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 589 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 590 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 591 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 592 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 593 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 594 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 595 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 596 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 597 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 598 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 599 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= 600 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 601 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 602 | google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 603 | google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 604 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 605 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 606 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 607 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 608 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 609 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 610 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 611 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 612 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 613 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 614 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 615 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 616 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 617 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 618 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 619 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 620 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 621 | gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= 622 | gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 623 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 624 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 625 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 626 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 627 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 628 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 629 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 630 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 631 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 632 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 633 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 634 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 635 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 636 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 637 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 638 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 639 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 640 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 641 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 Isan Rivkin isanrivkin@gmail.com 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file 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 | 17 | package main 18 | 19 | import ( 20 | "github.com/isan-rivkin/route53-cli/cmd" 21 | ) 22 | 23 | func main() { 24 | 25 | cmd.Execute() 26 | 27 | } 28 | -------------------------------------------------------------------------------- /sdk/output.go: -------------------------------------------------------------------------------- 1 | package sdk 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | awsu "github.com/isan-rivkin/route53-cli/aws_utils" 7 | 8 | "github.com/aws/aws-sdk-go/aws" 9 | ) 10 | 11 | type RoutingOutput struct { 12 | Policy awsu.RoutingPolicy `json:"policy,omitempty"` 13 | Differentiator string `json:"differentiator,omitempty"` 14 | } 15 | 16 | type RecordOutput struct { 17 | HzID string `json:"hostedZoneId"` 18 | Record string `json:"record"` 19 | Type string `json:"type"` 20 | Alias string `json:"alias"` 21 | WebURL string `json:"url"` 22 | Routing RoutingOutput `json:"routing,omitempty"` 23 | Resources []string `json:"resources,omitempty"` 24 | } 25 | 26 | type HostedZoneOutput struct { 27 | Name string `json:"name"` 28 | Id string `json:"id"` 29 | WebUrl string `json:"url"` 30 | TotalRecords int64 `json:"totalRecords"` 31 | Private bool `json:"private"` 32 | } 33 | 34 | func NewHostedZoneOutput(a *awsu.GetRecordAliasesResult) HostedZoneOutput { 35 | id := aws.StringValue(a.HostedZone.Id) 36 | o := HostedZoneOutput{ 37 | Name: aws.StringValue(a.HostedZone.Name), 38 | Id: id, 39 | WebUrl: awsu.GenerateRoute53HostedZoneWebURL(id), 40 | TotalRecords: aws.Int64Value(a.HostedZone.ResourceRecordSetCount), 41 | Private: aws.BoolValue(a.HostedZone.Config.PrivateZone), 42 | } 43 | return o 44 | } 45 | 46 | type ResultOutput struct { 47 | Records []RecordOutput `json:"records"` 48 | HostedZones map[string]HostedZoneOutput `json:"hostedZones"` 49 | } 50 | 51 | func NewRecordOutput(a awsu.RecordAccessor) RecordOutput { 52 | o := RecordOutput{ 53 | HzID: a.GetHostedZoneID(), 54 | } 55 | rec, _ := a.GetRecord() 56 | typ, _ := a.GetRecordType() 57 | alias, _ := a.GetAliasValue() 58 | webUrl, _ := a.GetWebURL() 59 | 60 | resources, _ := a.GetResources() 61 | 62 | o.Record = rec 63 | o.Type = typ 64 | o.Alias = alias 65 | o.WebURL = webUrl 66 | 67 | o.Resources = resources 68 | 69 | rp, hasRp := a.GetRoutingPolicy() 70 | 71 | if hasRp { 72 | rpd, _ := a.GetRoutingPolicyDifferentiator() 73 | o.Routing = RoutingOutput{ 74 | Policy: rp, 75 | Differentiator: rpd, 76 | } 77 | } 78 | return o 79 | } 80 | func ToSimpleOutput(resultSet []*awsu.GetRecordAliasesResult) *ResultOutput { 81 | var records []RecordOutput 82 | accessors := awsu.FlatRecordResultToAccessors(resultSet) 83 | for _, a := range accessors { 84 | records = append(records, NewRecordOutput(a)) 85 | } 86 | 87 | // add hosted zone info 88 | seenHz := map[string]HostedZoneOutput{} 89 | for _, rs := range resultSet { 90 | hz := NewHostedZoneOutput(rs) 91 | // make sure there is no duplicates 92 | if _, seen := seenHz[hz.Id]; !seen { 93 | seenHz[hz.Id] = hz 94 | } 95 | 96 | } 97 | 98 | return &ResultOutput{Records: records, HostedZones: seenHz} 99 | } 100 | 101 | func ToJSONOutput(resultSet []*awsu.GetRecordAliasesResult, pretty bool) ([]byte, error) { 102 | result := ToSimpleOutput(resultSet) 103 | if pretty { 104 | return json.MarshalIndent(result, "", " ") 105 | } else { 106 | return json.Marshal(result) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /sdk/search.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 Isan Rivkin isanrivkin@gmail.com 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file 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 | 17 | package sdk 18 | 19 | import ( 20 | "fmt" 21 | 22 | awsu "github.com/isan-rivkin/route53-cli/aws_utils" 23 | 24 | log "github.com/sirupsen/logrus" 25 | ) 26 | 27 | type AWSAuthInput struct { 28 | AWSProfile string 29 | } 30 | 31 | type Input struct { 32 | SkipNSVerification bool 33 | AwsAuth AWSAuthInput 34 | Record string 35 | RecursiveSearch bool 36 | RecursiveMaxDepth int 37 | Debug bool 38 | MuteLogs bool 39 | } 40 | 41 | func NewInput(recordInput, awsProfile string, debug, muteLogs bool, skipNSVerification, recursiveSearch bool, recursiveMaxDepth int) (Input, error) { 42 | i := Input{} 43 | 44 | if recordInput == "" { 45 | return i, fmt.Errorf("record input to search is empty") 46 | } 47 | 48 | i.SkipNSVerification = skipNSVerification 49 | i.AwsAuth = AWSAuthInput{AWSProfile: awsProfile} 50 | i.Record = recordInput 51 | i.Debug = debug 52 | i.MuteLogs = muteLogs 53 | i.RecursiveSearch = recursiveSearch 54 | i.RecursiveMaxDepth = recursiveMaxDepth 55 | 56 | if i.Debug && !i.MuteLogs { 57 | log.SetLevel(log.DebugLevel) 58 | } 59 | if i.MuteLogs { 60 | log.SetLevel(log.PanicLevel) 61 | } 62 | 63 | if skipNSVerification { 64 | log.Warn("skipping nameserver verification, possibly inccorect result, not recomended.") 65 | } 66 | 67 | log.WithField("profile", awsProfile).Info("using aws environment session") 68 | 69 | return i, nil 70 | } 71 | 72 | func SearchR53(in Input) ([]*awsu.GetRecordAliasesResult, error) { 73 | api := awsu.NewRoute53Api(in.AwsAuth.AWSProfile) 74 | 75 | results, err := api.GetRecordSetAliasesRecursive(in.RecursiveMaxDepth, in.Record, in.SkipNSVerification, nil) 76 | 77 | if err != nil { 78 | return nil, err 79 | } 80 | return results, nil 81 | } 82 | --------------------------------------------------------------------------------