From 82bf00450f7ccbec96d433d76a1db05a361f9a80 Mon Sep 17 00:00:00 2001 From: zhaoyonghe Date: Wed, 29 Sep 2021 11:18:19 -0400 Subject: [PATCH] Initial commit. --- .gitignore | 5 + Code-of-Conduct.md | 49 ++ contributing.md => Contributing.md | 14 +- ISSUE_TEMPLATE/bug_report.md | 23 - ISSUE_TEMPLATE/feature_request.md | 16 - LICENSE | 177 +++++ PULL_REQUEST_TEMPLATE.md | 3 - README.md | 86 ++- go.mod | 45 ++ go.sum | 629 ++++++++++++++++++ rds/config/config.go | 176 +++++ rds/config/config_test.go | 137 ++++ rds/config/testdata/invalid_api_endpoint.yaml | 23 + .../invalid_missing_athenz_domain.yaml | 20 + rds/config/testdata/invalid_missing_host.yaml | 20 + .../testdata/invalid_missing_iam_role.yaml | 20 + .../testdata/invalid_missing_region.yaml | 20 + .../invalid_missing_ssl_root_cert.yaml | 20 + rds/config/testdata/invalid_missing_user.yaml | 20 + rds/config/testdata/invalid_port.yaml | 22 + .../testdata/invalid_renew_threshold.yaml | 22 + rds/config/testdata/valid.yaml | 21 + rds/conn_pool_mgr_mock_test.go | 83 +++ rds/connector.go | 148 +++++ rds/connector_test.go | 330 +++++++++ rds/credentials_creator_mock_test.go | 51 ++ rds/driver_mock_test.go | 50 ++ rds/gen.sh | 33 + rds/generate.go | 6 + rds/storage.go | 103 +++ rds/storage_creator_mock_test.go | 50 ++ rds/storage_mock_test.go | 373 +++++++++++ rds/storage_test.go | 121 ++++ rds/tools.go | 13 + 34 files changed, 2882 insertions(+), 47 deletions(-) create mode 100644 .gitignore create mode 100644 Code-of-Conduct.md rename contributing.md => Contributing.md (72%) delete mode 100644 ISSUE_TEMPLATE/bug_report.md delete mode 100644 ISSUE_TEMPLATE/feature_request.md create mode 100644 LICENSE delete mode 100644 PULL_REQUEST_TEMPLATE.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 rds/config/config.go create mode 100644 rds/config/config_test.go create mode 100644 rds/config/testdata/invalid_api_endpoint.yaml create mode 100644 rds/config/testdata/invalid_missing_athenz_domain.yaml create mode 100644 rds/config/testdata/invalid_missing_host.yaml create mode 100644 rds/config/testdata/invalid_missing_iam_role.yaml create mode 100644 rds/config/testdata/invalid_missing_region.yaml create mode 100644 rds/config/testdata/invalid_missing_ssl_root_cert.yaml create mode 100644 rds/config/testdata/invalid_missing_user.yaml create mode 100644 rds/config/testdata/invalid_port.yaml create mode 100644 rds/config/testdata/invalid_renew_threshold.yaml create mode 100644 rds/config/testdata/valid.yaml create mode 100644 rds/conn_pool_mgr_mock_test.go create mode 100644 rds/connector.go create mode 100644 rds/connector_test.go create mode 100644 rds/credentials_creator_mock_test.go create mode 100644 rds/driver_mock_test.go create mode 100755 rds/gen.sh create mode 100644 rds/generate.go create mode 100644 rds/storage.go create mode 100644 rds/storage_creator_mock_test.go create mode 100644 rds/storage_mock_test.go create mode 100644 rds/storage_test.go create mode 100644 rds/tools.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cc87c85 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# Copyright Yahoo 2021 +# Licensed under the terms of the Apache License 2.0. +# See LICENSE file in project root for terms. +rds/bin/mockgen +.idea/ diff --git a/Code-of-Conduct.md b/Code-of-Conduct.md new file mode 100644 index 0000000..6f088e3 --- /dev/null +++ b/Code-of-Conduct.md @@ -0,0 +1,49 @@ +# Yahoo Open Source Code of Conduct + +## Summary + +This Code of Conduct is our way to encourage good behavior and discourage bad behavior in our open source projects. We invite participation from many people to bring different perspectives to our projects. We will do our part to foster a welcoming and professional environment free of harassment. We expect participants to communicate professionally and thoughtfully during their involvement with this project. Participants may lose their good standing by engaging in misconduct. For example: insulting, threatening, or conveying unwelcome sexual content. + +This code does not replace the terms of service or acceptable use policies of the websites used to support this project. We acknowledge that participants may be subject to additional conduct terms based on their employment which may govern their online expressions. + +## Details + +This Code of Conduct makes our expectations of participants in this community explicit. + +* We forbid harassment and abusive speech within this community. +* We request participants to report misconduct to the project’s Response Team. +* We urge participants to refrain from using discussion forums to play out a fight. + +### Expected Behaviors + +We expect participants in this community to conduct themselves professionally. Since our primary mode of communication is text on an online forum (e.g. issues, pull requests, comments, emails, or chats) devoid of vocal tone, gestures, or other context that is often vital to understanding, it is important that participants are attentive to their interaction style. + +* **Assume positive intent.** We ask community members to assume positive intent on the part of other people’s communications. We may disagree on details, but we expect all suggestions to be supportive of the community goals. +* **Respect participants.** We expect occasional disagreements. Open Source projects are learning experiences. Ask, explore, challenge, and then _respectfully_ state if you agree or disagree. If your idea is rejected, be more persuasive not bitter. +* **Welcoming to new members.** New members bring new perspectives. Some ask questions that have been addressed before. _Kindly_ point to existing discussions. Everyone is new to every project once. +* **Be kind to beginners.** Beginners use open source projects to get experience. They might not be talented coders yet, and projects should not accept poor quality code. But we were all beginners once, and we need to engage kindly. +* **Consider your impact on others.** Your work will be used by others, and you depend on the work of others. We expect community members to be considerate and establish a balance their self-interest with communal interest. +* **Use words carefully.** We may not understand intent when you say something ironic. Often, people will misinterpret sarcasm in online communications. We ask community members to communicate plainly. +* **Leave with class.** When you wish to resign from participating in this project for any reason, you are free to fork the code and create a competitive project. Open Source explicitly allows this. Your exit should not be dramatic or bitter. + +### Unacceptable Behaviors + +Participants remain in good standing when they do not engage in misconduct or harassment (some examples follow). We do not list all forms of harassment, nor imply some forms of harassment are not worthy of action. Any participant who *feels* harassed or *observes* harassment, should report the incident to the Response Team. + +* **Don't be a bigot.** Calling out project members by their identity or background in a negative or insulting manner. This includes, but is not limited to, slurs or insinuations related to protected or suspect classes e.g. race, color, citizenship, national origin, political belief, religion, sexual orientation, gender identity and expression, age, size, culture, ethnicity, genetic features, language, profession, national minority status, mental or physical ability. +* **Don't insult.** Insulting remarks about a person’s lifestyle practices. +* **Don't dox.** Revealing private information about other participants without explicit permission. +* **Don't intimidate.** Threats of violence or intimidation of any project member. +* **Don't creep.** Unwanted sexual attention or content unsuited for the subject of this project. +* **Don't inflame.** We ask that victim of harassment not address their grievances in the public forum, as this often intensifies the problem. Report it, and let us address it off-line. +* **Don't disrupt.** Sustained disruptions in a discussion. + +### Scope + +Yahoo will assign a Response Team member with admin rights on the project and legal rights on the project copyright. The Response Team is empowered to restrict some privileges to the project as needed. Since this project is governed by an open source license, any participant may fork the code under the terms of the project license. The Response Team’s goal is to preserve the project if possible, and will restrict or remove participation from those who disrupt the project. + +This code does not replace the terms of service or acceptable use policies that are provided by the websites used to support this community. Nor does this code apply to communications or actions that take place outside of the context of this community. Many participants in this project are also subject to codes of conduct based on their employment. This code is a social-contract that informs participants of our social expectations. It is not a terms of service or legal contract. + +## License and Acknowledgment + +This text is shared under the [CC-BY-4.0 license](https://creativecommons.org/licenses/by/4.0/). This code is based on a study conducted by the [TODO Group](https://todogroup.org/) of many codes used in the open source community. If you have feedback about this code, contact our Response Team at the address listed above. diff --git a/contributing.md b/Contributing.md similarity index 72% rename from contributing.md rename to Contributing.md index 8f85642..7e9594c 100644 --- a/contributing.md +++ b/Contributing.md @@ -1,15 +1,17 @@ # How to contribute + First, thanks for taking the time to contribute to our project! There are many ways you can help out. -### Questions +## Questions If you have a question that needs an answer, [create an issue](https://help.github.com/articles/creating-an-issue/), and label it as a question. -### Issues for bugs or feature requests +## Issues for bugs or feature requests If you encounter any bugs in the code, or want to request a new feature or enhancement, please [create an issue](https://help.github.com/articles/creating-an-issue/) to report it. Kindly add a label to indicate what type of issue it is. -### Contribute Code +## Contribute Code + We welcome your pull requests for bug fixes. To implement something new, please create an issue first so we can discuss it together. ***Creating a Pull Request*** @@ -17,6 +19,10 @@ Please follow [best practices](https://github.com/trein/dev-best-practices/wiki/ When your code is ready to be submitted, [submit a pull request](https://help.github.com/articles/creating-a-pull-request/) to begin the code review process. -We only seek to accept code that you are authorized to contribute to the project. We have added a pull request template on our projects so that your contributions are made with the following confirmation: +We only seek to accept code that you are authorized to contribute to the project. We have added a pull request template on our projects so that your contributions are made with the following confirmation: > I confirm that this contribution is made under the terms of the license found in the root directory of this repository's source tree and that I have the authority necessary to make this contribution on behalf of its copyright owner. + +## Code of Conduct + +We encourage an inclusive and professional interactions on our project. We welcome everyone to open an issue, improve the documentation, report bug or submit a pull request. By participating in this project, you agree to abide by the [Yahoo Code of Conduct](Code-of-Conduct.md). If you feel there is a conduct issue related to this project, please raise it per the Code of Conduct process and we will address it. diff --git a/ISSUE_TEMPLATE/bug_report.md b/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 5ff14a1..0000000 --- a/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: "[BUG]" -labels: '' -assignees: '' - ---- - -**Describe the bug** - -**Steps to reproduce the behavior:** -1. Starting with '...' -2. Then doing '....' -3. Detecting the error '....' - -**Expected behavior** - -**Screenshots, if applicable** - -**Environment (Hardware / Software and Versions):** - -**Anything else** diff --git a/ISSUE_TEMPLATE/feature_request.md b/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 8419380..0000000 --- a/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: "[Feature]" -labels: '' -assignees: '' - ---- - -**Describe the solution you'd like** - -**If your feature request is related to a problem, please describe.** - -**What alternatives have you considered** - -**Anything else** diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 27ff1a7..0000000 --- a/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,3 +0,0 @@ - - -I confirm that this contribution is made under the terms of the license found in the root directory of this repository's source tree and that I have the authority necessary to make this contribution on behalf of its copyright owner. diff --git a/README.md b/README.md index 8808e40..85c3c6a 100644 --- a/README.md +++ b/README.md @@ -1 +1,85 @@ -This .github repository contains default files that apply to the projects in this organization. +# grafeas-rds + +> AWS RDS backend for Grafeas. This library can periodically refresh the IAM authentication token which is used as the password to connect to an AWS RDS service. + +## Table of Contents + +- [Background](#background) +- [Install](#install) +- [Usage](#usage) +- [Configuration](#configuration) +- [Contribute](#contribute) +- [License](#license) + +## Background + +[Grafeas](https://github.com/grafeas/grafeas) supports pluggable [storage backends](https://github.com/grafeas/grafeas#storage-backends), +and AWS RDS can be one of the options. +Furthermore, AWS RDS supports [IAM-based authentication](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/UsingWithRDS.IAMDBAuth.html), +which eliminates the needs to maintain a password, +including storing it, fetching it from the application, and rotating it periodically, etc. +However, the official documentation also states the following: + +> Each token has a lifetime of 15 minutes. + +As a result, we need a mechanism to refresh the token, hence this project. + +## Install + +This project is intended to be used as a library. + +Import `git.vzbuilders.com/theparanoids/grafeas-rds/rds` to use it. + +Note that the Go version has to be >= `1.17` (see [go.mod](go.mod)). + +## Usage + +If the underlying database were PostgreSQL, the code would look like this: + +```go +import ( + "log" + + "github.com/theparanoids/grafeas-rds/rds" + "github.com/grafeas/grafeas/go/v1beta1/storage" + "github.com/lib/pq" +) + +func main() { + provider := rds.NewGrafeasStorageProvider( + &pq.Driver{}, + YourCredentialsCreator{}, + YourStorageCreator{}, + ) + if err := storage.RegisterStorageTypeProvider("rds_postgres", provider.Provide); err != nil { + log.Fatalf("Error registering rds pgsql provider, %s", err) + } + // Set up and start the Grafeas server... +} +``` + +### Usage Notes + +- Currently the configuration passed to `CredentialsCreator.Create` contains only + [Athenz](https://github.com/AthenZ/athenz)-related fields; + we welcome contributions to add support for any other mechanism. +- Regarding `StorageCreator`, + we have an internal implementation to create a [grafeas-pqsql](https://github.com/grafeas/grafeas-pgsql) storage + given a custom `driver.Connector`, + and are actively working on upstreaming it. + +## Configuration + +A valid configuration file can be found [here](rds/config/testdata/valid.yaml); +it can be directly plugged into a configuration file for Grafeas server. + +Some default values are also provided in [`config.go`](rds/config/config.go). + +## Contribute + +Please refer to [Contributing.md](Contributing.md) for information about how to get involved. +We welcome issues, questions, and pull requests. + +## License + +This project is licensed under the terms of the [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) open source license. Please refer to [LICENSE](LICENSE) for the full terms. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a2e2dfa --- /dev/null +++ b/go.mod @@ -0,0 +1,45 @@ +// Copyright Yahoo 2021 +// Licensed under the terms of the Apache License 2.0. +// See LICENSE file in project root for terms. +module github.com/theparanoids/grafeas-rds + +go 1.17 + +require ( + github.com/aws/aws-sdk-go v1.40.51 + github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/golang/mock v1.6.0 + github.com/grafeas/grafeas v0.1.7-0.20210928191636-ff616c8055fd + golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b + google.golang.org/genproto v0.0.0-20210524171403-669157292da3 // indirect + google.golang.org/protobuf v1.27.1 +) + +require ( + github.com/boltdb/bolt v1.3.1 // indirect + github.com/fernet/fernet-go v0.0.0-20191111064656-eff2850e6001 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-cmp v0.5.5 // indirect + github.com/google/uuid v1.1.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.0.1 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/lib/pq v1.8.0 // indirect + github.com/magiconair/properties v1.8.1 // indirect + github.com/mitchellh/mapstructure v1.3.2 // indirect + github.com/pelletier/go-toml v1.8.0 // indirect + github.com/spf13/afero v1.3.2 // indirect + github.com/spf13/cast v1.3.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.7.1 // indirect + github.com/subosito/gotenv v1.2.0 // indirect + golang.org/x/mod v0.4.2 // indirect + golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect + golang.org/x/text v0.3.6 // indirect + golang.org/x/tools v0.1.1 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/grpc v1.36.1 // indirect + gopkg.in/ini.v1 v1.57.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7e7d6a8 --- /dev/null +++ b/go.sum @@ -0,0 +1,629 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.63.0/go.mod h1:GmezbQc7T2snqkEXWfZ0sy0VfkB/ivI2DdtJL2DEmlg= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Bowery/prompt v0.0.0-20190916142128-fa8279994f75/go.mod h1:4/6eNcqZ09BZ9wLK3tZOjBA1nDj+B0728nlX5YRlSmQ= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr/antlr4 v0.0.0-20201029161626-9a95f0cc3d7c/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aws/aws-sdk-go v1.40.51 h1:FfxDcjWqhMGwy+raf5Zf6AH8qsHIl9YG2dvJIBx1Aw4= +github.com/aws/aws-sdk-go v1.40.51/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/cmux v0.0.0-20170110192607-30d10be49292/go.mod h1:qRiX68mZX1lGBkTWyp3CLcenw9I94W2dLeRvMzcn9N4= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dchest/safefile v0.0.0-20151022103144-855e8d98f185/go.mod h1:cFRxtTwTOJkz2x3rQUNCYKWC93yP1VKjR8NUhqFxZNU= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fernet/fernet-go v0.0.0-20191111064656-eff2850e6001 h1:/UMxx5lGDg30aioUL9e7xJnbJfJeX7vhcm57fa5udaI= +github.com/fernet/fernet-go v0.0.0-20191111064656-eff2850e6001/go.mod h1:2H9hjfbpSMHwY503FclkV/lZTBh2YlOmLLSda12uL8c= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/logger v1.1.0/go.mod h1:w7O8nrRr0xufejBlQMI83MXqRusvREoJdaAxV+CoAB4= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grafeas/grafeas v0.1.7-0.20210928191636-ff616c8055fd h1:ImQi/Q6my+iZUlyV+sMT1EP20oHeFUQIWf/F7GLEILU= +github.com/grafeas/grafeas v0.1.7-0.20210928191636-ff616c8055fd/go.mod h1:Pput/pZJun9a6Uzmvadd8lWmNBC7HLkT7lWb/vwCQls= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.0.1 h1:X2vfSnm1WC8HEo0MBHZg2TcuDUHJj6kd1TmEAQncnSA= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.0.1/go.mod h1:oVMjMN64nzEcepv1kdZKgx1qNYt4Ro0Gqefiq2JWdis= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kardianos/govendor v1.0.9/go.mod h1:yvmR6q9ZZ7nSF5Wvh40v0wfP+3TwwL8zYQp+itoZSVM= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg= +github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.3.2 h1:GDarE4TJQI52kYSbSAmLiId1Elfj+xgSDqrUZxFhxlU= +github.com/spf13/afero v1.3.2/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= +github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b h1:eB48h3HiRycXNy8E0Gf5e0hv7YT6Kt14L/D73G1fuwo= +golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200806022845-90696ccdc692/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1 h1:wGiQel/hW0NnEkJUk8lbzkX2gFJU6PFxf1v5OlCfuOs= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201113130914-ce600e9a6f9e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210524171403-669157292da3 h1:xFyh6GBb+NO1L0xqb978I3sBPQpk6FrKO0jJGRvdj/0= +google.golang.org/genproto v0.0.0-20210524171403-669157292da3/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.36.1 h1:cmUfbeGKnz9+2DD/UYsMQXeqbHZqZDs4eQwW0sFOpBY= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/grpc/examples v0.0.0-20201112215255-90f1b3ee835b/go.mod h1:IBqQ7wSUJ2Ep09a8rMWFsg4fmI2r38zwsq8a0GgxXpM= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= +gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/rds/config/config.go b/rds/config/config.go new file mode 100644 index 0000000..23db58f --- /dev/null +++ b/rds/config/config.go @@ -0,0 +1,176 @@ +// Copyright Yahoo 2021 +// Licensed under the terms of the Apache License 2.0. +// See LICENSE file in project root for terms. +package config + +import ( + "fmt" + "net/url" + + "github.com/grafeas/grafeas/go/config" +) + +const emptyFieldErrTemplate = `invalid field: "%s" must not be empty` + +// default values for Config +const ( + defaultPort = 5432 + defaultDBName = "grafeas" + defaultSSLMode = "verify-full" +) + +// Config is the configuration for PostgreSQL store. +// json tags are required because +// config.ConvertGenericConfigToSpecificType internally uses json package. +type Config struct { + Host string `json:"host"` + Port int `json:"port"` + // For rds_prostgres, DBName has to alrady exist and can be accessed by User. + DBName string `json:"db_name"` + User string `json:"user"` + Password string `json:"password"` + // Valid sslmodes: disable, allow, prefer, require, verify-ca, verify-full. + // See https://www.postgresql.org/docs/current/static/libpq-connect.html for details + SSLMode string `json:"ssl_mode"` + SSLRootCert string `json:"ssl_root_cert"` + // PaginationKey is a 32-bit URL-safe base64 key used to encrypt pagination tokens. + // Check the underlying DB implementation to see it's supported. + // Regarding PostgreSQL, if one is not provided, it will be generated [1]. + // Multiple grafeas instances in the same cluster must share the same value. + // + // [1] https://github.com/grafeas/grafeas-pgsql + PaginationKey string `json:"pagination_key"` + + ConnPool ConnPoolConfig `json:"conn_pool"` + + // IAMAuth is only used when Password is empty. + IAMAuth IAMAuthConfig `json:"iam_auth"` +} + +func New(ci *config.StorageConfiguration) (*Config, error) { + var c Config + + err := config.ConvertGenericConfigToSpecificType(ci, &c) + if err != nil { + return nil, fmt.Errorf("failed to convert the generic storage config to a rds config, err: %v", err) + } + c.populateDefaultValues() + + if err := c.validate(); err != nil { + return nil, err + } + return &c, nil +} + +func (c *Config) populateDefaultValues() { + if c.Port == 0 { + c.Port = defaultPort + } + if c.DBName == "" { + c.DBName = defaultDBName + } + if c.SSLMode == "" { + c.SSLMode = defaultSSLMode + } + c.IAMAuth.populateDefaultValues() +} + +func (c *Config) validate() error { + if c.Host == "" { + return fmt.Errorf(emptyFieldErrTemplate, "Config.Host") + } + if c.Port <= 0 { + return fmt.Errorf(`invalid field: "Config.Port" must be larger than zero, got %v`, c.Port) + } + if c.User == "" { + return fmt.Errorf(emptyFieldErrTemplate, "Config.User") + } + if c.SSLRootCert == "" && (c.SSLMode == "verify-ca" || c.SSLMode == "verify-full") { + return fmt.Errorf(`invalid field: Config.SSLRootCert must not be empty because SSLMode is %s`, c.SSLMode) + } + if c.Password != "" { + return nil + } + // The password is empty, so IAMAuth must be valid. + return c.IAMAuth.validate() +} + +// ConnPoolConfig contains the configuration related to connection pool management. +// The explanation of each field can be found in the following functions in sql package: +// +// - SetMaxOpenConns +// - SetMaxIdleConns +// - SetConnMaxLifetime +// - SetConnMaxIdleTime +// +// Default values are not provided for these fields because +// 0 is the zero value of `int`, but it's also a valid value for these fields. +type ConnPoolConfig struct { + MaxOpenConns int `json:"max_open_conns"` + MaxIdleConns int `json:"max_idle_conns"` + ConnMaxLifetimeInSeconds int `json:"conn_max_lifetime_in_seconds"` + ConnMaxIdleTimeInSeconds int `json:"conn_max_idle_time_in_seconds"` +} + +// IAMAuthConfig contains configuration required to +// get a temporary DB password (i.e. token) from AWS API. +type IAMAuthConfig struct { + // Region refers to the AWS region in which the DB resides. + Region string `json:"region"` + // CredentialsProvider specifies how to configure the AWS credentials provider. + CredentialsProvider ZTSCredentialProviderConfig `json:"credentials_provider"` +} + +func (c *IAMAuthConfig) populateDefaultValues() { + c.CredentialsProvider.populateDefaultValues() +} + +func (c *IAMAuthConfig) validate() error { + if c.Region == "" { + return fmt.Errorf(emptyFieldErrTemplate, "IAMAuthConfig.Region") + } + return c.CredentialsProvider.validate() +} + +// default values for ZTSCredentialProviderConfig +const ( + defaultRenewThresholdInSeconds = 600 +) + +// ZTSCredentialProviderConfig stores the configurations for configuring ZTSCredentialsProvider. +type ZTSCredentialProviderConfig struct { + // APIEndpoint is the endpoint for requesting AWS temporary credentials. + APIEndpoint string `json:"api_endpoint"` + // AthenzDomain is the Athenz domain associated with the AWS account. + AthenzDomain string `json:"athenz_domain"` + // IAMRole is the AWS IAM role who has access to the DB. + IAMRole string `json:"iam_role"` + // ExternalID refers to the one defined in AWS documentation. + // More info: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html + ExternalID string `json:"external_id"` + // RenewThresholdInSeconds defines the time period to refresh the credentials before it is expired. + RenewThresholdInSeconds int `json:"renew_threshold_in_seconds"` +} + +func (c *ZTSCredentialProviderConfig) populateDefaultValues() { + if c.RenewThresholdInSeconds == 0 { + c.RenewThresholdInSeconds = defaultRenewThresholdInSeconds + } +} + +func (c *ZTSCredentialProviderConfig) validate() error { + if _, err := url.Parse(c.APIEndpoint); err != nil { + return fmt.Errorf(`invalid field: "ZTSCredentialProviderConfig.APIEndpoint" should be a valid url, err: %v`, err) + } + if c.AthenzDomain == "" { + return fmt.Errorf(emptyFieldErrTemplate, "ZTSCredentialProviderConfig.AthenzDomain") + } + if c.IAMRole == "" { + return fmt.Errorf(emptyFieldErrTemplate, "ZTSCredentialProviderConfig.IAMRole") + } + if c.RenewThresholdInSeconds <= 0 { + return fmt.Errorf(`invalid field: "ZTSCredentialProviderConfig.RenewThreshold" must be greater than 0, got %v`, + c.RenewThresholdInSeconds) + } + return nil +} diff --git a/rds/config/config_test.go b/rds/config/config_test.go new file mode 100644 index 0000000..9d299d3 --- /dev/null +++ b/rds/config/config_test.go @@ -0,0 +1,137 @@ +// Copyright Yahoo 2021 +// Licensed under the terms of the Apache License 2.0. +// See LICENSE file in project root for terms. +package config + +import ( + "fmt" + "log" + "path" + "reflect" + "strings" + "testing" + + "github.com/grafeas/grafeas/go/config" +) + +func TestNewConfig(t *testing.T) { + t.Parallel() + + tests := []struct { + file string + wantErrMsg string + wantConfig Config + }{ + { + file: "valid.yaml", + wantConfig: Config{ + Host: "some-host.rds.amazonaws.com", + Port: defaultPort, + DBName: defaultDBName, + User: "grafeas_rw", + SSLMode: defaultSSLMode, + SSLRootCert: "/opt/rds-ca-2019-root.pem", + PaginationKey: "some_random_key", + ConnPool: ConnPoolConfig{ + MaxOpenConns: 50, + MaxIdleConns: 25, + ConnMaxLifetimeInSeconds: 1800, + ConnMaxIdleTimeInSeconds: 900, + }, + IAMAuth: IAMAuthConfig{ + Region: "us-west-2", + CredentialsProvider: ZTSCredentialProviderConfig{ + APIEndpoint: "https://zts.athenz.company.com:4443/zts/v1", + AthenzDomain: "grafeas", + IAMRole: "some-role.grafeas", + RenewThresholdInSeconds: defaultRenewThresholdInSeconds, + }, + }, + }, + }, + { + file: "invalid_api_endpoint.yaml", + wantErrMsg: `invalid field: "ZTSCredentialProviderConfig.APIEndpoint" should be a valid url`, + }, + { + file: "invalid_missing_athenz_domain.yaml", + wantErrMsg: fmt.Sprintf(emptyFieldErrTemplate, "ZTSCredentialProviderConfig.AthenzDomain"), + }, + { + file: "invalid_missing_host.yaml", + wantErrMsg: fmt.Sprintf(emptyFieldErrTemplate, "Config.Host"), + }, + { + file: "invalid_missing_user.yaml", + wantErrMsg: fmt.Sprintf(emptyFieldErrTemplate, "Config.User"), + }, + { + file: "invalid_missing_iam_role.yaml", + wantErrMsg: fmt.Sprintf(emptyFieldErrTemplate, "ZTSCredentialProviderConfig.IAMRole"), + }, + { + file: "invalid_missing_region.yaml", + wantErrMsg: fmt.Sprintf(emptyFieldErrTemplate, "IAMAuthConfig.Region"), + }, + { + file: "invalid_missing_ssl_root_cert.yaml", + wantErrMsg: `invalid field: Config.SSLRootCert must not be empty because SSLMode is`, + }, + { + file: "invalid_port.yaml", + wantErrMsg: `invalid field: "Config.Port" must be larger than zero, got`, + }, + { + file: "invalid_renew_threshold.yaml", + wantErrMsg: `invalid field: "ZTSCredentialProviderConfig.RenewThreshold" must be greater than 0, got`, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.file, func(t *testing.T) { + t.Parallel() + + // We simulate how Grafeas loads the config instead of coining our own loading logic because: + // 1. We want to use YAML for testing config files because Grafeas uses YAML for config files, + // but we don't want to add YAML tags by duplicating the existing JSON tags. + // 2. We can make sure that a tested config file can be directly plugged into a Grafeas config, + // and it will work transparently. + gc, err := config.LoadConfig(path.Join("testdata", tt.file)) + if err != nil { + log.Fatalf("failed to load config: %v", err) + } + c, err := New(gc.StorageConfig) + if (err != nil) != (tt.wantErrMsg != "") { + if err != nil { + t.Errorf("don't want error, but got %q", err) + } else { + t.Errorf("got nil error, but want error to include %q", tt.wantErrMsg) + } + return + } + if err != nil { + if !strings.Contains(err.Error(), tt.wantErrMsg) { + t.Errorf("want %q to include %q", err.Error(), tt.wantErrMsg) + } + return + } + if !reflect.DeepEqual(tt.wantConfig, *c) { + t.Errorf("config mismatch, want %v, got %v", tt.wantConfig, *c) + } + }) + } + t.Run("failed to convert config", func(t *testing.T) { + t.Parallel() + + wantErrMsg := "failed to convert the generic storage config to a rds config" + invalidConf := config.StorageConfiguration(make(chan struct{})) + _, err := New(&invalidConf) + if err == nil { + t.Fatalf("got nil error, but want error to be %q", wantErrMsg) + } + gotErrMsg := err.Error() + if !strings.Contains(gotErrMsg, wantErrMsg) { + t.Errorf("want %q to include %q", gotErrMsg, wantErrMsg) + } + }) +} diff --git a/rds/config/testdata/invalid_api_endpoint.yaml b/rds/config/testdata/invalid_api_endpoint.yaml new file mode 100644 index 0000000..b697c58 --- /dev/null +++ b/rds/config/testdata/invalid_api_endpoint.yaml @@ -0,0 +1,23 @@ +# Copyright Yahoo 2021 +# Licensed under the terms of the Apache License 2.0. +# See LICENSE file in project root for terms. +grafeas: + storage_type: "rds" + rds: + host: "some-host.rds.amazonaws.com" + user: "grafeas_rw" + ssl_root_cert: "/opt/rds-ca-2019-root.pem" + pagination_key: "some_random_key" + conn_pool: + max_open_conns: 50 + max_idle_conns: 25 + conn_max_lifetime_in_seconds: 1800 + conn_max_idle_time_in_seconds: 900 + iam_auth: + region: "us-west-2" + credentials_provider: + # Control characters are not allowed in an URL, so we add a newline here. + api_endpoint: | + "" + athenz_domain: "grafeas" + iam_role: "some-role.grafeas" diff --git a/rds/config/testdata/invalid_missing_athenz_domain.yaml b/rds/config/testdata/invalid_missing_athenz_domain.yaml new file mode 100644 index 0000000..f630fac --- /dev/null +++ b/rds/config/testdata/invalid_missing_athenz_domain.yaml @@ -0,0 +1,20 @@ +# Copyright Yahoo 2021 +# Licensed under the terms of the Apache License 2.0. +# See LICENSE file in project root for terms. +grafeas: + storage_type: "rds" + rds: + host: "some-host.rds.amazonaws.com" + user: "grafeas_rw" + ssl_root_cert: "/opt/rds-ca-2019-root.pem" + pagination_key: "some_random_key" + conn_pool: + max_open_conns: 50 + max_idle_conns: 25 + conn_max_lifetime_in_seconds: 1800 + conn_max_idle_time_in_seconds: 900 + iam_auth: + region: "us-west-2" + credentials_provider: + api_endpoint: "https://zts.athenz.company.com:4443/zts/v1" + iam_role: "some-role.grafeas" diff --git a/rds/config/testdata/invalid_missing_host.yaml b/rds/config/testdata/invalid_missing_host.yaml new file mode 100644 index 0000000..352ef25 --- /dev/null +++ b/rds/config/testdata/invalid_missing_host.yaml @@ -0,0 +1,20 @@ +# Copyright Yahoo 2021 +# Licensed under the terms of the Apache License 2.0. +# See LICENSE file in project root for terms. +grafeas: + storage_type: "rds" + rds: + user: "grafeas_rw" + ssl_root_cert: "/opt/rds-ca-2019-root.pem" + pagination_key: "some_random_key" + conn_pool: + max_open_conns: 50 + max_idle_conns: 25 + conn_max_lifetime_in_seconds: 1800 + conn_max_idle_time_in_seconds: 900 + iam_auth: + region: "us-west-2" + credentials_provider: + api_endpoint: "https://zts.athenz.company.com:4443/zts/v1" + athenz_domain: "grafeas" + iam_role: "some-role.grafeas" diff --git a/rds/config/testdata/invalid_missing_iam_role.yaml b/rds/config/testdata/invalid_missing_iam_role.yaml new file mode 100644 index 0000000..c8cb0cb --- /dev/null +++ b/rds/config/testdata/invalid_missing_iam_role.yaml @@ -0,0 +1,20 @@ +# Copyright Yahoo 2021 +# Licensed under the terms of the Apache License 2.0. +# See LICENSE file in project root for terms. +grafeas: + storage_type: "rds" + rds: + host: "some-host.rds.amazonaws.com" + user: "grafeas_rw" + ssl_root_cert: "/opt/rds-ca-2019-root.pem" + pagination_key: "some_random_key" + conn_pool: + max_open_conns: 50 + max_idle_conns: 25 + conn_max_lifetime_in_seconds: 1800 + conn_max_idle_time_in_seconds: 900 + iam_auth: + region: "us-west-2" + credentials_provider: + api_endpoint: "https://zts.athenz.company.com:4443/zts/v1" + athenz_domain: "grafeas" diff --git a/rds/config/testdata/invalid_missing_region.yaml b/rds/config/testdata/invalid_missing_region.yaml new file mode 100644 index 0000000..2c338f3 --- /dev/null +++ b/rds/config/testdata/invalid_missing_region.yaml @@ -0,0 +1,20 @@ +# Copyright Yahoo 2021 +# Licensed under the terms of the Apache License 2.0. +# See LICENSE file in project root for terms. +grafeas: + storage_type: "rds" + rds: + host: "some-host.rds.amazonaws.com" + user: "grafeas_rw" + ssl_root_cert: "/opt/rds-ca-2019-root.pem" + pagination_key: "some_random_key" + conn_pool: + max_open_conns: 50 + max_idle_conns: 25 + conn_max_lifetime_in_seconds: 1800 + conn_max_idle_time_in_seconds: 900 + iam_auth: + credentials_provider: + api_endpoint: "https://zts.athenz.company.com:4443/zts/v1" + athenz_domain: "grafeas" + iam_role: "some-role.grafeas" diff --git a/rds/config/testdata/invalid_missing_ssl_root_cert.yaml b/rds/config/testdata/invalid_missing_ssl_root_cert.yaml new file mode 100644 index 0000000..b9b7d5d --- /dev/null +++ b/rds/config/testdata/invalid_missing_ssl_root_cert.yaml @@ -0,0 +1,20 @@ +# Copyright Yahoo 2021 +# Licensed under the terms of the Apache License 2.0. +# See LICENSE file in project root for terms. +grafeas: + storage_type: "rds" + rds: + host: "some-host.rds.amazonaws.com" + user: "grafeas_rw" + pagination_key: "some_random_key" + conn_pool: + max_open_conns: 50 + max_idle_conns: 25 + conn_max_lifetime_in_seconds: 1800 + conn_max_idle_time_in_seconds: 900 + iam_auth: + region: "us-west-2" + credentials_provider: + api_endpoint: "https://zts.athenz.company.com:4443/zts/v1" + athenz_domain: "grafeas" + iam_role: "some-role.grafeas" diff --git a/rds/config/testdata/invalid_missing_user.yaml b/rds/config/testdata/invalid_missing_user.yaml new file mode 100644 index 0000000..24eb519 --- /dev/null +++ b/rds/config/testdata/invalid_missing_user.yaml @@ -0,0 +1,20 @@ +# Copyright Yahoo 2021 +# Licensed under the terms of the Apache License 2.0. +# See LICENSE file in project root for terms. +grafeas: + storage_type: "rds" + rds: + host: "some-host.rds.amazonaws.com" + ssl_root_cert: "/opt/rds-ca-2019-root.pem" + pagination_key: "some_random_key" + conn_pool: + max_open_conns: 50 + max_idle_conns: 25 + conn_max_lifetime_in_seconds: 1800 + conn_max_idle_time_in_seconds: 900 + iam_auth: + region: "us-west-2" + credentials_provider: + api_endpoint: "https://zts.athenz.company.com:4443/zts/v1" + athenz_domain: "grafeas" + iam_role: "some-role.grafeas" diff --git a/rds/config/testdata/invalid_port.yaml b/rds/config/testdata/invalid_port.yaml new file mode 100644 index 0000000..f89fc6c --- /dev/null +++ b/rds/config/testdata/invalid_port.yaml @@ -0,0 +1,22 @@ +# Copyright Yahoo 2021 +# Licensed under the terms of the Apache License 2.0. +# See LICENSE file in project root for terms. +grafeas: + storage_type: "rds" + rds: + host: "some-host.rds.amazonaws.com" + port: -1 + user: "grafeas_rw" + ssl_root_cert: "/opt/rds-ca-2019-root.pem" + pagination_key: "some_random_key" + conn_pool: + max_open_conns: 50 + max_idle_conns: 25 + conn_max_lifetime_in_seconds: 1800 + conn_max_idle_time_in_seconds: 900 + iam_auth: + region: "us-west-2" + credentials_provider: + api_endpoint: "https://zts.athenz.company.com:4443/zts/v1" + athenz_domain: "grafeas" + iam_role: "some-role.grafeas" diff --git a/rds/config/testdata/invalid_renew_threshold.yaml b/rds/config/testdata/invalid_renew_threshold.yaml new file mode 100644 index 0000000..1c5e63e --- /dev/null +++ b/rds/config/testdata/invalid_renew_threshold.yaml @@ -0,0 +1,22 @@ +# Copyright Yahoo 2021 +# Licensed under the terms of the Apache License 2.0. +# See LICENSE file in project root for terms. +grafeas: + storage_type: "rds" + rds: + host: "some-host.rds.amazonaws.com" + user: "grafeas_rw" + ssl_root_cert: "/opt/rds-ca-2019-root.pem" + pagination_key: "some_random_key" + conn_pool: + max_open_conns: 50 + max_idle_conns: 25 + conn_max_lifetime_in_seconds: 1800 + conn_max_idle_time_in_seconds: 900 + iam_auth: + region: "us-west-2" + credentials_provider: + api_endpoint: "https://zts.athenz.company.com:4443/zts/v1" + athenz_domain: "grafeas" + iam_role: "some-role.grafeas" + renew_threshold_in_seconds: -1 diff --git a/rds/config/testdata/valid.yaml b/rds/config/testdata/valid.yaml new file mode 100644 index 0000000..686c978 --- /dev/null +++ b/rds/config/testdata/valid.yaml @@ -0,0 +1,21 @@ +# Copyright Yahoo 2021 +# Licensed under the terms of the Apache License 2.0. +# See LICENSE file in project root for terms. +grafeas: + storage_type: "rds" + rds: + host: "some-host.rds.amazonaws.com" + user: "grafeas_rw" + ssl_root_cert: "/opt/rds-ca-2019-root.pem" + pagination_key: "some_random_key" + conn_pool: + max_open_conns: 50 + max_idle_conns: 25 + conn_max_lifetime_in_seconds: 1800 + conn_max_idle_time_in_seconds: 900 + iam_auth: + region: "us-west-2" + credentials_provider: + api_endpoint: "https://zts.athenz.company.com:4443/zts/v1" + athenz_domain: "grafeas" + iam_role: "some-role.grafeas" diff --git a/rds/conn_pool_mgr_mock_test.go b/rds/conn_pool_mgr_mock_test.go new file mode 100644 index 0000000..85437b6 --- /dev/null +++ b/rds/conn_pool_mgr_mock_test.go @@ -0,0 +1,83 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/theparanoids/grafeas-rds/rds (interfaces: ConnPoolMgr) + +// Package rds is a generated GoMock package. +package rds + +import ( + reflect "reflect" + time "time" + + gomock "github.com/golang/mock/gomock" +) + +// MockConnPoolMgr is a mock of ConnPoolMgr interface. +type MockConnPoolMgr struct { + ctrl *gomock.Controller + recorder *MockConnPoolMgrMockRecorder +} + +// MockConnPoolMgrMockRecorder is the mock recorder for MockConnPoolMgr. +type MockConnPoolMgrMockRecorder struct { + mock *MockConnPoolMgr +} + +// NewMockConnPoolMgr creates a new mock instance. +func NewMockConnPoolMgr(ctrl *gomock.Controller) *MockConnPoolMgr { + mock := &MockConnPoolMgr{ctrl: ctrl} + mock.recorder = &MockConnPoolMgrMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockConnPoolMgr) EXPECT() *MockConnPoolMgrMockRecorder { + return m.recorder +} + +// SetConnMaxIdleTime mocks base method. +func (m *MockConnPoolMgr) SetConnMaxIdleTime(arg0 time.Duration) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetConnMaxIdleTime", arg0) +} + +// SetConnMaxIdleTime indicates an expected call of SetConnMaxIdleTime. +func (mr *MockConnPoolMgrMockRecorder) SetConnMaxIdleTime(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetConnMaxIdleTime", reflect.TypeOf((*MockConnPoolMgr)(nil).SetConnMaxIdleTime), arg0) +} + +// SetConnMaxLifetime mocks base method. +func (m *MockConnPoolMgr) SetConnMaxLifetime(arg0 time.Duration) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetConnMaxLifetime", arg0) +} + +// SetConnMaxLifetime indicates an expected call of SetConnMaxLifetime. +func (mr *MockConnPoolMgrMockRecorder) SetConnMaxLifetime(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetConnMaxLifetime", reflect.TypeOf((*MockConnPoolMgr)(nil).SetConnMaxLifetime), arg0) +} + +// SetMaxIdleConns mocks base method. +func (m *MockConnPoolMgr) SetMaxIdleConns(arg0 int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetMaxIdleConns", arg0) +} + +// SetMaxIdleConns indicates an expected call of SetMaxIdleConns. +func (mr *MockConnPoolMgrMockRecorder) SetMaxIdleConns(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMaxIdleConns", reflect.TypeOf((*MockConnPoolMgr)(nil).SetMaxIdleConns), arg0) +} + +// SetMaxOpenConns mocks base method. +func (m *MockConnPoolMgr) SetMaxOpenConns(arg0 int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetMaxOpenConns", arg0) +} + +// SetMaxOpenConns indicates an expected call of SetMaxOpenConns. +func (mr *MockConnPoolMgrMockRecorder) SetMaxOpenConns(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMaxOpenConns", reflect.TypeOf((*MockConnPoolMgr)(nil).SetMaxOpenConns), arg0) +} diff --git a/rds/connector.go b/rds/connector.go new file mode 100644 index 0000000..e1bff80 --- /dev/null +++ b/rds/connector.go @@ -0,0 +1,148 @@ +// Copyright Yahoo 2021 +// Licensed under the terms of the Apache License 2.0. +// See LICENSE file in project root for terms. +package rds + +import ( + "database/sql/driver" + "fmt" + "log" + "sync" + "time" + + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/service/rds/rdsutils" + "golang.org/x/net/context" + + "github.com/theparanoids/grafeas-rds/rds/config" +) + +const ( + // A temporary DB password requested via IAM auth is only valid for 15 minutes. + // Ref: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.Connecting.html + refreshAuthTokenInterval = 10 * time.Minute + + errMsgCreateCredentials = "failed to create AWS credentials" + errMsgRefreshAuthToken = "failed to refresh auth token" + errMsgSetupIAMAuth = "failed to set up IAM auth" + + logsOptInIAMAuth = "Opt in IAM Authentication..." +) + +// connector implements driver.Connector +// Reference implementation: sql.dsnConnector. +type connector struct { + host string + port int + dbName string + user string + password string + sslMode string + sslRootCert string + + driver driver.Driver + // dsn refers to data source name. + // Only this variable should be accessed concurrently. + dsn string + // reader: when a new connection to DB is needed. + // writer: when the AWS auth token is refreshed. + dsnLock sync.RWMutex +} + +func newConnector(ctx context.Context, conf *config.Config, driver driver.Driver, cc CredentialsCreator, logger *log.Logger) (*connector, error) { + c := &connector{ + host: conf.Host, + port: conf.Port, + dbName: conf.DBName, + user: conf.User, + password: conf.Password, + sslMode: conf.SSLMode, + sslRootCert: conf.SSLRootCert, + driver: driver, + } + if cc == nil { + c.updateDSN() + } else { + logger.Printf("%s", logsOptInIAMAuth) + if err := c.setupIAMAuth(ctx, conf.IAMAuth, cc, logger); err != nil { + return nil, fmt.Errorf("%s, err: %v", errMsgSetupIAMAuth, err) + } + } + return c, nil +} + +func (c *connector) Connect(context.Context) (driver.Conn, error) { + dsn := c.readDSN() + return c.driver.Open(dsn) +} + +func (c *connector) Driver() driver.Driver { + return c.driver +} + +func (c *connector) setupIAMAuth(ctx context.Context, conf config.IAMAuthConfig, cc CredentialsCreator, logger *log.Logger) error { + var err error + creds, err := cc.Create(conf) + if err != nil { + return fmt.Errorf("%s, err: %v", errMsgCreateCredentials, err) + } + if err := c.refreshAuthToken(creds, conf.Region); err != nil { + return fmt.Errorf("%s, err: %v", errMsgRefreshAuthToken, err) + } + go c.refreshAuthTokenPeriodically(ctx, creds, conf.Region, refreshAuthTokenInterval, logger) + return nil +} + +func (c *connector) refreshAuthToken(creds *credentials.Credentials, region string) error { + endpoint := fmt.Sprintf("%s:%d", c.host, c.port) + authToken, err := rdsutils.BuildAuthToken(endpoint, region, c.user, creds) + if err != nil { + return err + } + c.updatePassword(authToken) + return nil +} + +func (c *connector) refreshAuthTokenPeriodically(ctx context.Context, creds *credentials.Credentials, region string, interval time.Duration, logger *log.Logger) { + ticker := time.NewTicker(interval) + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + logger.Println("try to refresh auth token") + if err := c.refreshAuthToken(creds, region); err != nil { + logger.Printf("%s, err: %v", errMsgRefreshAuthToken, err) + } + } + } +} + +// updatePassword should only be invoked by updateAuthToken. +func (c *connector) updatePassword(password string) { + c.password = password + c.updateDSN() +} + +func (c *connector) readDSN() string { + c.dsnLock.RLock() + defer c.dsnLock.RUnlock() + return c.dsn +} + +func (c *connector) updateDSN() { + dsn := c.assembleDSN() + c.dsnLock.Lock() + defer c.dsnLock.Unlock() + c.dsn = dsn +} + +func (c *connector) assembleDSN() string { + dsn := fmt.Sprintf("host=%s port=%d dbname=%s user=%s password=%s sslmode=%s", + c.host, c.port, c.dbName, c.user, c.password, c.sslMode, + ) + if c.sslRootCert != "" { + dsn = fmt.Sprintf("%s sslrootcert=%s", dsn, c.sslRootCert) + } + return dsn +} diff --git a/rds/connector_test.go b/rds/connector_test.go new file mode 100644 index 0000000..155a48b --- /dev/null +++ b/rds/connector_test.go @@ -0,0 +1,330 @@ +// Copyright Yahoo 2021 +// Licensed under the terms of the Apache License 2.0. +// See LICENSE file in project root for terms. +package rds + +import ( + "bytes" + "context" + "errors" + "log" + "strings" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/golang/mock/gomock" + + "github.com/theparanoids/grafeas-rds/rds/config" +) + +func TestNewConnector(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + creds *credentials.Credentials + wantLogs string + wantErrMsg string + }{ + { + name: "happy path - no IAM auth", + }, + { + name: "happy path - IAM auth", + creds: credentials.NewStaticCredentials("a", "b", "c"), + wantLogs: logsOptInIAMAuth, + }, + { + name: "failed to set up IAM auth - invalid credentials", + creds: credentials.AnonymousCredentials, + wantLogs: logsOptInIAMAuth, + wantErrMsg: errMsgSetupIAMAuth, + }, + } + + mockCtrl := gomock.NewController(t) + mockDriver := NewMockDriver(mockCtrl) + mockCredentialsCreator := NewMockCredentialsCreator(mockCtrl) + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + conf := config.Config{} + var cc CredentialsCreator + if tt.creds != nil { + // Region is initialized here to make sure that + // the value of IAMAuthConfig is different in each test case. + conf.IAMAuth.Region = tt.name + mockCredentialsCreator.EXPECT().Create(conf.IAMAuth).Return(tt.creds, nil) + cc = mockCredentialsCreator + } + var buf bytes.Buffer + logger := log.New(&buf, "", 0) + c, err := newConnector(ctx, &conf, mockDriver, cc, logger) + if (err == nil) != (tt.wantErrMsg == "") { + if err == nil { + t.Error("want error, but no error is returned") + } else { + t.Errorf("want no error, but an error is returned: %v", err) + } + return + } + logs := buf.String() + if !strings.Contains(logs, tt.wantLogs) { + t.Errorf("got %q, but want it to include %q", logs, tt.wantLogs) + } + if err != nil { + return + } + dsn := c.readDSN() + if dsn == "" { + t.Errorf("the dsn %q should not be empty", dsn) + } + }) + } +} + +func TestConnectorConnect(t *testing.T) { + t.Parallel() + + mockCtrl := gomock.NewController(t) + mockDriver := NewMockDriver(mockCtrl) + c := &connector{ + dsn: "some dsn", + driver: mockDriver, + } + mockDriver.EXPECT().Open(c.dsn).Times(1) + c.Connect(context.Background()) +} + +func TestConnectorDriver(t *testing.T) { + t.Parallel() + + mockCtrl := gomock.NewController(t) + want := NewMockDriver(mockCtrl) + c := &connector{driver: want} + got := c.Driver() + if got != want { + t.Errorf("got %v, want %v", got, want) + } +} + +func TestSetupIAMAuth(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + creds *credentials.Credentials + wantErrMsg string + }{ + { + name: "happy path", + creds: credentials.NewStaticCredentials("a", "b", "c"), + }, + { + name: "failed to create credentials", + wantErrMsg: errMsgCreateCredentials, + }, + { + name: "invalid credentials", + creds: credentials.AnonymousCredentials, + wantErrMsg: errMsgRefreshAuthToken, + }, + } + + mockCtrl := gomock.NewController(t) + mockCredentialsCreator := NewMockCredentialsCreator(mockCtrl) + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + // Region is initialized here to make sure that + // the value of IAMAuthConfig is different in each test case. + conf := config.IAMAuthConfig{Region: tt.name} + var err error + if tt.creds == nil { + err = errors.New("some error") + } + mockCredentialsCreator.EXPECT().Create(conf).Return(tt.creds, err) + c := &connector{} + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + err = c.setupIAMAuth(ctx, conf, mockCredentialsCreator, log.Default()) + if (err == nil) != (tt.wantErrMsg == "") { + if err == nil { + t.Error("want error, but no error is returned") + } else { + t.Errorf("want no error, but an error is returned: %v", err) + } + return + } + if err != nil { + return + } + dsn := c.readDSN() + if dsn == "" { + t.Errorf("the dsn %q should not be empty", dsn) + } + }) + } +} + +func TestRefreshAuthToken(t *testing.T) { + t.Parallel() + tests := []struct { + name string + region string + creds *credentials.Credentials + wantErr bool + }{ + { + name: "happy path", + region: "some-region", + creds: credentials.NewStaticCredentials("a", "b", "c"), + wantErr: false, + }, + { + name: "empty secret key in credentials should fail", + creds: credentials.AnonymousCredentials, + wantErr: true, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + c := &connector{} + err := c.refreshAuthToken(tt.creds, tt.region) + hasErr := err != nil + if tt.wantErr != hasErr { + if err == nil { + t.Error("want error, but no error is returned") + } else { + t.Errorf("want no error, but an error is returned: %v", err) + } + return + } + if hasErr { + return + } + if !strings.Contains(c.password, tt.region) { + t.Errorf("the password %q should contain the specified region %q", c.password, tt.region) + } + dsn := c.readDSN() + if !strings.Contains(dsn, tt.region) { + t.Errorf("the dsn %q should contain the specified region %q", dsn, tt.region) + } + }) + } +} + +func TestRefreshAuthTokenPeriodically(t *testing.T) { + t.Parallel() + + c := &connector{} + creds := credentials.NewStaticCredentials("a", "b", "c") + + t.Run("happy path", func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + refreshInterval := 1000 * time.Millisecond + checkInterval := 1300 * time.Millisecond + go c.refreshAuthTokenPeriodically(ctx, creds, "", refreshInterval, log.Default()) + time.Sleep(checkInterval) + oldDsn := c.readDSN() + if oldDsn == "" { + t.Error("the dsn should have been updated with the refreshed token, but it's not") + } + time.Sleep(checkInterval) + dsn := c.readDSN() + if oldDsn == dsn { + t.Error("the dsn is not updated on the correct interval, hence the token") + } + }) + t.Run("context is done", func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.Background()) + cancel() + startTime := time.Now() + c.refreshAuthTokenPeriodically(ctx, creds, "", refreshAuthTokenInterval, log.Default()) + if time.Since(startTime) >= refreshAuthTokenInterval { + t.Error("context is done, but the function does not return immediately") + } + }) + t.Run("invalid credentials", func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.Background()) + var buf bytes.Buffer + const interval = 10 * time.Millisecond + go func() { + time.Sleep(interval * 5) + cancel() + }() + // A blocking call is used here to avoid race condition on buf. + // The writer (i.e. refreshAuthTokenPeriodically) should stop writing to buf + // before the reader (i.e. buf.String()) attemps to read it. + c.refreshAuthTokenPeriodically(ctx, credentials.AnonymousCredentials, "", interval, log.New(&buf, "", 0)) + logs := buf.String() + if !strings.Contains(logs, errMsgRefreshAuthToken) { + t.Errorf("got %q, but want it to include %q", logs, errMsgRefreshAuthToken) + } + }) +} + +func TestUpdatePassword(t *testing.T) { + t.Parallel() + + c := &connector{} + pwd := "abc" + c.updatePassword(pwd) + if c.password != pwd { + t.Errorf("got %q, want %q", c.password, pwd) + } + dsn := c.readDSN() + if !strings.Contains(dsn, pwd) { + t.Errorf("the dsn %q should contain the password %q", dsn, pwd) + } +} + +func TestAssembleDSN(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + sslRootCert string + want string + }{ + { + name: "ssl root cert is not given", + want: "host=localhost port=5432 dbname=grafeas user=grafeas_rw password=dummy-password-for-unit-tests-only sslmode=verify-full", + }, + { + name: "ssl root cert is given", + sslRootCert: "ca.pem", + want: "host=localhost port=5432 dbname=grafeas user=grafeas_rw password=dummy-password-for-unit-tests-only sslmode=verify-full sslrootcert=ca.pem", + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + c := connector{ + host: "localhost", + port: 5432, + dbName: "grafeas", + user: "grafeas_rw", + password: "dummy-password-for-unit-tests-only", + sslMode: "verify-full", + sslRootCert: tt.sslRootCert, + } + got := c.assembleDSN() + if got != tt.want { + t.Errorf("got %q, want %q", got, tt.want) + } + }) + } +} diff --git a/rds/credentials_creator_mock_test.go b/rds/credentials_creator_mock_test.go new file mode 100644 index 0000000..cc42d2f --- /dev/null +++ b/rds/credentials_creator_mock_test.go @@ -0,0 +1,51 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/theparanoids/grafeas-rds/rds (interfaces: CredentialsCreator) + +// Package rds is a generated GoMock package. +package rds + +import ( + reflect "reflect" + + credentials "github.com/aws/aws-sdk-go/aws/credentials" + gomock "github.com/golang/mock/gomock" + config "github.com/theparanoids/grafeas-rds/rds/config" +) + +// MockCredentialsCreator is a mock of CredentialsCreator interface. +type MockCredentialsCreator struct { + ctrl *gomock.Controller + recorder *MockCredentialsCreatorMockRecorder +} + +// MockCredentialsCreatorMockRecorder is the mock recorder for MockCredentialsCreator. +type MockCredentialsCreatorMockRecorder struct { + mock *MockCredentialsCreator +} + +// NewMockCredentialsCreator creates a new mock instance. +func NewMockCredentialsCreator(ctrl *gomock.Controller) *MockCredentialsCreator { + mock := &MockCredentialsCreator{ctrl: ctrl} + mock.recorder = &MockCredentialsCreatorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCredentialsCreator) EXPECT() *MockCredentialsCreatorMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockCredentialsCreator) Create(arg0 config.IAMAuthConfig) (*credentials.Credentials, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", arg0) + ret0, _ := ret[0].(*credentials.Credentials) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Create indicates an expected call of Create. +func (mr *MockCredentialsCreatorMockRecorder) Create(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockCredentialsCreator)(nil).Create), arg0) +} diff --git a/rds/driver_mock_test.go b/rds/driver_mock_test.go new file mode 100644 index 0000000..ccb6b75 --- /dev/null +++ b/rds/driver_mock_test.go @@ -0,0 +1,50 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: database/sql/driver (interfaces: Driver) + +// Package rds is a generated GoMock package. +package rds + +import ( + driver "database/sql/driver" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockDriver is a mock of Driver interface. +type MockDriver struct { + ctrl *gomock.Controller + recorder *MockDriverMockRecorder +} + +// MockDriverMockRecorder is the mock recorder for MockDriver. +type MockDriverMockRecorder struct { + mock *MockDriver +} + +// NewMockDriver creates a new mock instance. +func NewMockDriver(ctrl *gomock.Controller) *MockDriver { + mock := &MockDriver{ctrl: ctrl} + mock.recorder = &MockDriverMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDriver) EXPECT() *MockDriverMockRecorder { + return m.recorder +} + +// Open mocks base method. +func (m *MockDriver) Open(arg0 string) (driver.Conn, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Open", arg0) + ret0, _ := ret[0].(driver.Conn) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Open indicates an expected call of Open. +func (mr *MockDriverMockRecorder) Open(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Open", reflect.TypeOf((*MockDriver)(nil).Open), arg0) +} diff --git a/rds/gen.sh b/rds/gen.sh new file mode 100755 index 0000000..bb0680b --- /dev/null +++ b/rds/gen.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# +# The copyright header is deliberately placed below the shebang line because +# the OS only looks at the first two bytes to determine which executable to use. +# +# Copyright Yahoo 2021 +# Licensed under the terms of the Apache License 2.0. +# See LICENSE file in project root for terms. +# +# This script should only be invoked by "go generate" +# because it makes sure that the current working directory is the project root, +# so the tool binaries will be placed inside the bin directory of this project, +# and they will be git-ignored. +set -euo pipefail + +main() { + # Since different projects may have different versions of mockgen, + # we first install mockgen of the version specified in the go.mod of this project, + # and the binary will be placed under the bin directory of this project and git-ignored. + GOBIN=$(pwd)/bin + export GOBIN + go install github.com/golang/mock/mockgen + + # Make sure that we are using the mockgen which is just built to generate mock code. + export PATH=$GOBIN:$PATH + mockgen -package rds -destination driver_mock_test.go database/sql/driver Driver + mockgen -package rds -destination conn_pool_mgr_mock_test.go . ConnPoolMgr + mockgen -package rds -destination storage_mock_test.go . Storage + mockgen -package rds -destination credentials_creator_mock_test.go . CredentialsCreator + mockgen -package rds -destination storage_creator_mock_test.go . StorageCreator +} + +main diff --git a/rds/generate.go b/rds/generate.go new file mode 100644 index 0000000..4927525 --- /dev/null +++ b/rds/generate.go @@ -0,0 +1,6 @@ +// Copyright Yahoo 2021 +// Licensed under the terms of the Apache License 2.0. +// See LICENSE file in project root for terms. +package rds + +//go:generate ./gen.sh diff --git a/rds/storage.go b/rds/storage.go new file mode 100644 index 0000000..6437021 --- /dev/null +++ b/rds/storage.go @@ -0,0 +1,103 @@ +// Copyright Yahoo 2021 +// Licensed under the terms of the Apache License 2.0. +// See LICENSE file in project root for terms. +package rds + +import ( + "context" + "database/sql/driver" + "fmt" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/grafeas/grafeas/go/config" + "github.com/grafeas/grafeas/go/v1beta1/storage" + rdsconfig "github.com/theparanoids/grafeas-rds/rds/config" +) + +const ( + errMsgInitConfig = "failed to initialize config" + errMsgInitConnector = "failed to initialize connector" + errMsgInitStorage = "failed to initialize store" +) + +// ConnPoolMgr manages a RDBMS connection pool. +// The methods are defined on sql.DB. +// Ref. https://golang.org/pkg/database/sql/ +type ConnPoolMgr interface { + SetMaxOpenConns(n int) + SetMaxIdleConns(n int) + SetConnMaxLifetime(d time.Duration) + SetConnMaxIdleTime(d time.Duration) +} + +// Storage contains all the methods to +// 1. be used as a backend for a Grafeas server AND +// 2. manage a RDBMS connection pool. +type Storage interface { + storage.Gs + storage.Ps + ConnPoolMgr +} + +// StorageCreator can be implemented based on the backend storage types (e.g. PostgreSQL, MySQL, etc.). +type StorageCreator interface { + Create(connector driver.Connector, paginationKey string) (Storage, error) +} + +// CredentialsCreator can be implemented to create Credentials based on different types of providers. +// Fields of IAMAuthConfig can be updated to incorporate such changes in the future. +type CredentialsCreator interface { + Create(rdsconfig.IAMAuthConfig) (*credentials.Credentials, error) +} + +// GrafeasStorageProvider contains the fields to flexibily create a storage.Storage. +type GrafeasStorageProvider struct { + drv driver.Driver + credentialsCreator CredentialsCreator + storageCreator StorageCreator +} + +// NewGrafeasStorageProvider returns a StorageProvider whose fields are populated with the arguments. +func NewGrafeasStorageProvider(drv driver.Driver, credentialsCreator CredentialsCreator, storageCreator StorageCreator) *GrafeasStorageProvider { + return &GrafeasStorageProvider{ + drv: drv, + credentialsCreator: credentialsCreator, + storageCreator: storageCreator, + } +} + +// Provide returns a storage which is configured based on the receiver's fields. +func (p GrafeasStorageProvider) Provide(_ string, confi *config.StorageConfiguration) (*storage.Storage, error) { + conf, err := rdsconfig.New(confi) + if err != nil { + return nil, fmt.Errorf("%s, err: %v", errMsgInitConfig, err) + } + + // TODO: Use the context passed from main after + // the signature of RegisterStorageTypeProvider is updated to include it. + connector, err := newConnector(context.Background(), conf, p.drv, p.credentialsCreator, log.Default()) + if err != nil { + return nil, fmt.Errorf("%s, err: %v", errMsgInitConnector, err) + } + + rdsStorage, err := p.storageCreator.Create(connector, conf.PaginationKey) + if err != nil { + return nil, fmt.Errorf("%s, err: %v", errMsgInitStorage, err) + } + setConnPoolParams(rdsStorage, conf.ConnPool) + + grafeasStorage := &storage.Storage{ + Ps: rdsStorage, + Gs: rdsStorage, + } + return grafeasStorage, nil +} + +func setConnPoolParams(mgr ConnPoolMgr, conf rdsconfig.ConnPoolConfig) { + mgr.SetMaxOpenConns(conf.MaxOpenConns) + mgr.SetMaxIdleConns(conf.MaxIdleConns) + mgr.SetConnMaxLifetime(time.Duration(conf.ConnMaxLifetimeInSeconds) * time.Second) + mgr.SetConnMaxIdleTime(time.Duration(conf.ConnMaxIdleTimeInSeconds) * time.Second) +} diff --git a/rds/storage_creator_mock_test.go b/rds/storage_creator_mock_test.go new file mode 100644 index 0000000..e45f4b8 --- /dev/null +++ b/rds/storage_creator_mock_test.go @@ -0,0 +1,50 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/theparanoids/grafeas-rds/rds (interfaces: StorageCreator) + +// Package rds is a generated GoMock package. +package rds + +import ( + driver "database/sql/driver" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockStorageCreator is a mock of StorageCreator interface. +type MockStorageCreator struct { + ctrl *gomock.Controller + recorder *MockStorageCreatorMockRecorder +} + +// MockStorageCreatorMockRecorder is the mock recorder for MockStorageCreator. +type MockStorageCreatorMockRecorder struct { + mock *MockStorageCreator +} + +// NewMockStorageCreator creates a new mock instance. +func NewMockStorageCreator(ctrl *gomock.Controller) *MockStorageCreator { + mock := &MockStorageCreator{ctrl: ctrl} + mock.recorder = &MockStorageCreatorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStorageCreator) EXPECT() *MockStorageCreatorMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockStorageCreator) Create(arg0 driver.Connector, arg1 string) (Storage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", arg0, arg1) + ret0, _ := ret[0].(Storage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Create indicates an expected call of Create. +func (mr *MockStorageCreatorMockRecorder) Create(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockStorageCreator)(nil).Create), arg0, arg1) +} diff --git a/rds/storage_mock_test.go b/rds/storage_mock_test.go new file mode 100644 index 0000000..a186d5f --- /dev/null +++ b/rds/storage_mock_test.go @@ -0,0 +1,373 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/theparanoids/grafeas-rds/rds (interfaces: Storage) + +// Package rds is a generated GoMock package. +package rds + +import ( + context "context" + reflect "reflect" + time "time" + + gomock "github.com/golang/mock/gomock" + grafeas_go_proto "github.com/grafeas/grafeas/proto/v1beta1/grafeas_go_proto" + project_go_proto "github.com/grafeas/grafeas/proto/v1beta1/project_go_proto" + fieldmaskpb "google.golang.org/protobuf/types/known/fieldmaskpb" +) + +// MockStorage is a mock of Storage interface. +type MockStorage struct { + ctrl *gomock.Controller + recorder *MockStorageMockRecorder +} + +// MockStorageMockRecorder is the mock recorder for MockStorage. +type MockStorageMockRecorder struct { + mock *MockStorage +} + +// NewMockStorage creates a new mock instance. +func NewMockStorage(ctrl *gomock.Controller) *MockStorage { + mock := &MockStorage{ctrl: ctrl} + mock.recorder = &MockStorageMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStorage) EXPECT() *MockStorageMockRecorder { + return m.recorder +} + +// BatchCreateNotes mocks base method. +func (m *MockStorage) BatchCreateNotes(arg0 context.Context, arg1, arg2 string, arg3 map[string]*grafeas_go_proto.Note) ([]*grafeas_go_proto.Note, []error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BatchCreateNotes", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].([]*grafeas_go_proto.Note) + ret1, _ := ret[1].([]error) + return ret0, ret1 +} + +// BatchCreateNotes indicates an expected call of BatchCreateNotes. +func (mr *MockStorageMockRecorder) BatchCreateNotes(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchCreateNotes", reflect.TypeOf((*MockStorage)(nil).BatchCreateNotes), arg0, arg1, arg2, arg3) +} + +// BatchCreateOccurrences mocks base method. +func (m *MockStorage) BatchCreateOccurrences(arg0 context.Context, arg1, arg2 string, arg3 []*grafeas_go_proto.Occurrence) ([]*grafeas_go_proto.Occurrence, []error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BatchCreateOccurrences", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].([]*grafeas_go_proto.Occurrence) + ret1, _ := ret[1].([]error) + return ret0, ret1 +} + +// BatchCreateOccurrences indicates an expected call of BatchCreateOccurrences. +func (mr *MockStorageMockRecorder) BatchCreateOccurrences(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchCreateOccurrences", reflect.TypeOf((*MockStorage)(nil).BatchCreateOccurrences), arg0, arg1, arg2, arg3) +} + +// CreateNote mocks base method. +func (m *MockStorage) CreateNote(arg0 context.Context, arg1, arg2, arg3 string, arg4 *grafeas_go_proto.Note) (*grafeas_go_proto.Note, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateNote", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(*grafeas_go_proto.Note) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateNote indicates an expected call of CreateNote. +func (mr *MockStorageMockRecorder) CreateNote(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNote", reflect.TypeOf((*MockStorage)(nil).CreateNote), arg0, arg1, arg2, arg3, arg4) +} + +// CreateOccurrence mocks base method. +func (m *MockStorage) CreateOccurrence(arg0 context.Context, arg1, arg2 string, arg3 *grafeas_go_proto.Occurrence) (*grafeas_go_proto.Occurrence, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateOccurrence", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*grafeas_go_proto.Occurrence) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateOccurrence indicates an expected call of CreateOccurrence. +func (mr *MockStorageMockRecorder) CreateOccurrence(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOccurrence", reflect.TypeOf((*MockStorage)(nil).CreateOccurrence), arg0, arg1, arg2, arg3) +} + +// CreateProject mocks base method. +func (m *MockStorage) CreateProject(arg0 context.Context, arg1 string, arg2 *project_go_proto.Project) (*project_go_proto.Project, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateProject", arg0, arg1, arg2) + ret0, _ := ret[0].(*project_go_proto.Project) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateProject indicates an expected call of CreateProject. +func (mr *MockStorageMockRecorder) CreateProject(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateProject", reflect.TypeOf((*MockStorage)(nil).CreateProject), arg0, arg1, arg2) +} + +// DeleteNote mocks base method. +func (m *MockStorage) DeleteNote(arg0 context.Context, arg1, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteNote", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteNote indicates an expected call of DeleteNote. +func (mr *MockStorageMockRecorder) DeleteNote(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteNote", reflect.TypeOf((*MockStorage)(nil).DeleteNote), arg0, arg1, arg2) +} + +// DeleteOccurrence mocks base method. +func (m *MockStorage) DeleteOccurrence(arg0 context.Context, arg1, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteOccurrence", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteOccurrence indicates an expected call of DeleteOccurrence. +func (mr *MockStorageMockRecorder) DeleteOccurrence(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOccurrence", reflect.TypeOf((*MockStorage)(nil).DeleteOccurrence), arg0, arg1, arg2) +} + +// DeleteProject mocks base method. +func (m *MockStorage) DeleteProject(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteProject", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteProject indicates an expected call of DeleteProject. +func (mr *MockStorageMockRecorder) DeleteProject(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteProject", reflect.TypeOf((*MockStorage)(nil).DeleteProject), arg0, arg1) +} + +// GetNote mocks base method. +func (m *MockStorage) GetNote(arg0 context.Context, arg1, arg2 string) (*grafeas_go_proto.Note, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetNote", arg0, arg1, arg2) + ret0, _ := ret[0].(*grafeas_go_proto.Note) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetNote indicates an expected call of GetNote. +func (mr *MockStorageMockRecorder) GetNote(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNote", reflect.TypeOf((*MockStorage)(nil).GetNote), arg0, arg1, arg2) +} + +// GetOccurrence mocks base method. +func (m *MockStorage) GetOccurrence(arg0 context.Context, arg1, arg2 string) (*grafeas_go_proto.Occurrence, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOccurrence", arg0, arg1, arg2) + ret0, _ := ret[0].(*grafeas_go_proto.Occurrence) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetOccurrence indicates an expected call of GetOccurrence. +func (mr *MockStorageMockRecorder) GetOccurrence(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOccurrence", reflect.TypeOf((*MockStorage)(nil).GetOccurrence), arg0, arg1, arg2) +} + +// GetOccurrenceNote mocks base method. +func (m *MockStorage) GetOccurrenceNote(arg0 context.Context, arg1, arg2 string) (*grafeas_go_proto.Note, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOccurrenceNote", arg0, arg1, arg2) + ret0, _ := ret[0].(*grafeas_go_proto.Note) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetOccurrenceNote indicates an expected call of GetOccurrenceNote. +func (mr *MockStorageMockRecorder) GetOccurrenceNote(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOccurrenceNote", reflect.TypeOf((*MockStorage)(nil).GetOccurrenceNote), arg0, arg1, arg2) +} + +// GetProject mocks base method. +func (m *MockStorage) GetProject(arg0 context.Context, arg1 string) (*project_go_proto.Project, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetProject", arg0, arg1) + ret0, _ := ret[0].(*project_go_proto.Project) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetProject indicates an expected call of GetProject. +func (mr *MockStorageMockRecorder) GetProject(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProject", reflect.TypeOf((*MockStorage)(nil).GetProject), arg0, arg1) +} + +// GetVulnerabilityOccurrencesSummary mocks base method. +func (m *MockStorage) GetVulnerabilityOccurrencesSummary(arg0 context.Context, arg1, arg2 string) (*grafeas_go_proto.VulnerabilityOccurrencesSummary, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetVulnerabilityOccurrencesSummary", arg0, arg1, arg2) + ret0, _ := ret[0].(*grafeas_go_proto.VulnerabilityOccurrencesSummary) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetVulnerabilityOccurrencesSummary indicates an expected call of GetVulnerabilityOccurrencesSummary. +func (mr *MockStorageMockRecorder) GetVulnerabilityOccurrencesSummary(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVulnerabilityOccurrencesSummary", reflect.TypeOf((*MockStorage)(nil).GetVulnerabilityOccurrencesSummary), arg0, arg1, arg2) +} + +// ListNoteOccurrences mocks base method. +func (m *MockStorage) ListNoteOccurrences(arg0 context.Context, arg1, arg2, arg3, arg4 string, arg5 int32) ([]*grafeas_go_proto.Occurrence, string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListNoteOccurrences", arg0, arg1, arg2, arg3, arg4, arg5) + ret0, _ := ret[0].([]*grafeas_go_proto.Occurrence) + ret1, _ := ret[1].(string) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// ListNoteOccurrences indicates an expected call of ListNoteOccurrences. +func (mr *MockStorageMockRecorder) ListNoteOccurrences(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListNoteOccurrences", reflect.TypeOf((*MockStorage)(nil).ListNoteOccurrences), arg0, arg1, arg2, arg3, arg4, arg5) +} + +// ListNotes mocks base method. +func (m *MockStorage) ListNotes(arg0 context.Context, arg1, arg2, arg3 string, arg4 int32) ([]*grafeas_go_proto.Note, string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListNotes", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].([]*grafeas_go_proto.Note) + ret1, _ := ret[1].(string) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// ListNotes indicates an expected call of ListNotes. +func (mr *MockStorageMockRecorder) ListNotes(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListNotes", reflect.TypeOf((*MockStorage)(nil).ListNotes), arg0, arg1, arg2, arg3, arg4) +} + +// ListOccurrences mocks base method. +func (m *MockStorage) ListOccurrences(arg0 context.Context, arg1, arg2, arg3 string, arg4 int32) ([]*grafeas_go_proto.Occurrence, string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListOccurrences", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].([]*grafeas_go_proto.Occurrence) + ret1, _ := ret[1].(string) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// ListOccurrences indicates an expected call of ListOccurrences. +func (mr *MockStorageMockRecorder) ListOccurrences(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListOccurrences", reflect.TypeOf((*MockStorage)(nil).ListOccurrences), arg0, arg1, arg2, arg3, arg4) +} + +// ListProjects mocks base method. +func (m *MockStorage) ListProjects(arg0 context.Context, arg1 string, arg2 int, arg3 string) ([]*project_go_proto.Project, string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListProjects", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].([]*project_go_proto.Project) + ret1, _ := ret[1].(string) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// ListProjects indicates an expected call of ListProjects. +func (mr *MockStorageMockRecorder) ListProjects(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListProjects", reflect.TypeOf((*MockStorage)(nil).ListProjects), arg0, arg1, arg2, arg3) +} + +// SetConnMaxIdleTime mocks base method. +func (m *MockStorage) SetConnMaxIdleTime(arg0 time.Duration) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetConnMaxIdleTime", arg0) +} + +// SetConnMaxIdleTime indicates an expected call of SetConnMaxIdleTime. +func (mr *MockStorageMockRecorder) SetConnMaxIdleTime(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetConnMaxIdleTime", reflect.TypeOf((*MockStorage)(nil).SetConnMaxIdleTime), arg0) +} + +// SetConnMaxLifetime mocks base method. +func (m *MockStorage) SetConnMaxLifetime(arg0 time.Duration) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetConnMaxLifetime", arg0) +} + +// SetConnMaxLifetime indicates an expected call of SetConnMaxLifetime. +func (mr *MockStorageMockRecorder) SetConnMaxLifetime(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetConnMaxLifetime", reflect.TypeOf((*MockStorage)(nil).SetConnMaxLifetime), arg0) +} + +// SetMaxIdleConns mocks base method. +func (m *MockStorage) SetMaxIdleConns(arg0 int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetMaxIdleConns", arg0) +} + +// SetMaxIdleConns indicates an expected call of SetMaxIdleConns. +func (mr *MockStorageMockRecorder) SetMaxIdleConns(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMaxIdleConns", reflect.TypeOf((*MockStorage)(nil).SetMaxIdleConns), arg0) +} + +// SetMaxOpenConns mocks base method. +func (m *MockStorage) SetMaxOpenConns(arg0 int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetMaxOpenConns", arg0) +} + +// SetMaxOpenConns indicates an expected call of SetMaxOpenConns. +func (mr *MockStorageMockRecorder) SetMaxOpenConns(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMaxOpenConns", reflect.TypeOf((*MockStorage)(nil).SetMaxOpenConns), arg0) +} + +// UpdateNote mocks base method. +func (m *MockStorage) UpdateNote(arg0 context.Context, arg1, arg2 string, arg3 *grafeas_go_proto.Note, arg4 *fieldmaskpb.FieldMask) (*grafeas_go_proto.Note, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateNote", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(*grafeas_go_proto.Note) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateNote indicates an expected call of UpdateNote. +func (mr *MockStorageMockRecorder) UpdateNote(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateNote", reflect.TypeOf((*MockStorage)(nil).UpdateNote), arg0, arg1, arg2, arg3, arg4) +} + +// UpdateOccurrence mocks base method. +func (m *MockStorage) UpdateOccurrence(arg0 context.Context, arg1, arg2 string, arg3 *grafeas_go_proto.Occurrence, arg4 *fieldmaskpb.FieldMask) (*grafeas_go_proto.Occurrence, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateOccurrence", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(*grafeas_go_proto.Occurrence) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateOccurrence indicates an expected call of UpdateOccurrence. +func (mr *MockStorageMockRecorder) UpdateOccurrence(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateOccurrence", reflect.TypeOf((*MockStorage)(nil).UpdateOccurrence), arg0, arg1, arg2, arg3, arg4) +} diff --git a/rds/storage_test.go b/rds/storage_test.go new file mode 100644 index 0000000..4b14664 --- /dev/null +++ b/rds/storage_test.go @@ -0,0 +1,121 @@ +// Copyright Yahoo 2021 +// Licensed under the terms of the Apache License 2.0. +// See LICENSE file in project root for terms. +package rds + +import ( + "errors" + "strings" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws/credentials" + gomock "github.com/golang/mock/gomock" + "github.com/grafeas/grafeas/go/config" + + rdsconfig "github.com/theparanoids/grafeas-rds/rds/config" +) + +func TestStorageProviderProvide(t *testing.T) { + t.Parallel() + + mockCtrl := gomock.NewController(t) + validConf := config.StorageConfiguration(rdsconfig.Config{ + Host: "some-host.rds.amazonaws.com", + User: "grafeas_rw", + Password: "dummy-password-for-unit-tests-only", + SSLRootCert: "/opt/rds-ca-2019-root.pem", + // ConnPool is populated in order to test if setConnPoolParams is invoked in Provide. + ConnPool: rdsconfig.ConnPoolConfig{ + MaxOpenConns: 1, + MaxIdleConns: 2, + ConnMaxLifetimeInSeconds: 3, + ConnMaxIdleTimeInSeconds: 4, + }, + }) + + type testCase struct { + name string + expect func(*testCase) + conf config.StorageConfiguration + store *MockStorage + storeCreator *MockStorageCreator + credsCreator *MockCredentialsCreator + wantErrMsg string + } + tests := []testCase{ + { + name: "happy path", + expect: func(tt *testCase) { + tt.credsCreator.EXPECT().Create(gomock.Any()).Times(1).Return(credentials.NewStaticCredentials("a", "b", "c"), nil) + tt.storeCreator.EXPECT().Create(gomock.Any(), gomock.Any()).Times(1).Return(tt.store, nil) + + conf := tt.conf.(rdsconfig.Config).ConnPool + tt.store.EXPECT().SetMaxOpenConns(conf.MaxOpenConns).Times(1) + tt.store.EXPECT().SetMaxIdleConns(conf.MaxIdleConns).Times(1) + tt.store.EXPECT().SetConnMaxLifetime(time.Duration(conf.ConnMaxLifetimeInSeconds) * time.Second).Times(1) + tt.store.EXPECT().SetConnMaxIdleTime(time.Duration(conf.ConnMaxIdleTimeInSeconds) * time.Second).Times(1) + }, + conf: validConf, + store: NewMockStorage(mockCtrl), + storeCreator: NewMockStorageCreator(mockCtrl), + credsCreator: NewMockCredentialsCreator(mockCtrl), + }, + { + name: "invalid config", + // An empty Config is invalid because the Host field does not have a default value. + conf: config.StorageConfiguration(rdsconfig.Config{}), + wantErrMsg: errMsgInitConfig, + }, + { + name: "invalid connector", + expect: func(tt *testCase) { + tt.credsCreator.EXPECT().Create(gomock.Any()).Times(1).Return(credentials.AnonymousCredentials, nil) + }, + conf: validConf, + credsCreator: NewMockCredentialsCreator(mockCtrl), + wantErrMsg: errMsgInitConnector, + }, + { + name: "invalid store", + expect: func(tt *testCase) { + tt.credsCreator.EXPECT().Create(gomock.Any()).Times(1).Return(credentials.NewStaticCredentials("a", "b", "c"), nil) + tt.storeCreator.EXPECT().Create(gomock.Any(), gomock.Any()).Times(1).Return(nil, errors.New("random error")) + }, + conf: validConf, + storeCreator: NewMockStorageCreator(mockCtrl), + credsCreator: NewMockCredentialsCreator(mockCtrl), + wantErrMsg: errMsgInitStorage, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + if tt.expect != nil { + tt.expect(&tt) + } + storageProvider := NewGrafeasStorageProvider(NewMockDriver(mockCtrl), tt.credsCreator, tt.storeCreator) + storage, err := storageProvider.Provide("", &tt.conf) + if (err != nil) != (tt.wantErrMsg != "") { + if err != nil { + t.Errorf("don't want error, but got %q", err) + } else { + t.Errorf("got nil error, but want error to include %q", tt.wantErrMsg) + } + return + } + if err != nil { + if !strings.Contains(err.Error(), tt.wantErrMsg) { + t.Errorf("want %q to include %q", err.Error(), tt.wantErrMsg) + } + return + } + + if storage.Gs != tt.store || storage.Ps != tt.store { + t.Errorf("unexpected fields: %v", storage) + } + }) + } +} diff --git a/rds/tools.go b/rds/tools.go new file mode 100644 index 0000000..4f0a016 --- /dev/null +++ b/rds/tools.go @@ -0,0 +1,13 @@ +// Copyright Yahoo 2021 +// Licensed under the terms of the Apache License 2.0. +// See LICENSE file in project root for terms. +// +// +build tools + +package rds + +// Although gomock is already in go.mod because +// it is also used as a library by the testing code, +// we still put it here to make sure that +// all dependant tool binaries are listed here for the sake of consistency. +import _ "github.com/golang/mock/mockgen"