├── .github └── workflows │ └── go.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── OWNERS ├── README.md ├── RELEASE.md ├── SECURITY.md ├── SECURITY_CONTACTS ├── cmd ├── completion.go ├── cycles.go ├── graph.go ├── list.go ├── root.go ├── stats.go ├── utils.go └── utils_test.go ├── code-of-conduct.md ├── depstat-demo.gif ├── go.mod ├── go.sum └── main.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 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: make build 23 | 24 | - name: Test 25 | run: make test 26 | 27 | lint: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v2 31 | 32 | - name: Run golint 33 | run: | 34 | export PATH=$PATH:$(go env GOPATH)/bin 35 | make lint 36 | 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | depstat 2 | .DS_Store 3 | bin -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://git.k8s.io/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt: 4 | 5 | _As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._ 6 | 7 | ## Getting Started 8 | 9 | We have full documentation on how to get started contributing here: 10 | 11 | 14 | 15 | - [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests 16 | - [Kubernetes Contributor Guide](https://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](https://git.k8s.io/community/contributors/guide#contributing) 17 | - [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet) - Common resources for existing developers 18 | 19 | ## Mentorship 20 | 21 | - [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers! 22 | 23 | 27 | 28 | ## Contact Information 29 | 30 | - [Slack channel](https://kubernetes.slack.com/messages/k8s-code-organization) 31 | 32 | ## PR Workflow 33 | 34 | 1. Clone the repository by running 35 | ``` 36 | git clone https://github.com/kubernetes-sigs/depstat.git 37 | ``` 38 | 39 | 2. Create another branch where you'll be making your changes by running, 40 | 41 | ``` 42 | git checkout -b branch_name 43 | ``` 44 | 45 | 3. After making your changes you can test them by creating a binary of depstat with your changes. To do this run, `go build`. This will create a binary of depstat with your changes in the project root which you can use to test. 46 | 47 | 4. After you're satisfied with your changes, commit and push your branch: 48 | 49 | ``` 50 | git add . 51 | git commit -s -m "commit message goes here" 52 | git push origin branch_name 53 | ``` 54 | 55 | 5. You can now use this branch to [create a pull request](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) to the `main` branch of the project. 56 | 57 | You can read more about the GitHub Workflow [here](https://www.kubernetes.dev/docs/guide/github-workflow/). 58 | 59 | 6. One of the important jobs run in the CI is [golangci-lint](https://github.com/golangci/golangci-lint). You might see it complaining about code formatting and its output can be confusing at times. You can run `gofmt -d .` locally to see the changes needed to make the CI happy :) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test 2 | test: 3 | go test -v ./... 4 | 5 | .PHONY: build 6 | build: 7 | go build 8 | 9 | .PHONY: lint 10 | lint: 11 | go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.39.0 12 | golangci-lint run --verbose --enable gofmt 13 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | approvers: 2 | - alenkacz 3 | - dims 4 | - navidshaikh 5 | - nikhita 6 | - RinkiyaKeDad 7 | 8 | reviewers: 9 | - alenkacz 10 | - dims 11 | - navidshaikh 12 | - nikhita 13 | - RinkiyaKeDad -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # depstat 2 | 3 | `depstat` is a command-line tool for analyzing dependencies of Go modules enabled projects. 4 | 5 | ![depstat demo with k8s repo](./depstat-demo.gif) 6 | 7 | ## Installation 8 | To install depstat you can run 9 | 10 | ``` 11 | go install github.com/kubernetes-sigs/depstat@latest 12 | ``` 13 | 14 | ## Usage 15 | 16 | - `depstat` can be used as a standalone command-line application. You can use `depstat` to produce metrics about the dependencies of your Go modules enabled project. 17 | 18 | - Another common way to run `depstat` is in the CI pipeline of your project. This would help you analyze the dependency changes which come with PRs. 19 | You can look at how this is done for the [kubernetes/kubernetes](https://github.com/kubernetes/kubernetes) repo using [prow](https://github.com/kubernetes/test-infra/tree/master/prow) [here](https://github.com/kubernetes/test-infra/blob/master/config/jobs/kubernetes/sig-arch/kubernetes-depstat.yaml). 20 | 21 | ## Commands 22 | 23 | To see the list of commands `depstat` offers you can run `depstat help`. `depstat` currently supports the following commands: 24 | 25 | ### cycles 26 | 27 | `depstat cycles` shows all the cycles present in the dependencies of the project. 28 | 29 | An example of a cycle in project dependenies is: 30 | `golang.org/x/net -> golang.org/x/crypto -> golang.org/x/net` 31 | 32 | `--json` prints the output of the cycles command in JSON format. For the above example the JSON output would look like this: 33 | ``` 34 | { 35 | "cycles": [ 36 | [ 37 | "golang.org/x/net", 38 | "golang.org/x/crypto", 39 | "golang.org/x/net" 40 | ] 41 | ] 42 | } 43 | ``` 44 | 45 | ### graph 46 | 47 | `depstat graph` will generate a `graph.dot` file which can be used with [Graphviz](https://graphviz.org)'s dot command to visualize the dependencies of a project. 48 | 49 | For example, after running `depstat graph`, an SVG can be created using: 50 | `twopi -Tsvg -o dag.svg graph.dot` 51 | 52 | By default, the graph would be created around the main module (first module in the `go mod graph` output), but you can choose to create a graph around a particular dependency using the `--dep` flag. 53 | 54 | ### list 55 | 56 | `depstat list` shows a sorted list of all project dependencies. These include both direct and transitive dependencies. 57 | 58 | 1. Direct dependencies: Dependencies that are directly used in the code of the project. These do not include standard go packages like `fmt`, etc. These are dependencies that appear on the right side of the main module in the `go mod graph` output. 59 | 60 | 2. Transitive dependencies: These are dependencies that get imported because they are needed by some direct dependency of the project. These are dependencies that appear on the right side of a dependency that isn't the main module in the `go mod graph` output. 61 | 62 | ### stats 63 | 64 | `depstat stats` will provide the following metrics about the dependencies of the project: 65 | 66 | 1. Direct Dependencies: Total number of dependencies required by the [main module(s)](#main-module) directly. 67 | 68 | 2. Transitive Dependencies: Total number of transitive dependencies (dependencies which are further needed by direct dependencies of the project). 69 | 70 | 3. Total Dependencies: Total number of dependencies of the [main module(s)](#main-module). 71 | 72 | 4. Max Depth of Dependencies: Length of the longest chain starting from the first [main module](#main-module); defaults to length from the first module encountered in "go mod graph" output. 73 | 74 | - The `--json` flag gives this output in a JSON format. 75 | - `--verbose` mode will help provide you with the list of all the dependencies and will also print the longest dependency chain. 76 | 77 | #### main module 78 | By default, the first module encountered in "go mod graph" output is treated as the main module by `depstat`. Depstat uses this main module to determine the direct and transitive dependencies. This behavior can be changed by specifying the main module manually using the `--mainModules` flag with the stats command. The flag takes a list of modules names, for example: 79 | 80 | ``` 81 | depstat stats --mainModules="k8s.io/kubernetes,k8s.io/kubectl" 82 | ``` 83 | 84 | ## Project Goals 85 | `depstat` is being developed under the code organization sub-project under [SIG Architecture](https://github.com/kubernetes/community/tree/master/sig-architecture). The goal is to make it easy to evaluate dependency updates to Kubernetes. This is done by running `depstat` as part of the Kubernetes CI pipeline. 86 | 87 | ## Community Contact Information 88 | You can reach the maintainers of this project at: 89 | 90 | [#k8s-code-organization](https://kubernetes.slack.com/messages/k8s-code-organization) on the [Kubernetes slack](http://slack.k8s.io). 91 | 92 | ### Code of conduct 93 | 94 | Participation in the Kubernetes community is governed by the [Kubernetes Code of Conduct](code-of-conduct.md). 95 | 96 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release Process 2 | 3 | depstat is released on an as-needed basis. The process is as follows: 4 | 5 | 1. An issue proposing a new release with a changelog since the last release is created. 6 | 2. All [OWNERS](OWNERS) must LGTM this release. 7 | 3. An OWNER builds the latest binary with the appropriate tag by running `go build -ldflags "-X main.DepstatVersion="`. 8 | 4. An OWNER uses the GitHub releases page to create a new release and drops the built binaries along with the changelog. 9 | 5. The release issue is closed. 10 | 6. An announcement email is sent to `kubernetes-dev@googlegroups.com` with the subject `[ANNOUNCE] depstat $VERSION is released`. 11 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Security Announcements 4 | 5 | Join the [kubernetes-security-announce] group for security and vulnerability announcements. 6 | 7 | You can also subscribe to an RSS feed of the above using [this link][kubernetes-security-announce-rss]. 8 | 9 | ## Reporting a Vulnerability 10 | 11 | Instructions for reporting a vulnerability can be found on the 12 | [Kubernetes Security and Disclosure Information] page. 13 | 14 | ## Supported Versions 15 | 16 | Information about supported Kubernetes versions can be found on the 17 | [Kubernetes version and version skew support policy] page on the Kubernetes website. 18 | 19 | [kubernetes-security-announce]: https://groups.google.com/forum/#!forum/kubernetes-security-announce 20 | [kubernetes-security-announce-rss]: https://groups.google.com/forum/feed/kubernetes-security-announce/msgs/rss_v2_0.xml?num=50 21 | [Kubernetes version and version skew support policy]: https://kubernetes.io/docs/setup/release/version-skew-policy/#supported-versions 22 | [Kubernetes Security and Disclosure Information]: https://kubernetes.io/docs/reference/issues-security/security/#report-a-vulnerability -------------------------------------------------------------------------------- /SECURITY_CONTACTS: -------------------------------------------------------------------------------- 1 | # Defined below are the security contacts for this repo. 2 | # 3 | # They are the contact point for the Product Security Committee to reach out 4 | # to for triaging and handling of incoming issues. 5 | # 6 | # The below names agree to abide by the 7 | # [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy) 8 | # and will be removed and replaced if they violate that agreement. 9 | # 10 | # DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE 11 | # INSTRUCTIONS AT https://kubernetes.io/security/ 12 | 13 | alenkacz 14 | dims 15 | navidshaikh 16 | nikhita 17 | RinkiyaKeDad -------------------------------------------------------------------------------- /cmd/completion.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 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 cmd 18 | 19 | import ( 20 | "os" 21 | 22 | "github.com/spf13/cobra" 23 | ) 24 | 25 | var completionCmd = &cobra.Command{ 26 | Use: "completion [bash|zsh|fish|powershell]", 27 | Short: "Generate completion script", 28 | Long: `To load completions: 29 | Bash: 30 | $ source <(depstat completion bash) 31 | # To load completions for each session, execute once: 32 | # Linux: 33 | $ depstat completion bash > /etc/bash_completion.d/depstat 34 | # macOS: 35 | $ depstat completion bash > /usr/local/etc/bash_completion.d/depstat 36 | Zsh: 37 | # If shell completion is not already enabled in your environment, 38 | # you will need to enable it. You can execute the following once: 39 | $ echo "autoload -U compinit; compinit" >> ~/.zshrc 40 | # To load completions for each session, execute once: 41 | $ depstat completion zsh > "${fpath[1]}/_depstat" 42 | # You will need to start a new shell for this setup to take effect. 43 | fish: 44 | $ depstat completion fish | source 45 | # To load completions for each session, execute once: 46 | $ depstat completion fish > ~/.config/fish/completions/depstat.fish 47 | PowerShell: 48 | PS> depstat completion powershell | Out-String | Invoke-Expression 49 | # To load completions for every new session, run: 50 | PS> depstat completion powershell > depstat.ps1 51 | # and source this file from your PowerShell profile. 52 | `, 53 | DisableFlagsInUseLine: true, 54 | ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, 55 | Args: cobra.ExactValidArgs(1), 56 | Run: func(cmd *cobra.Command, args []string) { 57 | switch args[0] { 58 | case "bash": 59 | _ = cmd.Root().GenBashCompletion(os.Stdout) 60 | case "zsh": 61 | _ = cmd.Root().GenZshCompletion(os.Stdout) 62 | case "fish": 63 | _ = cmd.Root().GenFishCompletion(os.Stdout, true) 64 | case "powershell": 65 | _ = cmd.Root().GenPowerShellCompletion(os.Stdout) 66 | } 67 | }, 68 | } 69 | 70 | func init() { 71 | rootCmd.AddCommand(completionCmd) 72 | } 73 | -------------------------------------------------------------------------------- /cmd/cycles.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 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 cmd 18 | 19 | import ( 20 | "encoding/json" 21 | "fmt" 22 | 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | var jsonOutputCycles bool 27 | 28 | // analyzeDepsCmd represents the analyzeDeps command 29 | var cyclesCmd = &cobra.Command{ 30 | Use: "cycles", 31 | Short: "Prints cycles in dependency chains.", 32 | Long: `Will show all the cycles in the dependencies of the project.`, 33 | RunE: func(cmd *cobra.Command, args []string) error { 34 | 35 | if len(args) != 0 { 36 | return fmt.Errorf("cycles does not take any arguments") 37 | } 38 | 39 | overview := getDepInfo(nil) 40 | var cycleChains []Chain 41 | var temp Chain 42 | getCycleChains(overview.MainModules[0], overview.Graph, temp, &cycleChains) 43 | cycles := getCycles(cycleChains) 44 | 45 | if !jsonOutputCycles { 46 | fmt.Println("All cycles in dependencies are: ") 47 | for _, c := range cycles { 48 | printChain(c) 49 | } 50 | } else { 51 | // create json 52 | outputObj := struct { 53 | Cycles []Chain `json:"cycles"` 54 | }{ 55 | Cycles: cycles, 56 | } 57 | outputRaw, err := json.MarshalIndent(outputObj, "", "\t") 58 | if err != nil { 59 | return err 60 | } 61 | fmt.Print(string(outputRaw)) 62 | } 63 | return nil 64 | }, 65 | } 66 | 67 | // get all chains which have a cycle 68 | func getCycleChains(currentDep string, graph map[string][]string, currentChain Chain, cycleChains *[]Chain) { 69 | currentChain = append(currentChain, currentDep) 70 | _, ok := graph[currentDep] 71 | if ok { 72 | for _, dep := range graph[currentDep] { 73 | if !contains(currentChain, dep) { 74 | cpy := make(Chain, len(currentChain)) 75 | copy(cpy, currentChain) 76 | getCycleChains(dep, graph, cpy, cycleChains) 77 | } else { 78 | *cycleChains = append(*cycleChains, append(currentChain, dep)) 79 | } 80 | } 81 | } 82 | } 83 | 84 | // gets the cycles from the cycleChains 85 | func getCycles(cycleChains []Chain) []Chain { 86 | var cycles []Chain 87 | for _, chain := range cycleChains { 88 | var cycle Chain 89 | start := false 90 | startDep := chain[len(chain)-1] 91 | for _, val := range chain { 92 | if val == startDep { 93 | start = true 94 | } 95 | if start { 96 | cycle = append(cycle, val) 97 | } 98 | } 99 | if !sliceContains(cycles, cycle) { 100 | cycles = append(cycles, cycle) 101 | } 102 | } 103 | return cycles 104 | } 105 | 106 | func init() { 107 | rootCmd.AddCommand(cyclesCmd) 108 | cyclesCmd.Flags().BoolVarP(&jsonOutputCycles, "json", "j", false, "Get the output in JSON format") 109 | } 110 | -------------------------------------------------------------------------------- /cmd/graph.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 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 cmd 18 | 19 | import ( 20 | "fmt" 21 | "io/ioutil" 22 | "sort" 23 | "strings" 24 | 25 | "github.com/spf13/cobra" 26 | ) 27 | 28 | var dep string 29 | 30 | var graphCmd = &cobra.Command{ 31 | Use: "graph", 32 | Short: "Generate a .dot file to be used with Graphviz's dot command.", 33 | Long: `A graph.dot file will be generated which can be used with Graphviz's dot command. 34 | For example to generate a svg image use: 35 | twopi -Tsvg -o dag.svg graph.dot`, 36 | RunE: func(cmd *cobra.Command, args []string) error { 37 | overview := getDepInfo(nil) 38 | // strict ensures that there is only one edge between two vertices 39 | // overlap = false ensures the vertices don't overlap 40 | fileContents := "strict digraph {\ngraph [overlap=false];\n" 41 | 42 | // graph to be generated is based around input dep 43 | if dep != "" { 44 | var chains []Chain 45 | var temp Chain 46 | getAllChains(overview.MainModules[0], overview.Graph, temp, &chains) 47 | fileContents += getFileContentsForSingleDep(chains, dep) 48 | } else { 49 | fileContents += getFileContentsForAllDeps(overview) 50 | } 51 | fileContents += "}" 52 | fileContentsByte := []byte(fileContents) 53 | err := ioutil.WriteFile("./graph.dot", fileContentsByte, 0644) 54 | if err != nil { 55 | return err 56 | } 57 | fmt.Println("\nCreated graph.dot file!") 58 | return nil 59 | }, 60 | } 61 | 62 | // find all possible chains starting from currentDep 63 | func getAllChains(currentDep string, graph map[string][]string, currentChain Chain, chains *[]Chain) { 64 | currentChain = append(currentChain, currentDep) 65 | _, ok := graph[currentDep] 66 | if ok { 67 | for _, dep := range graph[currentDep] { 68 | if !contains(currentChain, dep) { 69 | cpy := make(Chain, len(currentChain)) 70 | copy(cpy, currentChain) 71 | getAllChains(dep, graph, cpy, chains) 72 | } else { 73 | *chains = append(*chains, currentChain) 74 | } 75 | } 76 | } else { 77 | *chains = append(*chains, currentChain) 78 | } 79 | } 80 | 81 | // get the contents of the .dot file for the graph 82 | // when the -d flag is set 83 | func getFileContentsForSingleDep(chains []Chain, dep string) string { 84 | // to color the entered node as yellow 85 | data := colorMainNode(dep) 86 | 87 | // add all chains which have the input dep to the .dot file 88 | for _, chain := range chains { 89 | if chainContains(chain, dep) { 90 | for i := range chain { 91 | if chain[i] == dep { 92 | chain[i] = "MainNode" 93 | } else { 94 | chain[i] = "\"" + chain[i] + "\"" 95 | } 96 | } 97 | data += strings.Join(chain, " -> ") 98 | data += "\n" 99 | } 100 | } 101 | return data 102 | } 103 | 104 | // get the contents of the .dot file for the graph 105 | // of all dependencies (when -d is not set) 106 | func getFileContentsForAllDeps(overview *DependencyOverview) string { 107 | 108 | // color the main module as yellow 109 | data := colorMainNode(overview.MainModules[0]) 110 | allDeps := getAllDeps(overview.DirectDepList, overview.TransDepList) 111 | allDeps = append(allDeps, overview.MainModules[0]) 112 | sort.Strings(allDeps) 113 | for _, dep := range allDeps { 114 | _, ok := overview.Graph[dep] 115 | if !ok { 116 | continue 117 | } 118 | // main module can never be a neighbour 119 | for _, neighbour := range overview.Graph[dep] { 120 | if dep == overview.MainModules[0] { 121 | // for the main module use a colored node 122 | data += fmt.Sprintf("\"MainNode\" -> \"%s\"\n", neighbour) 123 | } else { 124 | data += fmt.Sprintf("\"%s\" -> \"%s\"\n", dep, neighbour) 125 | } 126 | } 127 | } 128 | return data 129 | } 130 | 131 | func chainContains(chain Chain, dep string) bool { 132 | for _, d := range chain { 133 | if d == dep { 134 | return true 135 | } 136 | } 137 | return false 138 | } 139 | 140 | func colorMainNode(mainNode string) string { 141 | return fmt.Sprintf("MainNode [label=\"%s\", style=\"filled\" color=\"yellow\"]\n", mainNode) 142 | } 143 | 144 | func init() { 145 | rootCmd.AddCommand(graphCmd) 146 | graphCmd.Flags().StringVarP(&dep, "dep", "d", "", "Specify dependency to create a graph around") 147 | } 148 | -------------------------------------------------------------------------------- /cmd/list.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 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 cmd 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/spf13/cobra" 23 | ) 24 | 25 | // analyzeDepsCmd represents the analyzeDeps command 26 | var listCmd = &cobra.Command{ 27 | Use: "list", 28 | Short: "Lists all project dependencies", 29 | Long: `Gives a list of all the dependencies of the project. 30 | These include both direct as well as transitive dependencies.`, 31 | RunE: func(cmd *cobra.Command, args []string) error { 32 | 33 | if len(args) != 0 { 34 | return fmt.Errorf("list does not take any arguments") 35 | } 36 | 37 | depGraph := getDepInfo(nil) 38 | fmt.Println("List of all dependencies:") 39 | allDeps := getAllDeps(depGraph.DirectDepList, depGraph.TransDepList) 40 | printDeps(allDeps) 41 | return nil 42 | }, 43 | } 44 | 45 | func init() { 46 | rootCmd.AddCommand(listCmd) 47 | 48 | } 49 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 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 cmd 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | var DepstatVersion string 27 | 28 | // rootCmd represents the base command when called without any subcommands 29 | var rootCmd = &cobra.Command{ 30 | Use: "depstat", 31 | Short: "Analyze your Go project's dependencies", 32 | Long: `depstat will help you get details about the dependencies of your Go modules enabled project`, 33 | Version: DepstatVersion, 34 | // Uncomment the following line if your bare application 35 | // has an action associated with it: 36 | // Run: func(cmd *cobra.Command, args []string) { }, 37 | } 38 | 39 | // Execute adds all child commands to the root command and sets flags appropriately. 40 | // This is called by main.main(). It only needs to happen once to the rootCmd. 41 | func Execute() { 42 | if err := rootCmd.Execute(); err != nil { 43 | fmt.Println(err) 44 | os.Exit(1) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /cmd/stats.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 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 cmd 18 | 19 | import ( 20 | "encoding/json" 21 | "fmt" 22 | 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | var dir string 27 | var jsonOutput bool 28 | var csvOutput bool 29 | var verbose bool 30 | var mainModules []string 31 | 32 | type Chain []string 33 | 34 | // statsCmd represents the statsDeps command 35 | var statsCmd = &cobra.Command{ 36 | Use: "stats", 37 | Short: "Shows metrics about dependency chains", 38 | Long: `Provides the following metrics: 39 | 1. Direct Dependencies: Total number of dependencies required by the mainModule(s) directly 40 | 2. Transitive Dependencies: Total number of transitive dependencies (dependencies which are further needed by direct dependencies of the project) 41 | 3. Total Dependencies: Total number of dependencies of the mainModule(s) 42 | 4. Max Depth of Dependencies: Length of the longest chain starting from the first mainModule; defaults to length from the first module encountered in "go mod graph" output`, 43 | RunE: func(cmd *cobra.Command, args []string) error { 44 | depGraph := getDepInfo(mainModules) 45 | 46 | if len(args) != 0 { 47 | return fmt.Errorf("stats does not take any arguments") 48 | } 49 | 50 | // get the longest chain 51 | var temp Chain 52 | longestChain := getLongestChain(depGraph.MainModules[0], depGraph.Graph, temp, map[string]Chain{}) 53 | // get values 54 | maxDepth := len(longestChain) 55 | directDeps := len(depGraph.DirectDepList) 56 | transitiveDeps := len(depGraph.TransDepList) 57 | totalDeps := len(getAllDeps(depGraph.DirectDepList, depGraph.TransDepList)) 58 | 59 | if !jsonOutput && !csvOutput { 60 | fmt.Printf("Direct Dependencies: %d \n", directDeps) 61 | fmt.Printf("Transitive Dependencies: %d \n", transitiveDeps) 62 | fmt.Printf("Total Dependencies: %d \n", totalDeps) 63 | fmt.Printf("Max Depth Of Dependencies: %d \n", maxDepth) 64 | } 65 | 66 | if verbose { 67 | fmt.Println("All dependencies:") 68 | printDeps(getAllDeps(depGraph.DirectDepList, depGraph.TransDepList)) 69 | } 70 | 71 | // print the longest chain 72 | if verbose { 73 | fmt.Println("Longest chain/s: ") 74 | printChain(longestChain) 75 | } 76 | 77 | if jsonOutput { 78 | // create json 79 | outputObj := struct { 80 | DirectDeps int `json:"directDependencies"` 81 | TransDeps int `json:"transitiveDependencies"` 82 | TotalDeps int `json:"totalDependencies"` 83 | MaxDepth int `json:"maxDepthOfDependencies"` 84 | }{ 85 | DirectDeps: directDeps, 86 | TransDeps: transitiveDeps, 87 | TotalDeps: totalDeps, 88 | MaxDepth: maxDepth, 89 | } 90 | outputRaw, err := json.MarshalIndent(outputObj, "", "\t") 91 | if err != nil { 92 | return err 93 | } 94 | fmt.Print(string(outputRaw)) 95 | } 96 | if csvOutput { 97 | fmt.Println("Direct,Transitive,Total,MaxDepth") 98 | fmt.Printf("%d,%d,%d,%d\n", directDeps, transitiveDeps, totalDeps, maxDepth) 99 | } 100 | return nil 101 | }, 102 | } 103 | 104 | // get the longest chain starting from currentDep 105 | func getLongestChain(currentDep string, graph map[string][]string, currentChain Chain, longestChains map[string]Chain) Chain { 106 | // fmt.Println(strings.Repeat(" ", len(currentChain)), currentDep) 107 | 108 | // already computed 109 | if longestChain, ok := longestChains[currentDep]; ok { 110 | return longestChain 111 | } 112 | 113 | deps := graph[currentDep] 114 | 115 | if len(deps) == 0 { 116 | // we have no dependencies, our longest chain is just us 117 | longestChains[currentDep] = Chain{currentDep} 118 | return longestChains[currentDep] 119 | } 120 | 121 | if contains(currentChain, currentDep) { 122 | // we've already been visited in the current chain, avoid cycles but also don't record a longest chain for currentDep 123 | return nil 124 | } 125 | 126 | currentChain = append(currentChain, currentDep) 127 | // find the longest dependency chain 128 | var longestDepChain Chain 129 | for _, dep := range deps { 130 | depChain := getLongestChain(dep, graph, currentChain, longestChains) 131 | if len(depChain) > len(longestDepChain) { 132 | longestDepChain = depChain 133 | } 134 | } 135 | // prepend ourselves to the longest of our dependencies' chains and persist 136 | longestChains[currentDep] = append(Chain{currentDep}, longestDepChain...) 137 | return longestChains[currentDep] 138 | } 139 | 140 | func init() { 141 | rootCmd.AddCommand(statsCmd) 142 | statsCmd.Flags().StringVarP(&dir, "dir", "d", "", "Directory containing the module to evaluate. Defaults to the current directory.") 143 | statsCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Get additional details") 144 | statsCmd.Flags().BoolVarP(&jsonOutput, "json", "j", false, "Get the output in JSON format") 145 | statsCmd.Flags().BoolVarP(&csvOutput, "csv", "c", false, "Get the output in CSV format") 146 | statsCmd.Flags().StringSliceVarP(&mainModules, "mainModules", "m", []string{}, "Enter modules whose dependencies should be considered direct dependencies; defaults to the first module encountered in `go mod graph` output") 147 | } 148 | -------------------------------------------------------------------------------- /cmd/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 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 cmd 18 | 19 | import ( 20 | "bufio" 21 | "fmt" 22 | "log" 23 | "os/exec" 24 | "sort" 25 | "strings" 26 | ) 27 | 28 | func printChain(slice []string) { 29 | fmt.Println() 30 | fmt.Println(strings.Join(slice, " -> ")) 31 | } 32 | 33 | // DependencyOverview holds dependency module informations 34 | type DependencyOverview struct { 35 | // Dependency graph edges modelled as node plus adjacency nodes 36 | Graph map[string][]string 37 | // List of all direct dependencies 38 | DirectDepList []string 39 | // List of all transitive dependencies 40 | TransDepList []string 41 | // Name of the module from which the dependencies are computed 42 | MainModules []string 43 | } 44 | 45 | func getDepInfo(mainModules []string) *DependencyOverview { 46 | // get output of "go mod graph" in a string 47 | goModGraph := exec.Command("go", "mod", "graph") 48 | if dir != "" { 49 | goModGraph.Dir = dir 50 | } 51 | goModGraphOutput, err := goModGraph.Output() 52 | if err != nil { 53 | log.Fatal(err) 54 | } 55 | goModGraphOutputString := string(goModGraphOutput) 56 | 57 | // create a graph of dependencies from that output 58 | depGraph := generateGraph(goModGraphOutputString, mainModules) 59 | return &depGraph 60 | } 61 | 62 | func printDeps(deps []string) { 63 | fmt.Println() 64 | sort.Strings(deps) 65 | for _, dep := range deps { 66 | fmt.Println(dep) 67 | } 68 | fmt.Println() 69 | } 70 | 71 | // we need this since a dependency can be both a direct and an indirect dependency 72 | func getAllDeps(directDeps []string, transDeps []string) []string { 73 | var allDeps []string 74 | for _, dep := range directDeps { 75 | if !contains(allDeps, dep) { 76 | allDeps = append(allDeps, dep) 77 | } 78 | } 79 | for _, dep := range transDeps { 80 | if !contains(allDeps, dep) { 81 | allDeps = append(allDeps, dep) 82 | } 83 | } 84 | return allDeps 85 | } 86 | 87 | func contains(s []string, str string) bool { 88 | for _, v := range s { 89 | if v == str { 90 | return true 91 | } 92 | } 93 | return false 94 | } 95 | 96 | // compares two slices of strings 97 | func isSliceSame(a, b []string) bool { 98 | if len(a) != len(b) { 99 | return false 100 | } 101 | for iterator := 0; iterator < len(a); iterator++ { 102 | if a[iterator] != b[iterator] { 103 | return false 104 | } 105 | } 106 | return true 107 | } 108 | 109 | func sliceContains(val []Chain, key Chain) bool { 110 | for _, v := range val { 111 | if isSliceSame(v, key) { 112 | return true 113 | } 114 | } 115 | return false 116 | } 117 | 118 | type module struct { 119 | name string 120 | version string 121 | } 122 | 123 | func parseModule(s string) module { 124 | if strings.Contains(s, "@") { 125 | parts := strings.SplitN(s, "@", 2) 126 | return module{name: parts[0], version: parts[1]} 127 | } 128 | return module{name: s} 129 | } 130 | 131 | func generateGraph(goModGraphOutputString string, mainModules []string) DependencyOverview { 132 | depGraph := DependencyOverview{MainModules: mainModules} 133 | versionedGraph := make(map[module][]module) 134 | var lhss []module 135 | graph := make(map[string][]string) 136 | scanner := bufio.NewScanner(strings.NewReader(goModGraphOutputString)) 137 | 138 | var versionedMainModules []module 139 | var seenVersionedMainModules = map[module]bool{} 140 | for scanner.Scan() { 141 | line := scanner.Text() 142 | words := strings.Fields(line) 143 | 144 | lhs := parseModule(words[0]) 145 | if len(versionedMainModules) == 0 || contains(mainModules, lhs.name) { 146 | if !seenVersionedMainModules[lhs] { 147 | // remember our root module and listed main modules 148 | versionedMainModules = append(versionedMainModules, lhs) 149 | seenVersionedMainModules[lhs] = true 150 | } 151 | } 152 | if len(depGraph.MainModules) == 0 { 153 | // record the first module we see as the main module by default 154 | depGraph.MainModules = append(depGraph.MainModules, lhs.name) 155 | } 156 | rhs := parseModule(words[1]) 157 | 158 | // remember the order we observed lhs modules in 159 | if len(versionedGraph[lhs]) == 0 { 160 | lhss = append(lhss, lhs) 161 | } 162 | // record this lhs -> rhs relationship 163 | versionedGraph[lhs] = append(versionedGraph[lhs], rhs) 164 | } 165 | 166 | // record effective versions of modules required by our main modules 167 | // in go1.17+, the main module records effective versions of all dependencies, even indirect ones 168 | effectiveVersions := map[string]string{} 169 | for _, mm := range versionedMainModules { 170 | for _, m := range versionedGraph[mm] { 171 | if effectiveVersions[m.name] < m.version { 172 | effectiveVersions[m.name] = m.version 173 | } 174 | } 175 | } 176 | 177 | type edge struct { 178 | from module 179 | to module 180 | } 181 | 182 | // figure out which modules in the graph are reachable from the effective versions required by our main modules 183 | reachableModules := map[string]module{} 184 | // start with our main modules 185 | var toVisit []edge 186 | for _, m := range versionedMainModules { 187 | toVisit = append(toVisit, edge{to: m}) 188 | } 189 | for len(toVisit) > 0 { 190 | from := toVisit[0].from 191 | v := toVisit[0].to 192 | toVisit = toVisit[1:] 193 | if _, reachable := reachableModules[v.name]; reachable { 194 | // already flagged as reachable 195 | continue 196 | } 197 | // mark as reachable 198 | reachableModules[v.name] = from 199 | if effectiveVersion, ok := effectiveVersions[v.name]; ok && effectiveVersion > v.version { 200 | // replace with the effective version if applicable 201 | v.version = effectiveVersion 202 | } else { 203 | // set the effective version 204 | effectiveVersions[v.name] = v.version 205 | } 206 | // queue dependants of this to check for reachability 207 | for _, m := range versionedGraph[v] { 208 | toVisit = append(toVisit, edge{from: v, to: m}) 209 | } 210 | } 211 | 212 | for _, lhs := range lhss { 213 | if _, reachable := reachableModules[lhs.name]; !reachable { 214 | // this is not reachable via required versions, skip it 215 | continue 216 | } 217 | if effectiveVersion, ok := effectiveVersions[lhs.name]; ok && effectiveVersion != lhs.version { 218 | // this is not the effective version in our graph, skip it 219 | continue 220 | } 221 | // fmt.Println(lhs.name, "via", reachableModules[lhs.name]) 222 | 223 | for _, rhs := range versionedGraph[lhs] { 224 | // we don't want to add the same dep again 225 | if !contains(graph[lhs.name], rhs.name) { 226 | graph[lhs.name] = append(graph[lhs.name], rhs.name) 227 | } 228 | 229 | // if the LHS is a mainModule 230 | // then RHS is a direct dep else transitive dep 231 | if contains(depGraph.MainModules, lhs.name) && contains(depGraph.MainModules, rhs.name) { 232 | continue 233 | } else if contains(depGraph.MainModules, lhs.name) { 234 | if !contains(depGraph.DirectDepList, rhs.name) { 235 | // fmt.Println(rhs.name, "via", lhs) 236 | depGraph.DirectDepList = append(depGraph.DirectDepList, rhs.name) 237 | } 238 | } else if !contains(depGraph.MainModules, lhs.name) { 239 | if !contains(depGraph.TransDepList, rhs.name) { 240 | // fmt.Println(rhs.name, "via", lhs) 241 | depGraph.TransDepList = append(depGraph.TransDepList, rhs.name) 242 | } 243 | } 244 | } 245 | } 246 | 247 | depGraph.Graph = graph 248 | 249 | return depGraph 250 | } 251 | -------------------------------------------------------------------------------- /cmd/utils_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 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 cmd 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | func Test_getChains_simple(t *testing.T) { 24 | 25 | /* 26 | Graph: 27 | A 28 | / | \ 29 | B C D 30 | \/ | 31 | E G 32 | | 33 | F 34 | | 35 | H 36 | */ 37 | 38 | graph := make(map[string][]string) 39 | graph["A"] = []string{"B", "C", "D"} 40 | graph["B"] = []string{"E"} 41 | graph["C"] = []string{"E"} 42 | graph["D"] = []string{"G"} 43 | graph["E"] = []string{"F"} 44 | graph["F"] = []string{"H"} 45 | 46 | transDeps := []string{"E", "G", "F", "H"} 47 | directDeps := []string{"B", "C", "D"} 48 | mainModules := []string{"A"} 49 | overview := &DependencyOverview{ 50 | Graph: graph, 51 | TransDepList: transDeps, 52 | DirectDepList: directDeps, 53 | MainModules: mainModules, 54 | } 55 | 56 | var cycleChains []Chain 57 | var chains []Chain 58 | var temp Chain 59 | longestChain := getLongestChain("A", graph, temp, map[string]Chain{}) 60 | maxDepth := len(longestChain) 61 | getCycleChains("A", graph, temp, &cycleChains) 62 | getAllChains("A", graph, temp, &chains) 63 | cycles := getCycles(cycleChains) 64 | 65 | correctChains := [][]string{ 66 | {"A", "B", "E", "F", "H"}, 67 | {"A", "C", "E", "F", "H"}, 68 | {"A", "D", "G"}, 69 | } 70 | 71 | correctFileContentsForAllDeps := `MainNode [label="A", style="filled" color="yellow"] 72 | "MainNode" -> "B" 73 | "MainNode" -> "C" 74 | "MainNode" -> "D" 75 | "B" -> "E" 76 | "C" -> "E" 77 | "D" -> "G" 78 | "E" -> "F" 79 | "F" -> "H" 80 | ` 81 | if correctFileContentsForAllDeps != getFileContentsForAllDeps(overview) { 82 | t.Errorf("File contents for graph of all dependencies are wrong") 83 | } 84 | 85 | for i := range chains { 86 | if !isSliceSame(chains[i], correctChains[i]) { 87 | t.Errorf("Chains are not same") 88 | } 89 | } 90 | 91 | correctFileContentsForSingleDep := `MainNode [label="E", style="filled" color="yellow"] 92 | "A" -> "B" -> MainNode -> "F" -> "H" 93 | "A" -> "C" -> MainNode -> "F" -> "H" 94 | ` 95 | if correctFileContentsForSingleDep != getFileContentsForSingleDep(chains, "E") { 96 | t.Errorf("File contents for graph of a single dependency are wrong") 97 | } 98 | 99 | if len(cycles) != 0 { 100 | t.Errorf("There should be no cycles") 101 | } 102 | 103 | if maxDepth != 5 { 104 | t.Errorf("Max depth of dependencies was incorrect") 105 | } 106 | 107 | correctLongestChain := Chain{"A", "B", "E", "F", "H"} 108 | 109 | if !isSliceSame(correctLongestChain, longestChain) { 110 | t.Errorf("First longest path was incorrect") 111 | } 112 | 113 | } 114 | 115 | func Test_getChains_cycle(t *testing.T) { 116 | 117 | /* 118 | Graph: 119 | A 120 | / \ 121 | B C 122 | | | 123 | D E 124 | / \ 125 | H F 126 | \ / 127 | G 128 | */ 129 | 130 | graph := make(map[string][]string) 131 | graph["A"] = []string{"B", "C"} 132 | graph["B"] = []string{"D"} 133 | graph["C"] = []string{"E"} 134 | graph["D"] = []string{"F"} 135 | graph["F"] = []string{"G"} 136 | graph["G"] = []string{"H"} 137 | graph["H"] = []string{"D"} 138 | 139 | transDeps := []string{"D", "E", "F", "G", "H"} 140 | directDeps := []string{"B", "C"} 141 | mainModules := []string{"A"} 142 | overview := &DependencyOverview{ 143 | Graph: graph, 144 | TransDepList: transDeps, 145 | DirectDepList: directDeps, 146 | MainModules: mainModules, 147 | } 148 | 149 | var cycleChains []Chain 150 | var chains []Chain 151 | var temp Chain 152 | longestChain := getLongestChain("A", graph, temp, map[string]Chain{}) 153 | maxDepth := len(longestChain) 154 | getCycleChains("A", graph, temp, &cycleChains) 155 | getAllChains("A", graph, temp, &chains) 156 | cycles := getCycles(cycleChains) 157 | 158 | correctFileContentsForAllDeps := `MainNode [label="A", style="filled" color="yellow"] 159 | "MainNode" -> "B" 160 | "MainNode" -> "C" 161 | "B" -> "D" 162 | "C" -> "E" 163 | "D" -> "F" 164 | "F" -> "G" 165 | "G" -> "H" 166 | "H" -> "D" 167 | ` 168 | if correctFileContentsForAllDeps != getFileContentsForAllDeps(overview) { 169 | t.Errorf("File contents for graph of all dependencies are wrong") 170 | } 171 | 172 | correctChains := [][]string{ 173 | {"A", "B", "D", "F", "G", "H"}, 174 | {"A", "C", "E"}, 175 | } 176 | for i := range chains { 177 | if !isSliceSame(chains[i], correctChains[i]) { 178 | t.Errorf("Chains are not same") 179 | } 180 | } 181 | 182 | correctFileContentsForSingleDep := `MainNode [label="H", style="filled" color="yellow"] 183 | "A" -> "B" -> "D" -> "F" -> "G" -> MainNode 184 | ` 185 | if correctFileContentsForSingleDep != getFileContentsForSingleDep(chains, "H") { 186 | t.Errorf("File contents for graph of a single dependency are wrong") 187 | } 188 | 189 | cyc := []string{"D", "F", "G", "H", "D"} 190 | 191 | if len(cycles) != 1 { 192 | t.Errorf("Number of cycles is not correct") 193 | } 194 | 195 | if !isSliceSame(cycles[0], cyc) { 196 | t.Errorf("Cycle is not correct") 197 | } 198 | 199 | if maxDepth != 6 { 200 | t.Errorf("Max depth of dependencies was incorrect") 201 | } 202 | 203 | correctLongestChain := []string{"A", "B", "D", "F", "G", "H"} 204 | if !isSliceSame(longestChain, correctLongestChain) { 205 | t.Errorf("Longest path was incorrect") 206 | } 207 | } 208 | 209 | func Test_getChains_cycle_2(t *testing.T) { 210 | 211 | /* 212 | Graph: 213 | A 214 | / | 215 | B | 216 | || | 217 | C -- 218 | / \ 219 | D E 220 | \ / 221 | F 222 | */ 223 | 224 | graph := make(map[string][]string) 225 | graph["A"] = []string{"B", "C"} 226 | graph["B"] = []string{"C"} 227 | graph["C"] = []string{"B", "E"} 228 | graph["E"] = []string{"F"} 229 | graph["F"] = []string{"D"} 230 | graph["D"] = []string{"C"} 231 | 232 | transDeps := []string{"C", "B", "E", "F", "D"} 233 | directDeps := []string{"B", "C"} 234 | mainModules := []string{"A"} 235 | overview := &DependencyOverview{ 236 | Graph: graph, 237 | TransDepList: transDeps, 238 | DirectDepList: directDeps, 239 | MainModules: mainModules, 240 | } 241 | 242 | var cycleChains []Chain 243 | var chains []Chain 244 | var temp Chain 245 | longestChain := getLongestChain("A", graph, temp, map[string]Chain{}) 246 | maxDepth := len(longestChain) 247 | getCycleChains("A", graph, temp, &cycleChains) 248 | getAllChains("A", graph, temp, &chains) 249 | cycles := getCycles(cycleChains) 250 | 251 | correctChains := [][]string{ 252 | {"A", "B", "C"}, 253 | {"A", "B", "C", "E", "F", "D"}, 254 | {"A", "C", "B"}, 255 | {"A", "C", "E", "F", "D"}, 256 | } 257 | 258 | correctFileContentsForAllDeps := `MainNode [label="A", style="filled" color="yellow"] 259 | "MainNode" -> "B" 260 | "MainNode" -> "C" 261 | "B" -> "C" 262 | "C" -> "B" 263 | "C" -> "E" 264 | "D" -> "C" 265 | "E" -> "F" 266 | "F" -> "D" 267 | ` 268 | if correctFileContentsForAllDeps != getFileContentsForAllDeps(overview) { 269 | t.Errorf("File contents for graph of all dependencies are wrong") 270 | } 271 | 272 | for i := range chains { 273 | if !isSliceSame(chains[i], correctChains[i]) { 274 | t.Errorf("Chains are not same") 275 | } 276 | } 277 | correctFileContentsForSingleDep := `MainNode [label="B", style="filled" color="yellow"] 278 | "A" -> MainNode -> "C" 279 | "A" -> MainNode -> "C" -> "E" -> "F" -> "D" 280 | "A" -> "C" -> MainNode 281 | ` 282 | if correctFileContentsForSingleDep != getFileContentsForSingleDep(chains, "B") { 283 | t.Errorf("File contents for graph of a single dependency are wrong") 284 | } 285 | if maxDepth != 6 { 286 | t.Errorf("Max depth of dependencies was incorrect") 287 | } 288 | 289 | if len(cycles) != 3 { 290 | t.Errorf("Number of cycles is incorrect") 291 | } 292 | cyc1 := []string{"B", "C", "B"} 293 | cyc2 := []string{"C", "E", "F", "D", "C"} 294 | cyc3 := []string{"C", "B", "C"} 295 | 296 | if !isSliceSame(cycles[0], cyc1) { 297 | t.Errorf("B C B cycle is incorrect") 298 | } 299 | 300 | if !isSliceSame(cycles[1], cyc2) { 301 | t.Errorf("C E F D C cycle is incorrect") 302 | } 303 | 304 | if !isSliceSame(cycles[2], cyc3) { 305 | t.Errorf("C B C cycle is incorrect") 306 | } 307 | 308 | correctLongestChain := []string{"A", "B", "C", "E", "F", "D"} 309 | if !isSliceSame(longestChain, correctLongestChain) { 310 | t.Errorf("Longest path was incorrect") 311 | } 312 | } 313 | 314 | // order matters 315 | func Test_isSliceSame_Pass(t *testing.T) { 316 | a := []string{"A", "B", "C", "D"} 317 | b := []string{"A", "B", "C", "D"} 318 | if !isSliceSame(a, b) { 319 | t.Errorf("Slices should have been same") 320 | } 321 | } 322 | 323 | func Test_isSliceSame_Fail(t *testing.T) { 324 | a := []string{"A", "B", "C", "D"} 325 | b := []string{"A", "B", "D", "C"} 326 | if isSliceSame(a, b) { 327 | t.Errorf("Slices should have been different") 328 | } 329 | } 330 | 331 | func Test_sliceContains_Pass(t *testing.T) { 332 | var a []Chain 333 | a = append(a, Chain{"A", "B", "C"}) 334 | a = append(a, Chain{"B", "C"}) 335 | a = append(a, Chain{"C", "A", "B"}) 336 | b := Chain{"B", "C"} 337 | if !sliceContains(a, b) { 338 | t.Errorf("Slice a should have b") 339 | } 340 | } 341 | 342 | func Test_sliceContains_Fail(t *testing.T) { 343 | var a []Chain 344 | a = append(a, Chain{"A", "B", "C"}) 345 | a = append(a, Chain{"B", "C"}) 346 | a = append(a, Chain{"C", "A", "B"}) 347 | b := Chain{"E", "C"} 348 | if sliceContains(a, b) { 349 | t.Errorf("Slice a should not have b") 350 | } 351 | } 352 | 353 | func getGoModGraphTestData() string { 354 | /* 355 | Graph: 356 | A 357 | / | \ 358 | G B D 359 | | \ | / \ 360 | F C E 361 | */ 362 | goModGraphOutputString := `A@1.1 G@1.5 363 | A@1.1 B@1.3 364 | A@1.1 D@1.2 365 | G@1.5 F@1.3 366 | G@1.5 C@1.1 367 | B@1.3 C@1.1 368 | D@1.2 C@1.1 369 | D@1.2 E@1.8` 370 | 371 | return goModGraphOutputString 372 | } 373 | func Test_generateGraph_empty_mainModule(t *testing.T) { 374 | depGraph := generateGraph(getGoModGraphTestData(), nil) 375 | 376 | transitiveDependencyList := []string{"F", "C", "E"} 377 | directDependencyList := []string{"G", "B", "D"} 378 | 379 | if depGraph.MainModules[0] != "A" { 380 | t.Errorf(`"A" must be the main module`) 381 | } 382 | 383 | if !isSliceSame(depGraph.DirectDepList, directDependencyList) { 384 | t.Errorf("Expected direct dependecies are %s but got %s", directDependencyList, depGraph.DirectDepList) 385 | } 386 | 387 | if !isSliceSame(depGraph.TransDepList, transitiveDependencyList) { 388 | t.Errorf("Expected transitive dependencies are %s but got %s", transitiveDependencyList, depGraph.TransDepList) 389 | } 390 | } 391 | 392 | func Test_generateGraph_custom_mainModule(t *testing.T) { 393 | mainModules := []string{"A", "D"} 394 | depGraph := generateGraph(getGoModGraphTestData(), mainModules) 395 | 396 | transitiveDependencyList := []string{"F", "C"} 397 | directDependencyList := []string{"G", "B", "C", "E"} 398 | 399 | if !isSliceSame(depGraph.MainModules, mainModules) { 400 | t.Errorf("Expected mainModules are %s but got %s", mainModules, depGraph.MainModules) 401 | } 402 | 403 | if !isSliceSame(depGraph.DirectDepList, directDependencyList) { 404 | t.Errorf("Expected direct dependecies are %s but got %s", directDependencyList, depGraph.DirectDepList) 405 | } 406 | 407 | if !isSliceSame(depGraph.TransDepList, transitiveDependencyList) { 408 | t.Errorf("Expected transitive dependencies are %s but got %s", transitiveDependencyList, depGraph.TransDepList) 409 | } 410 | } 411 | 412 | func Test_generateGraph_overridden_versions(t *testing.T) { 413 | mainModules := []string{"A", "D"} 414 | // obsolete C@v1 has a cycle with D@v1 and a transitive ref to unwanted dependency E@v1 415 | // effective version C@v2 updates to D@v2, which still has a cycle back to C@v2, but no dependency on E 416 | depGraph := generateGraph(`A B@v2 417 | A C@v2 418 | A D@v2 419 | B@v2 C@v1 420 | C@v1 D@v1 421 | D@v1 C@v1 422 | D@v1 E@v1 423 | C@v2 D@v2 424 | C@v2 F@v2 425 | D@v2 C@v2 426 | D@v2 G@v2`, mainModules) 427 | 428 | transitiveDependencyList := []string{"C", "D", "F"} 429 | directDependencyList := []string{"B", "C", "G"} 430 | 431 | if !isSliceSame(depGraph.MainModules, mainModules) { 432 | t.Errorf("Expected mainModules are %s but got %s", mainModules, depGraph.MainModules) 433 | } 434 | 435 | if !isSliceSame(depGraph.DirectDepList, directDependencyList) { 436 | t.Errorf("Expected direct dependecies are %s but got %s", directDependencyList, depGraph.DirectDepList) 437 | } 438 | 439 | if !isSliceSame(depGraph.TransDepList, transitiveDependencyList) { 440 | t.Errorf("Expected transitive dependencies are %s but got %s", transitiveDependencyList, depGraph.TransDepList) 441 | } 442 | } 443 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Community Code of Conduct 2 | 3 | Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) 4 | -------------------------------------------------------------------------------- /depstat-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-sigs/depstat/34e8a6168c8e0855645b74b5bcb9cdf018baf01f/depstat-demo.gif -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kubernetes-sigs/depstat 2 | 3 | go 1.16 4 | 5 | require github.com/spf13/cobra v1.1.1 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 9 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 10 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 11 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 12 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 13 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 14 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 15 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 16 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 17 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 18 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 19 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 20 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 21 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 22 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 23 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 24 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 25 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= 26 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 27 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 28 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 29 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 30 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 31 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 32 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 33 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 34 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 35 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 36 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 37 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 38 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 39 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 40 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 41 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 42 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 43 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 44 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 45 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 46 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 47 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 48 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 49 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 50 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 51 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 52 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 53 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 54 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 55 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 56 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 57 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 58 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 59 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 60 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 61 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 62 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 63 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 64 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 65 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 66 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 67 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 68 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 69 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 70 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 71 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 72 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 73 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 74 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 75 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 76 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 77 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 78 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 79 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 80 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 81 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 82 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 83 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 84 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 85 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 86 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 87 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 88 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 89 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 90 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 91 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 92 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 93 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 94 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 95 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 96 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 97 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 98 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 99 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 100 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 101 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 102 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 103 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 104 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 105 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 106 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 107 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 108 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 109 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 110 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 111 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 112 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 113 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 114 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 115 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 116 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 117 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 118 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 119 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 120 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 121 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 122 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 123 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 124 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 125 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 126 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 127 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 128 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 129 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 130 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 131 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 132 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 133 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 134 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 135 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 136 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 137 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 138 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 139 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 140 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 141 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 142 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 143 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 144 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 145 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 146 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 147 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 148 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 149 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 150 | github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= 151 | github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= 152 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 153 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 154 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 155 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 156 | github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= 157 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 158 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 159 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 160 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 161 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 162 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 163 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 164 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 165 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 166 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 167 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 168 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 169 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 170 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 171 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 172 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 173 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 174 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 175 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 176 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 177 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 178 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 179 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 180 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 181 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 182 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 183 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 184 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 185 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 186 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 187 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 188 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 189 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 190 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 191 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 192 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 193 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 194 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 195 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 196 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 197 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 198 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 199 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 200 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 201 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 202 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 203 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 204 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 205 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 206 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 207 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 208 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 209 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 210 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 211 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 212 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 213 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 214 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 215 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 216 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 217 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 218 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 219 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 220 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 221 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 222 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 223 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 224 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 225 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 226 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 227 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 228 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 229 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 230 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 231 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 232 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 233 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 234 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 235 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 236 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 237 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 238 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 239 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 240 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 241 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 242 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 243 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 244 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 245 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 246 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 247 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 248 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 249 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 250 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 251 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 252 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 253 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 254 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 255 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 256 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 257 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 258 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 259 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 260 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 261 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 262 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 263 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 264 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 265 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 266 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 267 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 268 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 269 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 270 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 271 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 272 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 273 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 274 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 275 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 276 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 277 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 278 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 279 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 280 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 281 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 282 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 283 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 284 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 285 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 286 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 287 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 The Kubernetes Authors. 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 "github.com/kubernetes-sigs/depstat/cmd" 20 | 21 | func main() { 22 | cmd.Execute() 23 | } 24 | --------------------------------------------------------------------------------