├── .circleci └── config.yml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── pull_request_template.md ├── .gitignore ├── .img └── management-network-diagram.png ├── .pre-commit-config.yaml ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── examples ├── bastion-host │ ├── README.md │ ├── main.tf │ ├── outputs.tf │ └── variables.tf ├── network-host-application │ ├── README.md │ ├── main.tf │ ├── outputs.tf │ └── variables.tf └── network-management │ ├── README.md │ ├── main.tf │ ├── outputs.tf │ └── variables.tf ├── main.tf ├── modules ├── bastion-host │ ├── README.md │ ├── main.tf │ ├── outputs.tf │ └── variables.tf ├── network-firewall │ ├── README.md │ ├── main.tf │ ├── outputs.tf │ └── variables.tf ├── network-peering │ ├── README.md │ ├── main.tf │ └── variables.tf ├── project-host-configuration │ ├── README.md │ ├── main.tf │ └── variables.tf └── vpc-network │ ├── README.md │ ├── main.tf │ ├── outputs.tf │ └── variables.tf ├── outputs.tf ├── test ├── bastion_host_test.go ├── go.mod ├── go.sum ├── management_network_test.go ├── network_helpers.go ├── terratest_options.go └── validation │ └── validate_all_modules_and_examples_test.go └── variables.tf /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | defaults: &defaults 4 | machine: 5 | image: ubuntu-2004:202104-01 6 | 7 | env: &env 8 | environment: 9 | GRUNTWORK_INSTALLER_VERSION: v0.0.30 10 | TERRATEST_LOG_PARSER_VERSION: v0.30.4 11 | MODULE_CI_VERSION: v0.38.4 12 | TERRAFORM_VERSION: 1.0.4 13 | TERRAGRUNT_VERSION: NONE 14 | PACKER_VERSION: NONE 15 | GOLANG_VERSION: 1.16 16 | GO111MODULE: auto 17 | 18 | jobs: 19 | precommit: 20 | <<: *env 21 | docker: 22 | - image: circleci/python:3.8.1 23 | steps: 24 | - checkout 25 | 26 | - run: 27 | name: install dependencies 28 | command: | 29 | curl -Ls https://raw.githubusercontent.com/gruntwork-io/gruntwork-installer/master/bootstrap-gruntwork-installer.sh | bash /dev/stdin --version "${GRUNTWORK_INSTALLER_VERSION}" 30 | gruntwork-install --module-name "gruntwork-module-circleci-helpers" --repo "https://github.com/gruntwork-io/terraform-aws-ci" --tag "${MODULE_CI_VERSION}" 31 | configure-environment-for-gruntwork-module \ 32 | --terraform-version ${TERRAFORM_VERSION} \ 33 | --terragrunt-version NONE \ 34 | --packer-version NONE \ 35 | --go-version ${GOLANG_VERSION} 36 | # Fail the build if the pre-commit hooks don't pass. Note: if you run pre-commit install locally, these hooks will 37 | # execute automatically every time before you commit, ensuring the build never fails at this step! 38 | - run: 39 | command: | 40 | pip install pre-commit==1.21.0 cfgv==2.0.1 zipp==1.1.0 yapf 41 | go get golang.org/x/tools/cmd/goimports 42 | export GOPATH=~/go/bin && export PATH=$PATH:$GOPATH 43 | pre-commit install 44 | pre-commit run --all-files 45 | 46 | test: 47 | <<: *defaults 48 | <<: *env 49 | steps: 50 | - checkout 51 | - run: &install_gruntwork_tooling 52 | name: install gruntwork tooling 53 | command: | 54 | sudo apt-get -y update 55 | curl -Ls https://raw.githubusercontent.com/gruntwork-io/gruntwork-installer/master/bootstrap-gruntwork-installer.sh | bash /dev/stdin --version "${GRUNTWORK_INSTALLER_VERSION}" 56 | gruntwork-install --module-name "gruntwork-module-circleci-helpers" --repo "https://github.com/gruntwork-io/terraform-aws-ci" --tag "${MODULE_CI_VERSION}" 57 | gruntwork-install --module-name "git-helpers" --repo "https://github.com/gruntwork-io/terraform-aws-ci" --tag "${MODULE_CI_VERSION}" 58 | gruntwork-install --binary-name "terratest_log_parser" --repo "https://github.com/gruntwork-io/terratest" --tag "${TERRATEST_LOG_PARSER_VERSION}" 59 | configure-environment-for-gruntwork-module --go-src-path ./test --terraform-version ${TERRAFORM_VERSION} --terragrunt-version ${TERRAGRUNT_VERSION} --packer-version ${PACKER_VERSION} --go-version ${GOLANG_VERSION} 60 | 61 | - run: 62 | name: run tests 63 | command: | 64 | # required for gcloud and kubectl to authenticate correctly 65 | echo $GCLOUD_SERVICE_KEY | gcloud auth activate-service-account --key-file=- 66 | gcloud --quiet config set project ${GOOGLE_PROJECT_ID} 67 | gcloud --quiet config set compute/zone ${GOOGLE_COMPUTE_ZONE} 68 | # required for terraform and terratest to authenticate correctly 69 | echo $GCLOUD_SERVICE_KEY > /tmp/gcloud.json 70 | export GOOGLE_APPLICATION_CREDENTIALS="/tmp/gcloud.json" 71 | # run the tests 72 | mkdir -p /tmp/logs 73 | run-go-tests --path test --timeout 2h | tee /tmp/logs/all.log 74 | no_output_timeout: 1h 75 | 76 | - run: 77 | name: parse test output 78 | command: terratest_log_parser --testlog /tmp/logs/all.log --outputdir /tmp/logs 79 | when: always 80 | 81 | - store_artifacts: 82 | path: /tmp/logs 83 | - store_test_results: 84 | path: /tmp/logs 85 | 86 | workflows: 87 | version: 2 88 | test: 89 | jobs: 90 | - precommit: 91 | context: 92 | - GCP__automated-tests 93 | - GITHUB__PAT__gruntwork-ci 94 | filters: 95 | tags: 96 | only: /^v.*/ 97 | - test: 98 | context: 99 | - GCP__automated-tests 100 | - GITHUB__PAT__gruntwork-ci 101 | requires: 102 | - precommit 103 | filters: 104 | tags: 105 | only: /^v.*/ 106 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report to help us improve. 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 14 | 15 | **Describe the bug** 16 | A clear and concise description of what the bug is. 17 | 18 | **To Reproduce** 19 | Steps to reproduce the behavior including the relevant Terraform/Terragrunt/Packer version number and any code snippets and module inputs you used. 20 | 21 | ```hcl 22 | // paste code snippets here 23 | ``` 24 | 25 | **Expected behavior** 26 | A clear and concise description of what you expected to happen. 27 | 28 | **Nice to have** 29 | - [ ] Terminal output 30 | - [ ] Screenshots 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Submit a feature request for this repo. 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | 14 | 15 | **Describe the solution you'd like** 16 | A clear and concise description of what you want to happen. 17 | 18 | **Describe alternatives you've considered** 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | **Additional context** 22 | Add any other context or screenshots about the feature request here. 23 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | ## Description 8 | 9 | 10 | 11 | ### Documentation 12 | 13 | 21 | 22 | 23 | 24 | ## TODOs 25 | 26 | Please ensure all of these TODOs are completed before asking for a review. 27 | 28 | - [ ] Ensure the branch is named correctly with the issue number. e.g: `feature/new-vpc-endpoints-955` or `bug/missing-count-param-434`. 29 | - [ ] Update the docs. 30 | - [ ] Keep the changes backward compatible where possible. 31 | - [ ] Run the pre-commit checks successfully. 32 | - [ ] Run the relevant tests successfully. 33 | - [ ] Ensure any 3rd party code adheres with our [license policy](https://www.notion.so/gruntwork/Gruntwork-licenses-and-open-source-usage-policy-f7dece1f780341c7b69c1763f22b1378) or delete this line if its not applicable. 34 | 35 | 36 | ## Related Issues 37 | 38 | 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Terraform files 2 | .terraform 3 | terraform.tfstate 4 | terraform.tfvars 5 | *.tfstate* 6 | 7 | # OS X files 8 | .history 9 | .DS_Store 10 | 11 | # IntelliJ files 12 | .idea_modules 13 | *.iml 14 | *.iws 15 | *.ipr 16 | .idea/ 17 | build/ 18 | */build/ 19 | out/ 20 | 21 | # Go best practices dictate that libraries should not include the vendor directory 22 | vendor 23 | 24 | # Folder used to store temporary test data by Terratest 25 | .test-data 26 | 27 | # Mock user-data log file 28 | mock-user-data.log 29 | 30 | # Ignore Terraform lock files, as we want to test the Terraform code in these repos with the latest provider 31 | # versions. 32 | .terraform.lock.hcl 33 | -------------------------------------------------------------------------------- /.img/management-network-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruntwork-io/terraform-google-network/a5dd3f6e90ffb290838ee9eb349eea3eae3f55ea/.img/management-network-diagram.png -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/gruntwork-io/pre-commit 3 | rev: v0.1.10 4 | hooks: 5 | - id: terraform-fmt 6 | - id: goimports 7 | 8 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @marinalimeira @robmorgan @ina-stoyanova @gruntwork-io/maintenance-tier-3-orion 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | Contributions to this Module are very welcome! We follow a fairly standard [pull request 4 | process](https://help.github.com/articles/about-pull-requests/) for contributions, subject to the following guidelines: 5 | 6 | 1. [File a GitHub issue](#file-a-github-issue) 7 | 1. [Update the documentation](#update-the-documentation) 8 | 1. [Update the tests](#update-the-tests) 9 | 1. [Update the code](#update-the-code) 10 | 1. [Create a pull request](#create-a-pull-request) 11 | 1. [Merge and release](#merge-and-release) 12 | 13 | ## File a GitHub issue 14 | 15 | Before starting any work, we recommend filing a GitHub issue in this repo. This is your chance to ask questions and 16 | get feedback from the maintainers and the community before you sink a lot of time into writing (possibly the wrong) 17 | code. If there is anything you're unsure about, just ask! 18 | 19 | ## Update the documentation 20 | 21 | We recommend updating the documentation *before* updating any code (see [Readme Driven 22 | Development](http://tom.preston-werner.com/2010/08/23/readme-driven-development.html)). This ensures the documentation 23 | stays up to date and allows you to think through the problem at a high level before you get lost in the weeds of 24 | coding. 25 | 26 | ## Update the tests 27 | 28 | We also recommend updating the automated tests *before* updating any code (see [Test Driven 29 | Development](https://en.wikipedia.org/wiki/Test-driven_development)). That means you add or update a test case, 30 | verify that it's failing with a clear error message, and *then* make the code changes to get that test to pass. This 31 | ensures the tests stay up to date and verify all the functionality in this Module, including whatever new 32 | functionality you're adding in your contribution. Check out the [tests](https://github.com/gruntwork-io/terraform-google-network/tree/master/test) folder for 33 | instructions on running the automated tests. 34 | 35 | ## Update the code 36 | 37 | At this point, make your code changes and use your new test case to verify that everything is working. As you work, 38 | keep in mind two things: 39 | 40 | 1. Backwards compatibility 41 | 1. Downtime 42 | 43 | ### Backwards compatibility 44 | 45 | Please make every effort to avoid unnecessary backwards incompatible changes. With Terraform code, this means: 46 | 47 | 1. Do not delete, rename, or change the type of input variables. 48 | 1. If you add an input variable, it should have a `default`. 49 | 1. Do not delete, rename, or change the type of output variables. 50 | 1. Do not delete or rename a module in the `modules` folder. 51 | 52 | If a backwards incompatible change cannot be avoided, please make sure to call that out when you submit a pull request, 53 | explaining why the change is absolutely necessary. 54 | 55 | ### Downtime 56 | 57 | Bear in mind that the Terraform code in this Module is used by real companies to run real infrastructure in 58 | production, and certain types of changes could cause downtime. For example, consider the following: 59 | 60 | 1. If you rename a resource (e.g. `aws_instance "foo"` -> `aws_instance "bar"`), Terraform will see that as deleting 61 | the old resource and creating a new one. 62 | 1. If you change certain attributes of a resource (e.g. the `name` of an `aws_elb`), the cloud provider (e.g. AWS) may 63 | treat that as an instruction to delete the old resource and a create a new one. 64 | 65 | Deleting certain types of resources (e.g. virtual servers, load balancers) can cause downtime, so when making code 66 | changes, think carefully about how to avoid that. For example, can you avoid downtime by using 67 | [create_before_destroy](https://www.terraform.io/docs/configuration/resources.html#create_before_destroy)? Or via 68 | the `terraform state` command? If so, make sure to note this in our pull request. If downtime cannot be avoided, 69 | please make sure to call that out when you submit a pull request. 70 | 71 | ## Create a pull request 72 | 73 | [Create a pull request](https://help.github.com/articles/creating-a-pull-request/) with your changes. Please make sure 74 | to include the following: 75 | 76 | 1. A description of the change, including a link to your GitHub issue. 77 | 1. The output of your automated test run, preferably in a [GitHub Gist](https://gist.github.com/). We cannot run 78 | automated tests for pull requests automatically due to [security 79 | concerns](https://circleci.com/docs/fork-pr-builds/#security-implications), so we need you to manually provide this 80 | test output so we can verify that everything is working. 81 | 1. Any notes on backwards incompatibility or downtime. 82 | 83 | ## Merge and release 84 | 85 | The maintainers for this repo will review your code and provide feedback. If everything looks good, they will merge the 86 | code and release a new version, which you'll be able to find in the [releases page](../../releases). 87 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | terraform-google-network 2 | Copyright 2019 Gruntwork, Inc. 3 | 4 | This product includes software developed at Gruntwork (https://www.gruntwork.io/). 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Sunset notice 2 | 3 | We believe there is an opportunity to create a truly outstanding developer experience for deploying to the cloud, however developing this vision requires that we temporarily limit our focus to just one cloud. Gruntwork has hundreds of customers currently using AWS, so we have temporarily suspended our maintenance efforts on this repo. Once we have implemented and validated our vision for the developer experience on the cloud, we look forward to picking this up. In the meantime, you are welcome to use this code in accordance with the open source license, however we will not be responding to GitHub Issues or Pull Requests. 4 | 5 | If you wish to be the maintainer for this project, we are open to considering that. Please contact us at support@gruntwork.io. 6 | 7 | --- 8 | 9 | [![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/gruntwork-io/terraform-google-network.svg?label=latest)](https://github.com/gruntwork-io/terraform-google-network/releases/latest) 10 | ![Terraform Version](https://img.shields.io/badge/tf-%3E%3D1.0.x-blue.svg) 11 | 12 | # Google VPC Network Modules 13 | 14 | This repo contains modules for creating [Virtual Private Cloud (VPC) networks](https://cloud.google.com/vpc/docs/vpc) on 15 | Google Cloud Platform (GCP) following best practices. 16 | 17 | ## Quickstart 18 | 19 | If you want to quickly spin up a VPC Network in GCP, you can run the example that is in the root of this repo. Check out 20 | [network-management example documentation](https://github.com/gruntwork-io/terraform-google-network/blob/master/examples/network-management) 21 | for instructions. 22 | 23 | ## What's in this repo 24 | 25 | This repo has the following folder structure: 26 | 27 | - [root](https://github.com/gruntwork-io/terraform-google-network/tree/master): The root folder contains an example of how 28 | to deploy a service-agnostic "management" VPC network in GCP. See [network-management](https://github.com/gruntwork-io/terraform-google-network/blob/master/examples/network-management) 29 | for the documentation. 30 | 31 | - [modules](https://github.com/gruntwork-io/terraform-google-network/tree/master/modules): This folder contains the 32 | main implementation code for this Module, broken down into multiple standalone submodules. 33 | 34 | The primary module is: 35 | 36 | - [vpc-network](https://github.com/gruntwork-io/terraform-google-network/tree/master/modules/vpc-network): Launch a 37 | secure VPC network on GCP. 38 | 39 | Inbound traffic to instances in the network is controlled by "access tiers", a pair of subnetwork and 40 | [network `tags`](https://cloud.google.com/vpc/docs/add-remove-network-tags). By defining an appropriate subnetwork 41 | and tag for an instance, you'll ensure that traffic to and from the instance is properly restricted. See 42 | [the Access Tier table](https://github.com/gruntwork-io/terraform-google-network/tree/master/modules/vpc-network#access-tier) 43 | for more details. 44 | 45 | There are also several supporting modules that add extra functionality on top of `vpc-network`: 46 | 47 | - [network-peering](https://github.com/gruntwork-io/terraform-google-network/tree/master/modules/network-peering): 48 | Configure peering connections between your networks, allowing you to limit access between environments and reduce 49 | the risk of production workloads being compromised. 50 | 51 | - [project-host-configuration](https://github.com/gruntwork-io/terraform-google-network/tree/master/modules/project-host-configuration): 52 | Configure your project to be a "host project" whose networks can be shared across multiple projects in the 53 | organization as part of a defense-in-depth security strategy, and to allow service-level billing across different 54 | teams within your organization. 55 | 56 | - [network-firewall](https://github.com/gruntwork-io/terraform-google-network/tree/master/modules/network-firewall): 57 | Configures the firewall rules expected by the `vpc-network` module. 58 | 59 | - [examples](https://github.com/gruntwork-io/terraform-google-network/tree/master/examples): This folder contains 60 | examples of how to use the submodules. 61 | 62 | - [test](https://github.com/gruntwork-io/terraform-google-network/tree/master/test): Automated tests for the submodules 63 | and examples. 64 | 65 | ## What's a VPC? 66 | 67 | A [Virtual Private Cloud (VPC) network](https://cloud.google.com/vpc/docs/vpc) or "network" is a private, isolated 68 | section of your cloud infrastructure. Networks are a virtual version of a physically segregated network that control 69 | connections between your resources and services both on Google Cloud and outside of it. 70 | 71 | Networks are global, and a single network can be used for all of your GCP resources across all regions. Subnetworks, 72 | ranges of IP addresses within a single region, can be used to usefully partition your private network IP space. 73 | 74 | ## What's a Module? 75 | 76 | A Module is a canonical, reusable, best-practices definition for how to run a single piece of infrastructure, such 77 | as a database or server cluster. Each Module is written using a combination of [Terraform](https://www.terraform.io/) 78 | and scripts (mostly bash) and include automated tests, documentation, and examples. It is maintained both by the open 79 | source community and companies that provide commercial support. 80 | 81 | Instead of figuring out the details of how to run a piece of infrastructure from scratch, you can reuse 82 | existing code that has been proven in production. And instead of maintaining all that infrastructure code yourself, 83 | you can leverage the work of the Module community to pick up infrastructure improvements through 84 | a version number bump. 85 | 86 | ## Who maintains this Module? 87 | 88 | This Module and its Submodules are maintained by [Gruntwork](http://www.gruntwork.io/). If you are looking for help or 89 | commercial support, send an email to 90 | [support@gruntwork.io](mailto:support@gruntwork.io?Subject=GKE%20Module). 91 | 92 | Gruntwork can help with: 93 | 94 | - Setup, customization, and support for this Module. 95 | - Modules and submodules for other types of infrastructure, such as VPCs, Docker clusters, databases, and continuous 96 | integration. 97 | - Modules and Submodules that meet compliance requirements, such as HIPAA. 98 | - Consulting & Training on AWS, Terraform, and DevOps. 99 | 100 | ## How do I contribute to this Module? 101 | 102 | Contributions are very welcome! Check out the [Contribution Guidelines](https://github.com/gruntwork-io/terraform-google-network/blob/master/CONTRIBUTING.md) 103 | for instructions. 104 | 105 | ## How is this Module versioned? 106 | 107 | This Module follows the principles of [Semantic Versioning](http://semver.org/). You can find each new release, along 108 | with the changelog, in the [Releases Page](https://github.com/gruntwork-io/terraform-google-network/releases). 109 | 110 | During initial development, the major version will be 0 (e.g., `0.x.y`), which indicates the code does not yet have a 111 | stable API. Once we hit `1.0.0`, we will make every effort to maintain a backwards compatible API and use the MAJOR, 112 | MINOR, and PATCH versions on each release to indicate any incompatibilities. 113 | 114 | ## License 115 | 116 | Please see [LICENSE](https://github.com/gruntwork-io/terraform-google-network/blob/master/LICENSE) for how the code in 117 | this repo is licensed. 118 | 119 | Copyright © 2019 Gruntwork, Inc. 120 | -------------------------------------------------------------------------------- /examples/bastion-host/README.md: -------------------------------------------------------------------------------- 1 | # Bastion Module 2 | 3 | This example shows how to use the Bastion Host module to configure access to resources internal to a network through a 4 | bastion host with OS Login. 5 | 6 | This example uses the `default` network and subnetwork to configure the bastion host, but they should be discouraged in 7 | production environments; instead, use an explicitly defined network such as a [Management Network](../network-management) 8 | or an [Application Network](../network-host-application). 9 | 10 | ## Limitations 11 | 12 | When OS Login is in use, SSH keys can't be added to instances manually. 13 | 14 | ## How do you run these examples? 15 | 16 | 1. Install [Terraform](https://www.terraform.io/). 17 | 1. Make sure you have Python installed (version 2.x) and in your `PATH`. 18 | 1. Open `variables.tf`, and fill in any required variables that don't have a 19 | default. 20 | 1. Run `terraform get`. 21 | 1. Run `terraform plan`. 22 | 1. If the plan looks good, run `terraform apply`. 23 | -------------------------------------------------------------------------------- /examples/bastion-host/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | # This module is now only being tested with Terraform 1.0.x. However, to make upgrading easier, we are setting 3 | # 0.12.26 as the minimum version, as that version added support for required_providers with source URLs, making it 4 | # forwards compatible with 1.0.x code. 5 | required_version = ">= 0.12.26" 6 | } 7 | 8 | # --------------------------------------------------------------------------------------------------------------------- 9 | # Create a Management Network for shared services 10 | # --------------------------------------------------------------------------------------------------------------------- 11 | 12 | module "management_network" { 13 | # When using these modules in your own templates, you will need to use a Git URL with a ref attribute that pins you 14 | # to a specific version of the modules, such as the following example: 15 | # source = "github.com/gruntwork-io/terraform-google-network.git//modules/vpc-network?ref=v0.1.2" 16 | source = "../../modules/vpc-network" 17 | 18 | name_prefix = var.name_prefix 19 | project = var.project 20 | region = var.region 21 | } 22 | 23 | # --------------------------------------------------------------------------------------------------------------------- 24 | # Create the bastion host to access private instances 25 | # --------------------------------------------------------------------------------------------------------------------- 26 | 27 | module "bastion_host" { 28 | # When using these modules in your own templates, you will need to use a Git URL with a ref attribute that pins you 29 | # to a specific version of the modules, such as the following example: 30 | # source = "github.com/gruntwork-io/terraform-google-network.git//modules/bastion-host?ref=v0.1.2" 31 | source = "../../modules/bastion-host" 32 | 33 | instance_name = "${var.name_prefix}-vm" 34 | subnetwork = module.management_network.public_subnetwork 35 | 36 | project = var.project 37 | zone = var.zone 38 | } 39 | 40 | # --------------------------------------------------------------------------------------------------------------------- 41 | # Create a private instance to use alongside the bastion host. 42 | # --------------------------------------------------------------------------------------------------------------------- 43 | 44 | resource "google_compute_instance" "private" { 45 | name = "${var.name_prefix}-private" 46 | machine_type = "n1-standard-1" 47 | zone = var.zone 48 | 49 | allow_stopping_for_update = true 50 | 51 | tags = [module.management_network.private] 52 | 53 | boot_disk { 54 | initialize_params { 55 | image = "debian-cloud/debian-9" 56 | } 57 | } 58 | 59 | network_interface { 60 | subnetwork = module.management_network.private_subnetwork 61 | } 62 | 63 | metadata = { 64 | enable-oslogin = "TRUE" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /examples/bastion-host/outputs.tf: -------------------------------------------------------------------------------- 1 | output "address" { 2 | description = "The public IP of the bastion host." 3 | value = module.bastion_host.address 4 | } 5 | 6 | output "private_instance" { 7 | description = "A reference (self_link) to the private instance" 8 | value = google_compute_instance.private.self_link 9 | } 10 | 11 | -------------------------------------------------------------------------------- /examples/bastion-host/variables.tf: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------------------------------------------------------------------------- 2 | # REQUIRED PARAMETERS 3 | # These parameters must be supplied when consuming this module. 4 | # --------------------------------------------------------------------------------------------------------------------- 5 | 6 | variable "project" { 7 | description = "The name of the GCP Project where all resources will be launched." 8 | type = string 9 | } 10 | 11 | variable "region" { 12 | description = "The region in which the VPC netowrk's subnetwork will be created." 13 | type = string 14 | } 15 | 16 | variable "zone" { 17 | description = "The zone in which the bastion host VM instance will be launched. Must be within the region." 18 | type = string 19 | } 20 | 21 | # --------------------------------------------------------------------------------------------------------------------- 22 | # OPTIONAL PARAMETERS 23 | # These parameters have reasonable defaults. 24 | # --------------------------------------------------------------------------------------------------------------------- 25 | 26 | variable "name_prefix" { 27 | description = "A name prefix used in resource names to ensure uniqueness across a project." 28 | type = string 29 | default = "bastion" 30 | } 31 | 32 | -------------------------------------------------------------------------------- /examples/network-host-application/README.md: -------------------------------------------------------------------------------- 1 | # Host Network for Applications 2 | 3 | This example creates a "host" network that can be shared across multiple projects, meant for multiple teams within an 4 | organization to run their services within with limited network configuration privileges. This network is appropriate to 5 | run a single environment- `production`, `staging`, etc. 6 | 7 | This example showcases a service project being created and used to run a service team's application. While it's present 8 | in a single Terraform configuration (`.tf`) file, two distinct provider aliases are used to showcase the different 9 | credentials the network operators and service teams use. 10 | 11 | ## Limitations 12 | 13 | Every network within the host project will be a host network, and the host project cannot be a service project (eg: 14 | share a network with another host project). 15 | 16 | While networks on Google Cloud Platform (GCP) are global, most resources that reside inside a VPC network live inside a 17 | regional subnetwork. This example showcases an application running within a single region. 18 | 19 | ## How do you run these examples? 20 | 21 | 1. Install [Terraform](https://www.terraform.io/). 22 | 1. Make sure you have Python installed (version 2.x) and in your `PATH`. 23 | 1. Open `variables.tf`, and fill in any required variables that don't have a 24 | default. 25 | 1. Run `terraform get`. 26 | 1. Run `terraform plan`. 27 | 1. If the plan looks good, run `terraform apply`. 28 | -------------------------------------------------------------------------------- /examples/network-host-application/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | # This module is now only being tested with Terraform 1.0.x. However, to make upgrading easier, we are setting 3 | # 0.12.26 as the minimum version, as that version added support for required_providers with source URLs, making it 4 | # forwards compatible with 1.0.x code. 5 | required_version = ">= 0.12.26" 6 | } 7 | 8 | module "application_network" { 9 | # When using these modules in your own templates, you will need to use a Git URL with a ref attribute that pins you 10 | # to a specific version of the modules, such as the following example: 11 | # source = "github.com/gruntwork-io/terraform-google-network.git//modules/vpc-network?ref=v0.1.2" 12 | source = "../../modules/vpc-network" 13 | 14 | name_prefix = var.name_prefix 15 | project = var.project 16 | region = var.region 17 | } 18 | 19 | module "project_host_configuration" { 20 | # When using these modules in your own templates, you will need to use a Git URL with a ref attribute that pins you 21 | # to a specific version of the modules, such as the following example: 22 | # source = "github.com/gruntwork-io/terraform-google-network.git//modules/project-host-configuration?ref=v0.1.2" 23 | source = "../../modules/project-host-configuration" 24 | 25 | project = var.project 26 | } 27 | -------------------------------------------------------------------------------- /examples/network-host-application/outputs.tf: -------------------------------------------------------------------------------- 1 | output "network" { 2 | description = "A reference (self_link) to the VPC network" 3 | value = module.application_network.network 4 | } 5 | 6 | # --------------------------------------------------------------------------------------------------------------------- 7 | # Public Subnetwork Outputs 8 | # --------------------------------------------------------------------------------------------------------------------- 9 | 10 | output "public_subnetwork" { 11 | description = "A reference (self_link) to the public subnetwork" 12 | value = module.application_network.public_subnetwork 13 | } 14 | 15 | output "public_subnetwork_cidr_block" { 16 | value = module.application_network.public_subnetwork_cidr_block 17 | } 18 | 19 | output "public_subnetwork_gateway" { 20 | value = module.application_network.public_subnetwork_gateway 21 | } 22 | 23 | output "public_subnetwork_secondary_cidr_block" { 24 | value = module.application_network.public_subnetwork_secondary_cidr_block 25 | } 26 | 27 | # --------------------------------------------------------------------------------------------------------------------- 28 | # Private Subnetwork Outputs 29 | # --------------------------------------------------------------------------------------------------------------------- 30 | 31 | output "private_subnetwork" { 32 | description = "A reference (self_link) to the private subnetwork" 33 | value = module.application_network.private_subnetwork 34 | } 35 | 36 | output "private_subnetwork_cidr_block" { 37 | value = module.application_network.private_subnetwork_cidr_block 38 | } 39 | 40 | output "private_subnetwork_gateway" { 41 | value = module.application_network.private_subnetwork_gateway 42 | } 43 | 44 | output "private_subnetwork_secondary_cidr_block" { 45 | value = module.application_network.private_subnetwork_secondary_cidr_block 46 | } 47 | 48 | # --------------------------------------------------------------------------------------------------------------------- 49 | # Access Tier - Network Tags 50 | # --------------------------------------------------------------------------------------------------------------------- 51 | 52 | output "public" { 53 | description = "The network tag string used for the public access tier" 54 | value = module.application_network.public 55 | } 56 | 57 | output "private" { 58 | description = "The network tag string used for the private access tier" 59 | value = module.application_network.private 60 | } 61 | 62 | output "private_persistence" { 63 | description = "The network tag string used for the private-persistence access tier" 64 | value = module.application_network.private_persistence 65 | } 66 | 67 | -------------------------------------------------------------------------------- /examples/network-host-application/variables.tf: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------------------------------------------------------------------------- 2 | # REQUIRED PARAMETERS 3 | # These parameters must be supplied when consuming this module. 4 | # --------------------------------------------------------------------------------------------------------------------- 5 | 6 | variable "project" { 7 | description = "The name of the GCP Project where all resources will be launched." 8 | type = string 9 | } 10 | 11 | variable "region" { 12 | description = "The Region in which all GCP resources will be launched." 13 | type = string 14 | } 15 | 16 | # --------------------------------------------------------------------------------------------------------------------- 17 | # OPTIONAL PARAMETERS 18 | # These parameters have reasonable defaults. 19 | # --------------------------------------------------------------------------------------------------------------------- 20 | 21 | variable "name_prefix" { 22 | description = "A name prefix used in resource names to ensure uniqueness across a project." 23 | type = string 24 | default = "application" 25 | } 26 | 27 | -------------------------------------------------------------------------------- /examples/network-management/README.md: -------------------------------------------------------------------------------- 1 | # Management Network 2 | 3 | This example creates a management network meant to be used by operators in a single project. It can be connected to 4 | application networks with network peering to access environments like `production` or `staging`. 5 | 6 | ## What are the instances included in this example? 7 | 8 | See the diagram below for a visual guide to the instances defined in this example. You can see an example of which 9 | connections between them are valid by browsing the test cases under [this example's tests](../../test/management_network_test.go) 10 | 11 | ![Network Diagram](https://raw.githubusercontent.com/gruntwork-io/terraform-google-network/master/.img/management-network-diagram.png) 12 | 13 | ## Limitations 14 | 15 | While networks on Google Cloud Platform (GCP) are global, most resources that reside inside a VPC network live inside a 16 | regional subnetwork. This example uses a single region to run the example management services. 17 | 18 | ## How do you run these examples? 19 | 20 | 1. Install [Terraform](https://www.terraform.io/). 21 | 1. Make sure you have Python installed (version 2.x) and in your `PATH`. 22 | 1. Open `variables.tf`, and fill in any required variables that don't have a 23 | default. 24 | 1. Run `terraform get`. 25 | 1. Run `terraform init`. 26 | 1. Run `terraform plan`. 27 | 1. If the plan looks good, run `terraform apply`. 28 | -------------------------------------------------------------------------------- /examples/network-management/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | # This module is now only being tested with Terraform 1.0.x. However, to make upgrading easier, we are setting 3 | # 0.12.26 as the minimum version, as that version added support for required_providers with source URLs, making it 4 | # forwards compatible with 1.0.x code. 5 | required_version = ">= 0.12.26" 6 | } 7 | 8 | # --------------------------------------------------------------------------------------------------------------------- 9 | # Create a Management Network for shared services 10 | # --------------------------------------------------------------------------------------------------------------------- 11 | 12 | module "management_network" { 13 | # When using these modules in your own templates, you will need to use a Git URL with a ref attribute that pins you 14 | # to a specific version of the modules, such as the following example: 15 | # source = "github.com/gruntwork-io/terraform-google-network.git//modules/vpc-network?ref=v0.1.2" 16 | source = "../../modules/vpc-network" 17 | 18 | name_prefix = var.name_prefix 19 | project = var.project 20 | region = var.region 21 | } 22 | 23 | # --------------------------------------------------------------------------------------------------------------------- 24 | # Create instances to tag & test connectivity with 25 | # --------------------------------------------------------------------------------------------------------------------- 26 | 27 | data "google_compute_zones" "available" { 28 | project = var.project 29 | region = var.region 30 | } 31 | 32 | // This instance acts as an arbitrary internet address for testing purposes 33 | resource "google_compute_instance" "default_network" { 34 | name = "${var.name_prefix}-default-network" 35 | machine_type = "n1-standard-1" 36 | zone = data.google_compute_zones.available.names[0] 37 | 38 | allow_stopping_for_update = true 39 | 40 | boot_disk { 41 | initialize_params { 42 | image = "debian-cloud/debian-9" 43 | } 44 | } 45 | 46 | network_interface { 47 | network = "default" 48 | 49 | access_config { 50 | // Ephemeral IP 51 | } 52 | } 53 | } 54 | 55 | resource "google_compute_instance" "public_with_ip" { 56 | name = "${var.name_prefix}-public-with-ip" 57 | machine_type = "n1-standard-1" 58 | zone = data.google_compute_zones.available.names[0] 59 | 60 | allow_stopping_for_update = true 61 | 62 | tags = [module.management_network.public] 63 | 64 | boot_disk { 65 | initialize_params { 66 | image = "debian-cloud/debian-9" 67 | } 68 | } 69 | 70 | network_interface { 71 | subnetwork = module.management_network.public_subnetwork 72 | 73 | access_config { 74 | // Ephemeral IP 75 | } 76 | } 77 | } 78 | 79 | resource "google_compute_instance" "public_without_ip" { 80 | name = "${var.name_prefix}-public-without-ip" 81 | machine_type = "n1-standard-1" 82 | zone = data.google_compute_zones.available.names[0] 83 | 84 | allow_stopping_for_update = true 85 | 86 | tags = [module.management_network.public] 87 | 88 | boot_disk { 89 | initialize_params { 90 | image = "debian-cloud/debian-9" 91 | } 92 | } 93 | 94 | network_interface { 95 | subnetwork = module.management_network.public_subnetwork 96 | } 97 | } 98 | 99 | resource "google_compute_instance" "private_public" { 100 | name = "${var.name_prefix}-private-public" 101 | machine_type = "n1-standard-1" 102 | zone = data.google_compute_zones.available.names[0] 103 | 104 | allow_stopping_for_update = true 105 | 106 | tags = [module.management_network.private] 107 | 108 | boot_disk { 109 | initialize_params { 110 | image = "debian-cloud/debian-9" 111 | } 112 | } 113 | 114 | network_interface { 115 | subnetwork = module.management_network.public_subnetwork 116 | } 117 | } 118 | 119 | resource "google_compute_instance" "private" { 120 | name = "${var.name_prefix}-private" 121 | machine_type = "n1-standard-1" 122 | zone = data.google_compute_zones.available.names[0] 123 | 124 | allow_stopping_for_update = true 125 | 126 | tags = [module.management_network.private] 127 | 128 | boot_disk { 129 | initialize_params { 130 | image = "debian-cloud/debian-9" 131 | } 132 | } 133 | 134 | network_interface { 135 | subnetwork = module.management_network.private_subnetwork 136 | } 137 | } 138 | 139 | resource "google_compute_instance" "private_persistence" { 140 | name = "${var.name_prefix}-private-persistence" 141 | machine_type = "n1-standard-1" 142 | zone = data.google_compute_zones.available.names[0] 143 | 144 | allow_stopping_for_update = true 145 | 146 | tags = [module.management_network.private_persistence] 147 | 148 | boot_disk { 149 | initialize_params { 150 | image = "debian-cloud/debian-9" 151 | } 152 | } 153 | 154 | network_interface { 155 | subnetwork = module.management_network.private_subnetwork 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /examples/network-management/outputs.tf: -------------------------------------------------------------------------------- 1 | output "network" { 2 | description = "A reference (self_link) to the VPC network" 3 | value = module.management_network.network 4 | } 5 | 6 | # --------------------------------------------------------------------------------------------------------------------- 7 | # Public Subnetwork Outputs 8 | # --------------------------------------------------------------------------------------------------------------------- 9 | 10 | output "public_subnetwork" { 11 | description = "A reference (self_link) to the public subnetwork" 12 | value = module.management_network.public_subnetwork 13 | } 14 | 15 | output "public_subnetwork_cidr_block" { 16 | value = module.management_network.public_subnetwork_cidr_block 17 | } 18 | 19 | output "public_subnetwork_gateway" { 20 | value = module.management_network.public_subnetwork_gateway 21 | } 22 | 23 | output "public_subnetwork_secondary_cidr_block" { 24 | value = module.management_network.public_subnetwork_secondary_cidr_block 25 | } 26 | 27 | # --------------------------------------------------------------------------------------------------------------------- 28 | # Private Subnetwork Outputs 29 | # --------------------------------------------------------------------------------------------------------------------- 30 | 31 | output "private_subnetwork" { 32 | description = "A reference (self_link) to the private subnetwork" 33 | value = module.management_network.private_subnetwork 34 | } 35 | 36 | output "private_subnetwork_cidr_block" { 37 | value = module.management_network.private_subnetwork_cidr_block 38 | } 39 | 40 | output "private_subnetwork_gateway" { 41 | value = module.management_network.private_subnetwork_gateway 42 | } 43 | 44 | output "private_subnetwork_secondary_cidr_block" { 45 | value = module.management_network.private_subnetwork_secondary_cidr_block 46 | } 47 | 48 | # --------------------------------------------------------------------------------------------------------------------- 49 | # Access Tier - Network Tags 50 | # --------------------------------------------------------------------------------------------------------------------- 51 | 52 | output "public" { 53 | description = "The network tag string used for the public access tier" 54 | value = module.management_network.public 55 | } 56 | 57 | output "private" { 58 | description = "The network tag string used for the private access tier" 59 | value = module.management_network.private 60 | } 61 | 62 | output "private_persistence" { 63 | description = "The network tag string used for the private-persistence access tier" 64 | value = module.management_network.private_persistence 65 | } 66 | 67 | # --------------------------------------------------------------------------------------------------------------------- 68 | # Instance Info (primarily for testing) 69 | # --------------------------------------------------------------------------------------------------------------------- 70 | 71 | output "instance_default_network" { 72 | description = "A reference (self link) to an instance in the default network. Note that the default network allows SSH." 73 | value = google_compute_instance.default_network.self_link 74 | } 75 | 76 | output "instance_public_with_ip" { 77 | description = "A reference (self link) to the instance tagged as public in a public subnetwork with an external IP" 78 | value = google_compute_instance.public_with_ip.self_link 79 | } 80 | 81 | output "instance_public_without_ip" { 82 | description = "A reference (self link) to the instance tagged as public in a public subnetwork without an internet IP" 83 | value = google_compute_instance.public_without_ip.self_link 84 | } 85 | 86 | output "instance_private_public" { 87 | description = "A reference (self link) to the instance tagged as private in a public subnetwork" 88 | value = google_compute_instance.private_public.self_link 89 | } 90 | 91 | output "instance_private" { 92 | description = "A reference (self link) to the instance tagged as private in a private subnetwork" 93 | value = google_compute_instance.private.self_link 94 | } 95 | 96 | output "instance_private_persistence" { 97 | description = "A reference (self link) to the instance tagged as private-persistence in a private subnetwork" 98 | value = google_compute_instance.private_persistence.self_link 99 | } 100 | 101 | -------------------------------------------------------------------------------- /examples/network-management/variables.tf: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------------------------------------------------------------------------- 2 | # REQUIRED PARAMETERS 3 | # These parameters must be supplied when consuming this module. 4 | # --------------------------------------------------------------------------------------------------------------------- 5 | 6 | variable "project" { 7 | description = "The name of the GCP Project where all resources will be launched." 8 | type = string 9 | } 10 | 11 | variable "region" { 12 | description = "The Region in which all GCP resources will be launched." 13 | type = string 14 | } 15 | 16 | # --------------------------------------------------------------------------------------------------------------------- 17 | # OPTIONAL PARAMETERS 18 | # These parameters have reasonable defaults. 19 | # --------------------------------------------------------------------------------------------------------------------- 20 | 21 | variable "name_prefix" { 22 | description = "A name prefix used in resource names to ensure uniqueness across a project." 23 | type = string 24 | default = "management" 25 | } 26 | 27 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | # This module is now only being tested with Terraform 1.0.x. However, to make upgrading easier, we are setting 3 | # 0.12.26 as the minimum version, as that version added support for required_providers with source URLs, making it 4 | # forwards compatible with 1.0.x code. 5 | required_version = ">= 0.12.26" 6 | } 7 | 8 | # --------------------------------------------------------------------------------------------------------------------- 9 | # Create a Management Network for shared services 10 | # --------------------------------------------------------------------------------------------------------------------- 11 | 12 | module "management_network" { 13 | # When using these modules in your own templates, you will need to use a Git URL with a ref attribute that pins you 14 | # to a specific version of the modules, such as the following example: 15 | # source = "github.com/gruntwork-io/terraform-google-network.git//modules/vpc-network?ref=v0.1.2" 16 | source = "./modules/vpc-network" 17 | 18 | name_prefix = var.name_prefix 19 | project = var.project 20 | region = var.region 21 | } 22 | 23 | # --------------------------------------------------------------------------------------------------------------------- 24 | # Create instances to tag & test connectivity with 25 | # --------------------------------------------------------------------------------------------------------------------- 26 | 27 | data "google_compute_zones" "available" { 28 | project = var.project 29 | region = var.region 30 | } 31 | 32 | // This instance acts as an arbitrary internet address for testing purposes 33 | resource "google_compute_instance" "default_network" { 34 | name = "${var.name_prefix}-default-network" 35 | machine_type = "n1-standard-1" 36 | zone = data.google_compute_zones.available.names[0] 37 | project = var.project 38 | 39 | allow_stopping_for_update = true 40 | 41 | boot_disk { 42 | initialize_params { 43 | image = "debian-cloud/debian-9" 44 | } 45 | } 46 | 47 | network_interface { 48 | network = "default" 49 | 50 | access_config { 51 | // Ephemeral IP 52 | } 53 | } 54 | } 55 | 56 | resource "google_compute_instance" "public_with_ip" { 57 | name = "${var.name_prefix}-public-with-ip" 58 | machine_type = "n1-standard-1" 59 | zone = data.google_compute_zones.available.names[0] 60 | project = var.project 61 | 62 | allow_stopping_for_update = true 63 | 64 | tags = [module.management_network.public] 65 | 66 | boot_disk { 67 | initialize_params { 68 | image = "debian-cloud/debian-9" 69 | } 70 | } 71 | 72 | network_interface { 73 | subnetwork = module.management_network.public_subnetwork 74 | 75 | access_config { 76 | // Ephemeral IP 77 | } 78 | } 79 | } 80 | 81 | resource "google_compute_instance" "public_without_ip" { 82 | name = "${var.name_prefix}-public-without-ip" 83 | machine_type = "n1-standard-1" 84 | zone = data.google_compute_zones.available.names[0] 85 | project = var.project 86 | 87 | allow_stopping_for_update = true 88 | 89 | tags = [module.management_network.public] 90 | 91 | boot_disk { 92 | initialize_params { 93 | image = "debian-cloud/debian-9" 94 | } 95 | } 96 | 97 | network_interface { 98 | subnetwork = module.management_network.public_subnetwork 99 | } 100 | } 101 | 102 | resource "google_compute_instance" "private_public" { 103 | name = "${var.name_prefix}-private-public" 104 | machine_type = "n1-standard-1" 105 | zone = data.google_compute_zones.available.names[0] 106 | project = var.project 107 | 108 | allow_stopping_for_update = true 109 | 110 | tags = [module.management_network.private] 111 | 112 | boot_disk { 113 | initialize_params { 114 | image = "debian-cloud/debian-9" 115 | } 116 | } 117 | 118 | network_interface { 119 | subnetwork = module.management_network.public_subnetwork 120 | } 121 | } 122 | 123 | resource "google_compute_instance" "private" { 124 | name = "${var.name_prefix}-private" 125 | machine_type = "n1-standard-1" 126 | zone = data.google_compute_zones.available.names[0] 127 | project = var.project 128 | 129 | allow_stopping_for_update = true 130 | 131 | tags = [module.management_network.private] 132 | 133 | boot_disk { 134 | initialize_params { 135 | image = "debian-cloud/debian-9" 136 | } 137 | } 138 | 139 | network_interface { 140 | subnetwork = module.management_network.private_subnetwork 141 | } 142 | } 143 | 144 | resource "google_compute_instance" "private_persistence" { 145 | name = "${var.name_prefix}-private-persistence" 146 | machine_type = "n1-standard-1" 147 | zone = data.google_compute_zones.available.names[0] 148 | project = var.project 149 | 150 | allow_stopping_for_update = true 151 | 152 | tags = [module.management_network.private_persistence] 153 | 154 | boot_disk { 155 | initialize_params { 156 | image = "debian-cloud/debian-9" 157 | } 158 | } 159 | 160 | network_interface { 161 | subnetwork = module.management_network.private_subnetwork 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /modules/bastion-host/README.md: -------------------------------------------------------------------------------- 1 | # Bastion Host Module 2 | 3 | The Bastion Host module is used to configure a [Google Compute Engine (GCE) VM Instance](https://cloud.google.com/compute/docs/instances/) 4 | as a bastion host or "jumpbox", allowing access to private instances inside your VPC network. 5 | 6 | Bastion hosts configured with this module are set up so access to the bastion host is controlled by the user's Google 7 | identity and the GCP IAM roles it's been granted through [OS Login](https://cloud.google.com/compute/docs/oslogin/). SSH 8 | keys for individual instances are unable to be managed by the user; instead, GCP manages access through fine-grained, 9 | revokable IAM roles. OS Login is the recommended way to manage many users across multiple instances or projects on GCP. 10 | 11 | ## How do I SSH to the host? 12 | 13 | You can access a VM instance through OS Login with normal SSH using your OS Login "profile" - the username and SSH key 14 | connected to your Google identity. See [managing instance access](https://cloud.google.com/compute/docs/instances/managing-instance-access) 15 | for a full reference. 16 | 17 | Assuming that you've configured your default SSH key to be our OS Login key, that means you can SSH to an instance with: 18 | 19 | ``` 20 | ssh your_identity_email_address_example.com@N.N.N.N 21 | ``` 22 | 23 | ### What is an OS Login profile? 24 | 25 | OS Login profiles are made up of a username and a single well-known and SSH key per user. Generally, this username will 26 | take the form of the user's email with special characters replaced by underscores. So, `alice-doe@example.com` would 27 | become `alice_doe_example_com`. Organization admin can change this username; if that's the case, users can confirm their 28 | username with the `posixAccounts.username` field in `gcloud compute os-login describe-profile`. 29 | 30 | The OS Login SSH key can be changed by the user with `gcloud compute os-login ssh-keys add --key-file [PUB_KEY_FILE_PATH]`. 31 | Alternatively, OS Login profiles can be managed through the [OS Login API](https://cloud.google.com/compute/docs/oslogin/rest/) 32 | or the GSuite [Directory API](https://developers.google.com/admin-sdk/directory/v1/reference/). See 33 | [adding SSH keys to a user account](https://cloud.google.com/compute/docs/instances/managing-instance-access#add_oslogin_keys) 34 | for a full reference. 35 | 36 | ## Can I configure access for users outside of my organization? 37 | 38 | Provided a user has a Google identity - such as a GCP account or a `@gmail.com` email, you can 39 | [configure access for external users](https://cloud.google.com/compute/docs/instances/managing-instance-access#external_user) 40 | using the IAM role `roles/compute.osLoginExternalUser` 41 | 42 | ## If I remove a user's IAM role, when is access revoked? 43 | 44 | When a user's IAM permissions are removed, ongoing sessions continue. When they try to access an instance using OS Login 45 | again, their permissions will be evaluated and they will be unable to connect. 46 | -------------------------------------------------------------------------------- /modules/bastion-host/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | # This module is now only being tested with Terraform 1.0.x. However, to make upgrading easier, we are setting 3 | # 0.12.26 as the minimum version, as that version added support for required_providers with source URLs, making it 4 | # forwards compatible with 1.0.x code. 5 | required_version = ">= 0.12.26" 6 | } 7 | 8 | # --------------------------------------------------------------------------------------------------------------------- 9 | # Create an instance with OS Login configured to use as a bastion host. 10 | # --------------------------------------------------------------------------------------------------------------------- 11 | 12 | resource "google_compute_instance" "bastion_host" { 13 | project = var.project 14 | name = var.instance_name 15 | machine_type = var.machine_type 16 | zone = var.zone 17 | 18 | tags = [var.tag] 19 | 20 | boot_disk { 21 | initialize_params { 22 | image = var.source_image 23 | } 24 | } 25 | 26 | network_interface { 27 | subnetwork = var.subnetwork 28 | 29 | // If var.static_ip is set use that IP, otherwise this will generate an ephemeral IP 30 | access_config { 31 | nat_ip = var.static_ip 32 | } 33 | } 34 | 35 | metadata_startup_script = var.startup_script 36 | 37 | metadata = { 38 | enable-oslogin = "TRUE" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /modules/bastion-host/outputs.tf: -------------------------------------------------------------------------------- 1 | output "instance" { 2 | description = "A reference (self_link) to the bastion host's VM instance" 3 | value = google_compute_instance.bastion_host.self_link 4 | } 5 | 6 | output "address" { 7 | description = "The public IP of the bastion host." 8 | value = google_compute_instance.bastion_host.network_interface[0].access_config[0].nat_ip 9 | } 10 | 11 | output "private_ip" { 12 | description = "The private IP of the bastion host." 13 | value = google_compute_instance.bastion_host.network_interface[0].network_ip 14 | } 15 | -------------------------------------------------------------------------------- /modules/bastion-host/variables.tf: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------------------------------------------------------------------------- 2 | # REQUIRED PARAMETERS 3 | # These variables are expected to be passed in by the operator 4 | # --------------------------------------------------------------------------------------------------------------------- 5 | 6 | variable "instance_name" { 7 | description = "The name of the VM instance" 8 | type = string 9 | } 10 | 11 | variable "subnetwork" { 12 | description = "A reference (self_link) to the subnetwork to place the bastion host in" 13 | type = string 14 | } 15 | 16 | variable "project" { 17 | description = "The project to create the bastion host in. Must match the subnetwork project." 18 | type = string 19 | } 20 | 21 | variable "zone" { 22 | description = "The zone to create the bastion host in. Must be within the subnetwork region." 23 | type = string 24 | } 25 | 26 | # --------------------------------------------------------------------------------------------------------------------- 27 | # OPTIONAL PARAMETERS 28 | # Generally, these values won't need to be changed. 29 | # --------------------------------------------------------------------------------------------------------------------- 30 | 31 | variable "tag" { 32 | description = "The GCP network tag to apply to the bastion host for firewall rules. Defaults to 'public', the expected public tag of this module." 33 | type = string 34 | default = "public" 35 | } 36 | 37 | variable "machine_type" { 38 | description = "The machine type of the instance." 39 | type = string 40 | default = "e2-micro" 41 | } 42 | 43 | variable "source_image" { 44 | description = "The source image to build the VM using. Specified by path reference or by {{project}}/{{image-family}}" 45 | type = string 46 | default = "ubuntu-os-cloud/ubuntu-1804-lts" 47 | } 48 | 49 | variable "startup_script" { 50 | description = "The script to be executed when the bastion host starts. It can be used to install additional software and/or configure the host." 51 | type = string 52 | default = "" 53 | } 54 | 55 | variable "static_ip" { 56 | description = "A static IP address to attach to the instance. The default will allocate an ephemeral IP" 57 | type = string 58 | default = null 59 | } 60 | -------------------------------------------------------------------------------- /modules/network-firewall/README.md: -------------------------------------------------------------------------------- 1 | # Network Firewall Module 2 | 3 | The Network Firewall module is used to configure a standard set of [firewall rules](https://cloud.google.com/vpc/docs/firewalls) 4 | for your network created using the [vpc-network](../vpc-network) module. 5 | 6 | Firewall rules on Google Cloud Platform (GCP) are created at the network level but act on each instance; if traffic is 7 | restricted between instances by the rule, they will be unable to communicate even if they're in the same network or 8 | subnetwork. 9 | 10 | The default firewall rules on GCP block inbound traffic and allow outbound traffic. Firewall rules are stateful; if a 11 | connection is allowed between a source and a target or a target and a destination, all subsequent traffic in either 12 | direction will be allowed as long as the connection is active. 13 | 14 | This module adds rules for 4 [network `tags`](https://cloud.google.com/vpc/docs/add-remove-network-tags) that can be 15 | applied to instances, similar to the division between subnetworks. 16 | 17 | * `public` - allow inbound traffic from all sources 18 | 19 | * `public-restricted` - allow inbound traffic from specific subnetworks on the internet 20 | 21 | * `private` - allow inbound traffic from within this network 22 | 23 | * `private-persistence` - allow inbound traffic from within this network, excluding instances tagged `public` 24 | 25 | Untagged instances will be unable to communicate with any other resources due to the implicit firewall rules. 26 | -------------------------------------------------------------------------------- /modules/network-firewall/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | # This module is now only being tested with Terraform 1.0.x. However, to make upgrading easier, we are setting 3 | # 0.12.26 as the minimum version, as that version added support for required_providers with source URLs, making it 4 | # forwards compatible with 1.0.x code. 5 | required_version = ">= 0.12.26" 6 | } 7 | 8 | data "google_compute_subnetwork" "public_subnetwork" { 9 | self_link = var.public_subnetwork 10 | } 11 | 12 | data "google_compute_subnetwork" "private_subnetwork" { 13 | self_link = var.private_subnetwork 14 | } 15 | 16 | // Define tags as locals so they can be interpolated off of + exported 17 | locals { 18 | public = "public" 19 | public_restricted = "public-restricted" 20 | private = "private" 21 | private_persistence = "private-persistence" 22 | } 23 | 24 | # --------------------------------------------------------------------------------------------------------------------- 25 | # public - allow ingress from anywhere 26 | # --------------------------------------------------------------------------------------------------------------------- 27 | 28 | resource "google_compute_firewall" "public_allow_all_inbound" { 29 | name = "${var.name_prefix}-public-allow-ingress" 30 | 31 | project = var.project 32 | network = var.network 33 | 34 | target_tags = [local.public] 35 | direction = "INGRESS" 36 | source_ranges = ["0.0.0.0/0"] 37 | 38 | priority = "1000" 39 | 40 | allow { 41 | protocol = "all" 42 | } 43 | } 44 | 45 | # --------------------------------------------------------------------------------------------------------------------- 46 | # public - allow ingress from specific sources 47 | # --------------------------------------------------------------------------------------------------------------------- 48 | 49 | resource "google_compute_firewall" "public_restricted_allow_inbound" { 50 | 51 | count = length(var.allowed_public_restricted_subnetworks) > 0 ? 1 : 0 52 | 53 | name = "${var.name_prefix}-public-restricted-allow-ingress" 54 | 55 | project = var.project 56 | network = var.network 57 | 58 | target_tags = [local.public_restricted] 59 | direction = "INGRESS" 60 | source_ranges = var.allowed_public_restricted_subnetworks 61 | 62 | priority = "1000" 63 | 64 | allow { 65 | protocol = "all" 66 | } 67 | } 68 | 69 | 70 | # --------------------------------------------------------------------------------------------------------------------- 71 | # private - allow ingress from within this network 72 | # --------------------------------------------------------------------------------------------------------------------- 73 | 74 | resource "google_compute_firewall" "private_allow_all_network_inbound" { 75 | name = "${var.name_prefix}-private-allow-ingress" 76 | 77 | project = var.project 78 | network = var.network 79 | 80 | target_tags = [local.private] 81 | direction = "INGRESS" 82 | 83 | source_ranges = [ 84 | data.google_compute_subnetwork.public_subnetwork.ip_cidr_range, 85 | data.google_compute_subnetwork.public_subnetwork.secondary_ip_range[0].ip_cidr_range, 86 | data.google_compute_subnetwork.public_subnetwork.secondary_ip_range[1].ip_cidr_range, 87 | data.google_compute_subnetwork.private_subnetwork.ip_cidr_range, 88 | data.google_compute_subnetwork.private_subnetwork.secondary_ip_range[0].ip_cidr_range, 89 | ] 90 | 91 | priority = "1000" 92 | 93 | allow { 94 | protocol = "all" 95 | } 96 | } 97 | 98 | # --------------------------------------------------------------------------------------------------------------------- 99 | # private-persistence - allow ingress from `private` and `private-persistence` instances in this network 100 | # --------------------------------------------------------------------------------------------------------------------- 101 | 102 | resource "google_compute_firewall" "private_allow_restricted_network_inbound" { 103 | name = "${var.name_prefix}-allow-restricted-inbound" 104 | 105 | project = var.project 106 | network = var.network 107 | 108 | target_tags = [local.private_persistence] 109 | direction = "INGRESS" 110 | 111 | # source_tags is implicitly within this network; tags are only applied to instances that rest within the same network 112 | source_tags = [local.private, local.private_persistence] 113 | 114 | priority = "1000" 115 | 116 | allow { 117 | protocol = "all" 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /modules/network-firewall/outputs.tf: -------------------------------------------------------------------------------- 1 | output "public" { 2 | description = "The string of the public tag" 3 | value = local.public 4 | } 5 | 6 | output "public_restricted" { 7 | description = "The string of the public tag" 8 | value = local.public_restricted 9 | } 10 | 11 | output "private" { 12 | description = "The string of the private tag" 13 | value = local.private 14 | } 15 | 16 | output "private_persistence" { 17 | description = "The string of the private-persistence tag" 18 | value = local.private_persistence 19 | } 20 | -------------------------------------------------------------------------------- /modules/network-firewall/variables.tf: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------------------------------------------------------------------------- 2 | # REQUIRED PARAMETERS 3 | # These variables are expected to be passed in by the operator 4 | # --------------------------------------------------------------------------------------------------------------------- 5 | 6 | variable "network" { 7 | description = "A reference (self_link) to the VPC network to apply firewall rules to" 8 | type = string 9 | } 10 | 11 | variable "public_subnetwork" { 12 | description = "A reference (self_link) to the public subnetwork of the network" 13 | type = string 14 | } 15 | 16 | variable "allowed_public_restricted_subnetworks" { 17 | description = "The public networks that is allowed access to the public_restricted subnetwork of the network" 18 | default = [] 19 | type = list(string) 20 | } 21 | 22 | variable "private_subnetwork" { 23 | description = "A reference (self_link) to the private subnetwork of the network" 24 | type = string 25 | } 26 | 27 | variable "project" { 28 | description = "The project to create the firewall rules in. Must match the network project." 29 | type = string 30 | } 31 | 32 | variable "name_prefix" { 33 | description = "A name prefix used in resource names to ensure uniqueness across a project." 34 | type = string 35 | } 36 | -------------------------------------------------------------------------------- /modules/network-peering/README.md: -------------------------------------------------------------------------------- 1 | # Network Peering Module 2 | 3 | The Network Peering module creates [VPC network peering connections](https://cloud.google.com/vpc/docs/vpc-peering) 4 | between networks. Normal networks are isolated, but some traffic may need to flow between them, such as allowing DevOps 5 | tools running in a [management network](https://github.com/gruntwork-io/terraform-google-network/tree/master/examples/network-management) 6 | to talk to apps or services in an [application network](https://github.com/gruntwork-io/terraform-google-network/tree/master/examples/network-host-application). 7 | 8 | When using a [host project](https://github.com/gruntwork-io/terraform-google-network/tree/master/modules/project-host-configuration) with component host networks, peerings should be made in the 9 | host project. 10 | 11 | ## How do you use this module? 12 | 13 | * See the [root README](https://github.com/gruntwork-io/terraform-google-network/blob/master/README.md) for instructions 14 | on using Terraform modules. 15 | * See the [examples](https://github.com/gruntwork-io/terraform-google-network/tree/master/examples) folder for example 16 | usage. 17 | * See [variables.tf](https://github.com/gruntwork-io/terraform-google-network/blob/master/modules/network-peering/variables.tf) 18 | for all the variables you can set on this module. 19 | -------------------------------------------------------------------------------- /modules/network-peering/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | # This module is now only being tested with Terraform 1.0.x. However, to make upgrading easier, we are setting 3 | # 0.12.26 as the minimum version, as that version added support for required_providers with source URLs, making it 4 | # forwards compatible with 1.0.x code. 5 | required_version = ">= 0.12.26" 6 | } 7 | 8 | resource "google_compute_network_peering" "first" { 9 | name = "${var.name_prefix}-first" 10 | network = var.first_network 11 | peer_network = var.second_network 12 | } 13 | 14 | resource "google_compute_network_peering" "second" { 15 | name = "${var.name_prefix}-second" 16 | network = var.second_network 17 | peer_network = var.first_network 18 | } 19 | -------------------------------------------------------------------------------- /modules/network-peering/variables.tf: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------------------------------------------------------------------------- 2 | # REQUIRED PARAMETERS 3 | # These variables are expected to be passed in by the operator 4 | # --------------------------------------------------------------------------------------------------------------------- 5 | 6 | variable "first_network" { 7 | description = "The self_link reference to the first network to peer" 8 | type = string 9 | } 10 | 11 | variable "second_network" { 12 | description = "The self_link reference to the second network to peer" 13 | type = string 14 | } 15 | 16 | # --------------------------------------------------------------------------------------------------------------------- 17 | # OPTIONAL PARAMETERS 18 | # Generally, these values won't need to be changed. 19 | # --------------------------------------------------------------------------------------------------------------------- 20 | 21 | variable "name_prefix" { 22 | description = "A name prefix used in resource names to ensure uniqueness across a project." 23 | type = string 24 | default = "peering" 25 | } 26 | 27 | -------------------------------------------------------------------------------- /modules/project-host-configuration/README.md: -------------------------------------------------------------------------------- 1 | # Project Host Configuration Module 2 | 3 | The Project Host Configuration module is used to turn a project into a ["host" project](https://cloud.google.com/vpc/docs/shared-vpc#shared_vpc_host_project_and_service_project_associations) 4 | whose networks are "host" networks, able to be shared across multiple service projects. 5 | 6 | ## How do you use this module? 7 | 8 | * See the [root README](https://github.com/gruntwork-io/terraform-google-network/blob/master/README.md) for instructions 9 | on using Terraform modules. 10 | * See the [examples](https://github.com/gruntwork-io/terraform-google-network/tree/master/examples) folder for example 11 | usage. 12 | * See [variables.tf](https://github.com/gruntwork-io/terraform-google-network/blob/master/modules/project-host-configuration/variables.tf) 13 | for all the variables you can set on this module. 14 | 15 | ## How is a "host" network different than a normal network? 16 | A host network is a [Shared VPC](https://cloud.google.com/vpc/docs/shared-vpc) concept; shared VPC is a feature that 17 | allows organizations to define a network inside a single project, and to share that network across multiple projects 18 | within their organization. 19 | 20 | Once a project enables shared VPC- becoming a "host" project- every network in that project becomes a host network that 21 | can be shared across multiple "service" projects. The host network(s) can only be managed from the host project, 22 | implementing the security best practice of least privilege for network administration 23 | 24 | ## What is a service project? 25 | A service project is a project that has been attached to a host project by the host project administrators. Service 26 | projects cannot also become host projects, and a service project may only connect to a single host project. 27 | 28 | Service projects have first-class access to usage of host networks, and can also define their own networks. In general, 29 | service projects should exclusively use host networks. Billing for resources in a Shared VPC network is generally 30 | attributed to the service project. See the [service project billing](https://cloud.google.com/vpc/docs/shared-vpc#billing) 31 | docs for details. 32 | -------------------------------------------------------------------------------- /modules/project-host-configuration/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | # This module is now only being tested with Terraform 1.0.x. However, to make upgrading easier, we are setting 3 | # 0.12.26 as the minimum version, as that version added support for required_providers with source URLs, making it 4 | # forwards compatible with 1.0.x code. 5 | required_version = ">= 0.12.26" 6 | } 7 | 8 | resource "google_compute_shared_vpc_host_project" "host" { 9 | project = var.project 10 | } 11 | -------------------------------------------------------------------------------- /modules/project-host-configuration/variables.tf: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------------------------------------------------------------------------- 2 | # REQUIRED PARAMETERS 3 | # These variables are expected to be passed in by the operator 4 | # --------------------------------------------------------------------------------------------------------------------- 5 | 6 | variable "project" { 7 | description = "The project ID for the project to enable as a host project" 8 | type = string 9 | } 10 | -------------------------------------------------------------------------------- /modules/vpc-network/README.md: -------------------------------------------------------------------------------- 1 | # VPC Network Module 2 | 3 | The VPC Network module creates a [Virtual Private Cloud (VPC) network](https://cloud.google.com/vpc/docs/using-vpc) on 4 | Google Cloud Platform (GCP) following best practices. 5 | 6 | When configuring networks for your organisation, you should generally define these "types" of networks: 7 | 8 | - `management` - a single network that runs your internal services such as DevOps services like Jenkins, peering to your 9 | application networks. This network should run in the same project as its services. 10 | 11 | - `application`- a network per environment (`staging`, `production`, etc.), running multiple services owned by multiple 12 | teams. Most application networks should be [host projects](https://github.com/gruntwork-io/terraform-google-network/tree/master/modules/project-host-configuration), 13 | allowing you to share a single network across multiple "service" projects that each contain a single application or 14 | service. See [host project](https://github.com/gruntwork-io/terraform-google-network/tree/master/modules/project-host-configuration) 15 | for more details. 16 | 17 | For more details on specific configuration of each type, see the [examples](https://github.com/gruntwork-io/terraform-google-network/tree/master/examples) 18 | provided in this module. 19 | 20 | ## How do you use this module? 21 | 22 | - See the [root README](https://github.com/gruntwork-io/terraform-google-network/blob/master/README.md) for instructions 23 | on using Terraform modules. 24 | - See the [examples](https://github.com/gruntwork-io/terraform-google-network/tree/master/examples) folder for example 25 | usage. 26 | - See [variables.tf](https://github.com/gruntwork-io/terraform-google-network/blob/master/modules/vpc-network/variables.tf) 27 | for all the variables you can set on this module. 28 | - See [outputs.tf](https://github.com/gruntwork-io/terraform-google-network/blob/master/modules/vpc-network/outputs.tf) 29 | for all the variables that are outputted by this module. 30 | 31 | ## What is a VPC network? 32 | 33 | See [the repo's root README](https://github.com/gruntwork-io/terraform-google-network/blob/master/README.md) to learn more about VPC networks. 34 | 35 | ## Access Tier 36 | 37 | In this module, there are several "access tiers"- the pair of an instance's subnetwork and [network `tags`](https://cloud.google.com/vpc/docs/add-remove-network-tags). 38 | By placing instances in the appropriate subnetworks and using restrictive network tags, you can guarantee that only the 39 | intended traffic is able to reach your infrastructure. 40 | 41 | Instances in the network must be tagged with the following network tags in order for inbound traffic to be allowed to 42 | reach them. All other inbound traffic is denied, including internal traffic; 43 | 44 | - `public` - allow inbound traffic from all sources 45 | 46 | - `public-restricted` - allow inbound traffic from specific subnetworks on the internet 47 | 48 | - `private` - allow inbound traffic from within this network 49 | 50 | - `private-persistence` - allow inbound traffic from tagged sources within this network, excluding instances tagged 51 | `public` 52 | 53 | See the [network-firewall](https://github.com/gruntwork-io/terraform-google-network/tree/master/modules/network-firewall) 54 | submodule for more details. 55 | 56 | A VPC network defines two subnetworks instances can reside in; 57 | 58 | - `public` - instances are able to communicate over the public internet through Cloud NAT if an external IP was not 59 | provided 60 | 61 | - `private` - instances are exclusively able to communicate within your network or with private Google services if an 62 | external IP was not provided 63 | 64 | ## What is Private Google Access? 65 | 66 | [Private Google Access](https://cloud.google.com/vpc/docs/configure-private-google-access) is a GCP feature where 67 | instances within your network that don't have public IP addresses assigned can access most Google APIs and services 68 | without NAT or a bastion. Private Google Access is enabled at the subnetwork level, and subnetworks created using this 69 | module will have Private Google Access enabled. 70 | 71 | ## What is alias IP? 72 | 73 | An [alias IP range](https://cloud.google.com/vpc/docs/alias-ip) allows you to use a different IP address for each of 74 | your services such as container pods on a VM. 75 | 76 | When using alias IP, anti-spoofing checks are performed against traffic, ensuring that traffic exiting VMs uses VM IP 77 | addresses and pod IP addresses as source addresses, and that that VMs do not send traffic with arbitrary source IP 78 | addresses. 79 | 80 | When you configure separate address ranges for VMs and their services, you can set up firewall rules for alias ranges 81 | separately from the primary range. For example, you can allow certain traffic for container pods and deny similar 82 | traffic for the VM's primary IP address. 83 | 84 | ### How is a secondary range connected to an alias IP range? 85 | 86 | While all subnetworks have a primary address range, the range VM's internal addresses are drawn from, and alias IPs 87 | could be drawn from the primary range, subnetworks can also define a secondary range for exclusive use with alias IP 88 | ranges. This simplifies constructing firewall rules, and helps an organisation ensure that services are running securely 89 | in containers distinct from the VM. 90 | 91 | This module automatically configures secondary ranges for use with alias IP. 92 | 93 | ## What are VPC Flow Logs? 94 | 95 | [VPC Flow Logs](https://cloud.google.com/vpc/docs/using-flow-logs) are a feature where subnetworks in your network will 96 | have their traffic flow between VM instances sampled and sent to Stackdriver; there, you can use them for a variety of 97 | purposes including forensics and expense optimization. Only TCP and UDP traffic is logged. Flow logging is enabled by 98 | default in this module, and can be disabled by setting the `log_config` variable to `null`. 99 | 100 | ## Network Architecture 101 | 102 | This network architecture is inspired by the VPC Architecture described by Ben Whaley in his blog post 103 | [A Reference VPC Architecture](https://www.whaletech.co/2014/10/02/reference-vpc-architecture.html). Notably, while the 104 | reference architecture made the distinction between machines by subnetwork tier, this module uses [network `tags`](https://cloud.google.com/vpc/docs/add-remove-network-tags) 105 | to do so. 106 | 107 | Additionally, the hard distinction between "Application" and "Management" in terms of tiers has been removed- either 108 | can include or exclude "persistence" instances. Instead, Whaley's "Application" networks are generally host networks 109 | with attached service projects, and "Management" networks should be used with services inside the same project. 110 | 111 | ## Gotchas 112 | 113 | In order to allow any inter-network communication, instances _must_ be tagged with one of `public`, `private`, or 114 | `private-persistence`. See the [network-firewall](https://github.com/gruntwork-io/terraform-google-network/tree/master/modules/network-firewall) 115 | submodule for more details. 116 | -------------------------------------------------------------------------------- /modules/vpc-network/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | # This module is now only being tested with Terraform 1.0.x. However, to make upgrading easier, we are setting 3 | # 0.12.26 as the minimum version, as that version added support for required_providers with source URLs, making it 4 | # forwards compatible with 1.0.x code. 5 | required_version = ">= 0.12.26" 6 | } 7 | 8 | # --------------------------------------------------------------------------------------------------------------------- 9 | # Create the Network & corresponding Router to attach other resources to 10 | # Networks that preserve the default route are automatically enabled for Private Google Access to GCP services 11 | # provided subnetworks each opt-in; in general, Private Google Access should be the default. 12 | # --------------------------------------------------------------------------------------------------------------------- 13 | 14 | resource "google_compute_network" "vpc" { 15 | name = "${var.name_prefix}-network" 16 | project = var.project 17 | 18 | # Always define custom subnetworks- one subnetwork per region isn't useful for an opinionated setup 19 | auto_create_subnetworks = "false" 20 | 21 | # A global routing mode can have an unexpected impact on load balancers; always use a regional mode 22 | routing_mode = "REGIONAL" 23 | } 24 | 25 | resource "google_compute_router" "vpc_router" { 26 | name = "${var.name_prefix}-router" 27 | 28 | project = var.project 29 | region = var.region 30 | network = google_compute_network.vpc.self_link 31 | } 32 | 33 | # --------------------------------------------------------------------------------------------------------------------- 34 | # Public Subnetwork Config 35 | # Public internet access for instances with addresses is automatically configured by the default gateway for 0.0.0.0/0 36 | # External access is configured with Cloud NAT, which subsumes egress traffic for instances without external addresses 37 | # --------------------------------------------------------------------------------------------------------------------- 38 | 39 | resource "google_compute_subnetwork" "vpc_subnetwork_public" { 40 | name = "${var.name_prefix}-subnetwork-public" 41 | 42 | project = var.project 43 | region = var.region 44 | network = google_compute_network.vpc.self_link 45 | 46 | private_ip_google_access = true 47 | ip_cidr_range = cidrsubnet(var.cidr_block, var.cidr_subnetwork_width_delta, 0) 48 | 49 | secondary_ip_range { 50 | range_name = var.public_subnetwork_secondary_range_name 51 | ip_cidr_range = cidrsubnet( 52 | var.secondary_cidr_block, 53 | var.secondary_cidr_subnetwork_width_delta, 54 | 0 55 | ) 56 | } 57 | 58 | secondary_ip_range { 59 | range_name = var.public_services_secondary_range_name 60 | ip_cidr_range = var.public_services_secondary_cidr_block != null ? var.public_services_secondary_cidr_block : cidrsubnet( 61 | var.secondary_cidr_block, 62 | var.secondary_cidr_subnetwork_width_delta, 63 | 1 * (2 + var.secondary_cidr_subnetwork_spacing) 64 | ) 65 | } 66 | 67 | dynamic "log_config" { 68 | for_each = var.log_config == null ? [] : tolist([var.log_config]) 69 | 70 | content { 71 | aggregation_interval = var.log_config.aggregation_interval 72 | flow_sampling = var.log_config.flow_sampling 73 | metadata = var.log_config.metadata 74 | } 75 | } 76 | } 77 | 78 | resource "google_compute_router_nat" "vpc_nat" { 79 | name = "${var.name_prefix}-nat" 80 | 81 | project = var.project 82 | region = var.region 83 | router = google_compute_router.vpc_router.name 84 | 85 | nat_ip_allocate_option = "AUTO_ONLY" 86 | 87 | # "Manually" define the subnetworks for which the NAT is used, so that we can exclude the public subnetwork 88 | source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS" 89 | 90 | subnetwork { 91 | name = google_compute_subnetwork.vpc_subnetwork_public.self_link 92 | source_ip_ranges_to_nat = ["ALL_IP_RANGES"] 93 | } 94 | } 95 | 96 | # --------------------------------------------------------------------------------------------------------------------- 97 | # Private Subnetwork Config 98 | # --------------------------------------------------------------------------------------------------------------------- 99 | 100 | resource "google_compute_subnetwork" "vpc_subnetwork_private" { 101 | name = "${var.name_prefix}-subnetwork-private" 102 | 103 | project = var.project 104 | region = var.region 105 | network = google_compute_network.vpc.self_link 106 | 107 | private_ip_google_access = true 108 | ip_cidr_range = cidrsubnet( 109 | var.cidr_block, 110 | var.cidr_subnetwork_width_delta, 111 | 1 * (1 + var.cidr_subnetwork_spacing) 112 | ) 113 | 114 | secondary_ip_range { 115 | range_name = "private-services" 116 | ip_cidr_range = var.private_services_secondary_cidr_block != null ? var.private_services_secondary_cidr_block : cidrsubnet( 117 | var.secondary_cidr_block, 118 | var.secondary_cidr_subnetwork_width_delta, 119 | 1 * (1 + var.secondary_cidr_subnetwork_spacing) 120 | ) 121 | } 122 | 123 | dynamic "log_config" { 124 | for_each = var.log_config == null ? [] : tolist([var.log_config]) 125 | 126 | content { 127 | aggregation_interval = var.log_config.aggregation_interval 128 | flow_sampling = var.log_config.flow_sampling 129 | metadata = var.log_config.metadata 130 | } 131 | } 132 | } 133 | 134 | # --------------------------------------------------------------------------------------------------------------------- 135 | # Attach Firewall Rules to allow inbound traffic to tagged instances 136 | # --------------------------------------------------------------------------------------------------------------------- 137 | 138 | module "network_firewall" { 139 | source = "../network-firewall" 140 | 141 | name_prefix = var.name_prefix 142 | 143 | project = var.project 144 | network = google_compute_network.vpc.self_link 145 | allowed_public_restricted_subnetworks = var.allowed_public_restricted_subnetworks 146 | 147 | public_subnetwork = google_compute_subnetwork.vpc_subnetwork_public.self_link 148 | private_subnetwork = google_compute_subnetwork.vpc_subnetwork_private.self_link 149 | } 150 | -------------------------------------------------------------------------------- /modules/vpc-network/outputs.tf: -------------------------------------------------------------------------------- 1 | output "network" { 2 | description = "A reference (self_link) to the VPC network" 3 | value = google_compute_network.vpc.self_link 4 | } 5 | 6 | # --------------------------------------------------------------------------------------------------------------------- 7 | # Public Subnetwork Outputs 8 | # --------------------------------------------------------------------------------------------------------------------- 9 | 10 | output "public_subnetwork" { 11 | description = "A reference (self_link) to the public subnetwork" 12 | value = google_compute_subnetwork.vpc_subnetwork_public.self_link 13 | } 14 | 15 | output "public_subnetwork_name" { 16 | description = "Name of the public subnetwork" 17 | value = google_compute_subnetwork.vpc_subnetwork_public.name 18 | } 19 | 20 | output "public_subnetwork_cidr_block" { 21 | value = google_compute_subnetwork.vpc_subnetwork_public.ip_cidr_range 22 | } 23 | 24 | output "public_subnetwork_gateway" { 25 | value = google_compute_subnetwork.vpc_subnetwork_public.gateway_address 26 | } 27 | 28 | output "public_subnetwork_secondary_cidr_block" { 29 | value = google_compute_subnetwork.vpc_subnetwork_public.secondary_ip_range[0].ip_cidr_range 30 | } 31 | 32 | output "public_subnetwork_secondary_range_name" { 33 | value = google_compute_subnetwork.vpc_subnetwork_public.secondary_ip_range[0].range_name 34 | } 35 | 36 | output "public_services_secondary_cidr_block" { 37 | value = google_compute_subnetwork.vpc_subnetwork_public.secondary_ip_range[1].ip_cidr_range 38 | } 39 | 40 | output "public_services_secondary_range_name" { 41 | value = google_compute_subnetwork.vpc_subnetwork_public.secondary_ip_range[1].range_name 42 | } 43 | 44 | # --------------------------------------------------------------------------------------------------------------------- 45 | # Private Subnetwork Outputs 46 | # --------------------------------------------------------------------------------------------------------------------- 47 | 48 | output "private_subnetwork" { 49 | description = "A reference (self_link) to the private subnetwork" 50 | value = google_compute_subnetwork.vpc_subnetwork_private.self_link 51 | } 52 | 53 | output "private_subnetwork_name" { 54 | description = "Name of the private subnetwork" 55 | value = google_compute_subnetwork.vpc_subnetwork_private.name 56 | } 57 | 58 | output "private_subnetwork_cidr_block" { 59 | value = google_compute_subnetwork.vpc_subnetwork_private.ip_cidr_range 60 | } 61 | 62 | output "private_subnetwork_gateway" { 63 | value = google_compute_subnetwork.vpc_subnetwork_private.gateway_address 64 | } 65 | 66 | output "private_subnetwork_secondary_cidr_block" { 67 | value = google_compute_subnetwork.vpc_subnetwork_private.secondary_ip_range[0].ip_cidr_range 68 | } 69 | 70 | output "private_subnetwork_secondary_range_name" { 71 | value = google_compute_subnetwork.vpc_subnetwork_private.secondary_ip_range[0].range_name 72 | } 73 | 74 | # --------------------------------------------------------------------------------------------------------------------- 75 | # Access Tier - Network Tags 76 | # --------------------------------------------------------------------------------------------------------------------- 77 | 78 | output "public" { 79 | description = "The network tag string used for the public access tier" 80 | value = module.network_firewall.public 81 | } 82 | 83 | output "public_restricted" { 84 | description = "The string of the public tag" 85 | value = module.network_firewall.public_restricted 86 | } 87 | 88 | output "private" { 89 | description = "The network tag string used for the private access tier" 90 | value = module.network_firewall.private 91 | } 92 | 93 | output "private_persistence" { 94 | description = "The network tag string used for the private-persistence access tier" 95 | value = module.network_firewall.private_persistence 96 | } 97 | -------------------------------------------------------------------------------- /modules/vpc-network/variables.tf: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------------------------------------------------------------------------- 2 | # REQUIRED PARAMETERS 3 | # These variables are expected to be passed in by the operator 4 | # --------------------------------------------------------------------------------------------------------------------- 5 | 6 | variable "project" { 7 | description = "The project ID for the network" 8 | type = string 9 | } 10 | 11 | variable "region" { 12 | description = "The region for subnetworks in the network" 13 | type = string 14 | } 15 | 16 | variable "name_prefix" { 17 | description = "A name prefix used in resource names to ensure uniqueness across a project." 18 | type = string 19 | } 20 | 21 | # --------------------------------------------------------------------------------------------------------------------- 22 | # OPTIONAL PARAMETERS 23 | # Generally, these values won't need to be changed. 24 | # --------------------------------------------------------------------------------------------------------------------- 25 | 26 | variable "cidr_block" { 27 | description = "The IP address range of the VPC in CIDR notation. A prefix of /16 is recommended. Do not use a prefix higher than /27." 28 | default = "10.0.0.0/16" 29 | type = string 30 | } 31 | 32 | variable "cidr_subnetwork_width_delta" { 33 | description = "The difference between your network and subnetwork netmask; an /16 network and a /20 subnetwork would be 4." 34 | type = number 35 | default = 4 36 | } 37 | 38 | variable "cidr_subnetwork_spacing" { 39 | description = "How many subnetwork-mask sized spaces to leave between each subnetwork type." 40 | type = number 41 | default = 0 42 | } 43 | 44 | variable "public_subnetwork_secondary_range_name" { 45 | description = "The name associated with the pod subnetwork secondary range, used when adding an alias IP range to a VM instance. The name must be 1-63 characters long, and comply with RFC1035. The name must be unique within the subnetwork." 46 | type = string 47 | default = "public-cluster" 48 | } 49 | 50 | variable "public_services_secondary_range_name" { 51 | description = "The name associated with the services subnetwork secondary range, used when adding an alias IP range to a VM instance. The name must be 1-63 characters long, and comply with RFC1035. The name must be unique within the subnetwork." 52 | type = string 53 | default = "public-services" 54 | } 55 | 56 | variable "secondary_cidr_block" { 57 | description = "The IP address range of the VPC's secondary address range in CIDR notation. A prefix of /16 is recommended. Do not use a prefix higher than /27." 58 | type = string 59 | default = "10.1.0.0/16" 60 | } 61 | 62 | variable "public_services_secondary_cidr_block" { 63 | description = "The IP address range of the VPC's public services secondary address range in CIDR notation. A prefix of /16 is recommended. Do not use a prefix higher than /27. Note: this variable is optional and is used primarily for backwards compatibility, if not specified a range will be calculated using var.secondary_cidr_block, var.secondary_cidr_subnetwork_width_delta and var.secondary_cidr_subnetwork_spacing." 64 | type = string 65 | default = null 66 | } 67 | 68 | variable "private_services_secondary_cidr_block" { 69 | description = "The IP address range of the VPC's private services secondary address range in CIDR notation. A prefix of /16 is recommended. Do not use a prefix higher than /27. Note: this variable is optional and is used primarily for backwards compatibility, if not specified a range will be calculated using var.secondary_cidr_block, var.secondary_cidr_subnetwork_width_delta and var.secondary_cidr_subnetwork_spacing." 70 | type = string 71 | default = null 72 | } 73 | 74 | variable "secondary_cidr_subnetwork_width_delta" { 75 | description = "The difference between your network and subnetwork's secondary range netmask; an /16 network and a /20 subnetwork would be 4." 76 | type = number 77 | default = 4 78 | } 79 | 80 | variable "secondary_cidr_subnetwork_spacing" { 81 | description = "How many subnetwork-mask sized spaces to leave between each subnetwork type's secondary ranges." 82 | type = number 83 | default = 0 84 | } 85 | 86 | variable "log_config" { 87 | description = "The logging options for the subnetwork flow logs. Setting this value to `null` will disable them. See https://www.terraform.io/docs/providers/google/r/compute_subnetwork.html for more information and examples." 88 | type = object({ 89 | aggregation_interval = string 90 | flow_sampling = number 91 | metadata = string 92 | }) 93 | 94 | default = { 95 | aggregation_interval = "INTERVAL_10_MIN" 96 | flow_sampling = 0.5 97 | metadata = "INCLUDE_ALL_METADATA" 98 | } 99 | } 100 | 101 | variable "allowed_public_restricted_subnetworks" { 102 | description = "The public networks that is allowed access to the public_restricted subnetwork of the network" 103 | default = [] 104 | type = list(string) 105 | } 106 | -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "network" { 2 | description = "A reference (self_link) to the VPC network" 3 | value = module.management_network.network 4 | } 5 | 6 | # --------------------------------------------------------------------------------------------------------------------- 7 | # Public Subnetwork Outputs 8 | # --------------------------------------------------------------------------------------------------------------------- 9 | 10 | output "public_subnetwork" { 11 | description = "A reference (self_link) to the public subnetwork" 12 | value = module.management_network.public_subnetwork 13 | } 14 | 15 | output "public_subnetwork_cidr_block" { 16 | value = module.management_network.public_subnetwork_cidr_block 17 | } 18 | 19 | output "public_subnetwork_gateway" { 20 | value = module.management_network.public_subnetwork_gateway 21 | } 22 | 23 | output "public_subnetwork_secondary_cidr_block" { 24 | value = module.management_network.public_subnetwork_secondary_cidr_block 25 | } 26 | 27 | # --------------------------------------------------------------------------------------------------------------------- 28 | # Private Subnetwork Outputs 29 | # --------------------------------------------------------------------------------------------------------------------- 30 | 31 | output "private_subnetwork" { 32 | description = "A reference (self_link) to the private subnetwork" 33 | value = module.management_network.private_subnetwork 34 | } 35 | 36 | output "private_subnetwork_cidr_block" { 37 | value = module.management_network.private_subnetwork_cidr_block 38 | } 39 | 40 | output "private_subnetwork_gateway" { 41 | value = module.management_network.private_subnetwork_gateway 42 | } 43 | 44 | output "private_subnetwork_secondary_cidr_block" { 45 | value = module.management_network.private_subnetwork_secondary_cidr_block 46 | } 47 | 48 | # --------------------------------------------------------------------------------------------------------------------- 49 | # Access Tier - Network Tags 50 | # --------------------------------------------------------------------------------------------------------------------- 51 | 52 | output "public" { 53 | description = "The network tag string used for the public access tier" 54 | value = module.management_network.public 55 | } 56 | 57 | output "private" { 58 | description = "The network tag string used for the private access tier" 59 | value = module.management_network.private 60 | } 61 | 62 | output "private_persistence" { 63 | description = "The network tag string used for the private-persistence access tier" 64 | value = module.management_network.private_persistence 65 | } 66 | 67 | # --------------------------------------------------------------------------------------------------------------------- 68 | # Instance Info (primarily for testing) 69 | # --------------------------------------------------------------------------------------------------------------------- 70 | 71 | output "instance_default_network" { 72 | description = "A reference (self link) to an instance in the default network. Note that the default network allows SSH." 73 | value = google_compute_instance.default_network.self_link 74 | } 75 | 76 | output "instance_public_with_ip" { 77 | description = "A reference (self link) to the instance tagged as public in a public subnetwork with an external IP" 78 | value = google_compute_instance.public_with_ip.self_link 79 | } 80 | 81 | output "instance_public_without_ip" { 82 | description = "A reference (self link) to the instance tagged as public in a public subnetwork without an internet IP" 83 | value = google_compute_instance.public_without_ip.self_link 84 | } 85 | 86 | output "instance_private_public" { 87 | description = "A reference (self link) to the instance tagged as private in a public subnetwork" 88 | value = google_compute_instance.private_public.self_link 89 | } 90 | 91 | output "instance_private" { 92 | description = "A reference (self link) to the instance tagged as private in a private subnetwork" 93 | value = google_compute_instance.private.self_link 94 | } 95 | 96 | output "instance_private_persistence" { 97 | description = "A reference (self link) to the instance tagged as private-persistence in a private subnetwork" 98 | value = google_compute_instance.private_persistence.self_link 99 | } 100 | 101 | -------------------------------------------------------------------------------- /test/bastion_host_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "path/filepath" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/gruntwork-io/terratest/modules/gcp" 9 | "github.com/gruntwork-io/terratest/modules/random" 10 | "github.com/gruntwork-io/terratest/modules/ssh" 11 | "github.com/gruntwork-io/terratest/modules/terraform" 12 | test_structure "github.com/gruntwork-io/terratest/modules/test-structure" 13 | ) 14 | 15 | func TestBastionHost(t *testing.T) { 16 | t.Parallel() 17 | 18 | //os.Setenv("SKIP_bootstrap", "true") 19 | //os.Setenv("SKIP_deploy", "true") 20 | //os.Setenv("SKIP_ssh_tests", "true") 21 | //os.Setenv("SKIP_teardown", "true") 22 | 23 | _examplesDir := test_structure.CopyTerraformFolderToTemp(t, "../", "examples") 24 | exampleDir := filepath.Join(_examplesDir, "bastion-host") 25 | 26 | test_structure.RunTestStage(t, "bootstrap", func() { 27 | project := gcp.GetGoogleProjectIDFromEnvVar(t) 28 | region := getRandomRegion(t, project) 29 | zone := gcp.GetRandomZoneForRegion(t, project, region) 30 | 31 | terraformOptions := createBastionHostTerraformOptions(t, strings.ToLower(random.UniqueId()), project, region, zone, exampleDir) 32 | 33 | test_structure.SaveTerraformOptions(t, exampleDir, terraformOptions) 34 | test_structure.SaveString(t, exampleDir, KEY_PROJECT, project) 35 | }) 36 | 37 | // At the end of the test, run `terraform destroy` to clean up any resources that were created 38 | defer test_structure.RunTestStage(t, "teardown", func() { 39 | terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir) 40 | terraform.Destroy(t, terraformOptions) 41 | }) 42 | 43 | test_structure.RunTestStage(t, "deploy", func() { 44 | terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir) 45 | terraform.InitAndApply(t, terraformOptions) 46 | }) 47 | 48 | /* 49 | Test SSH 50 | */ 51 | test_structure.RunTestStage(t, "ssh_tests", func() { 52 | terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir) 53 | project := test_structure.LoadString(t, exampleDir, KEY_PROJECT) 54 | 55 | address := terraform.Output(t, terraformOptions, "address") 56 | googleIdentity := gcp.GetGoogleIdentityEmailEnvVar(t) 57 | 58 | keyPair := ssh.GenerateRSAKeyPair(t, 2048) 59 | key := keyPair.PublicKey 60 | 61 | user := googleIdentity 62 | 63 | defer gcp.DeleteSSHKey(t, user, key) 64 | gcp.ImportSSHKey(t, user, key) 65 | 66 | loginProfile := gcp.GetLoginProfile(t, user) 67 | sshUsername := loginProfile.PosixAccounts[0].Username 68 | 69 | bastionHost := ssh.Host{ 70 | Hostname: address, 71 | SshKeyPair: keyPair, 72 | SshUserName: sshUsername, 73 | } 74 | 75 | private := FetchFromOutput(t, terraformOptions, project, "private_instance") 76 | privateHost := ssh.Host{ 77 | Hostname: private.Name, 78 | SshKeyPair: keyPair, 79 | SshUserName: sshUsername, 80 | } 81 | 82 | sshChecks := []SSHCheck{ 83 | // Success 84 | {"bastion", func(t *testing.T) { testSSHOn1Host(t, ExpectSuccess, bastionHost) }}, 85 | {"bastion to private", func(t *testing.T) { testSSHOn2Hosts(t, ExpectSuccess, bastionHost, privateHost) }}, 86 | 87 | // Failure 88 | {"private", func(t *testing.T) { testSSHOn1Host(t, ExpectFailure, privateHost) }}, 89 | } 90 | 91 | // We need to run a series of parallel funcs inside a serial func in order to ensure that defer statements are ran after they've all completed 92 | t.Run("sshConnections", func(t *testing.T) { 93 | for _, check := range sshChecks { 94 | check := check // capture variable in local scope 95 | 96 | t.Run(check.Name, func(t *testing.T) { 97 | t.Parallel() 98 | check.Check(t) 99 | }) 100 | } 101 | }) 102 | }) 103 | 104 | } 105 | -------------------------------------------------------------------------------- /test/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gruntwork-io/terraform-google-network/test 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/gruntwork-io/terratest v0.37.5 7 | github.com/stretchr/testify v1.4.0 8 | ) 9 | -------------------------------------------------------------------------------- /test/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.51.0 h1:PvKAVQWCtlGUSlZkGW3QLelKaWq7KYv/MW1EboG8bfM= 9 | cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= 10 | cloud.google.com/go/bigquery v1.0.1 h1:hL+ycaJpVE9M7nLoiXb/Pn10ENE2u+oddxbD8uu0ZVU= 11 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 12 | cloud.google.com/go/datastore v1.0.0 h1:Kt+gOPPp2LEPWp8CSfxhsM8ik9CcyE/gYu+0r+RnZvM= 13 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 14 | cloud.google.com/go/pubsub v1.0.1 h1:W9tAK3E57P75u0XLLR82LZyw8VpAnhmyTOxW9qzmyj8= 15 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 16 | cloud.google.com/go/storage v1.0.0 h1:VV2nUM3wwLLGh9lSABFgZMjInyUbJeaRSE64WuAIQ+4= 17 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 18 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 19 | github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= 20 | github.com/Azure/azure-sdk-for-go v38.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= 21 | github.com/Azure/azure-sdk-for-go v46.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= 22 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= 23 | github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= 24 | github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= 25 | github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0= 26 | github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= 27 | github.com/Azure/go-autorest/autorest v0.11.0/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= 28 | github.com/Azure/go-autorest/autorest v0.11.5/go.mod h1:foo3aIXRQ90zFve3r0QiDsrjGDUwWhKl0ZOQy1CT14k= 29 | github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= 30 | github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= 31 | github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= 32 | github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= 33 | github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= 34 | github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE= 35 | github.com/Azure/go-autorest/autorest/azure/auth v0.5.1/go.mod h1:ea90/jvmnAwDrSooLH4sRIehEPtG/EPUXavDh31MnA4= 36 | github.com/Azure/go-autorest/autorest/azure/cli v0.4.0/go.mod h1:JljT387FplPzBA31vUcvsetLKF3pec5bdAxjVU4kI2s= 37 | github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= 38 | github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= 39 | github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= 40 | github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= 41 | github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= 42 | github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= 43 | github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= 44 | github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= 45 | github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc= 46 | github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= 47 | github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8= 48 | github.com/Azure/go-autorest/autorest/validation v0.3.0/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= 49 | github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= 50 | github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= 51 | github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= 52 | github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= 53 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 54 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 55 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 56 | github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14= 57 | github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= 58 | github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= 59 | github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 60 | github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 61 | github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 62 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 63 | github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= 64 | github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= 65 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 66 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 67 | github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= 68 | github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0= 69 | github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= 70 | github.com/apparentlymart/go-textseg/v12 v12.0.0 h1:bNEQyAGak9tojivJNkoqWErVCQbjdL7GzRt3F8NvfJ0= 71 | github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= 72 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 73 | github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= 74 | github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 75 | github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 76 | github.com/aws/aws-sdk-go v1.38.28 h1:2ZzgEupSluR18ClxUnHwXKyuADheZpMblXRAsHqF0tI= 77 | github.com/aws/aws-sdk-go v1.38.28/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= 78 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 79 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 80 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 81 | github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 82 | github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= 83 | github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= 84 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 85 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 86 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 87 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 88 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 89 | github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= 90 | github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= 91 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 92 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 93 | github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= 94 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 95 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 96 | github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 97 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 98 | github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 99 | github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 100 | github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= 101 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 102 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 103 | github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= 104 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 105 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 106 | github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 107 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 108 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 109 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 110 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 111 | github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= 112 | github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= 113 | github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= 114 | github.com/docker/cli v0.0.0-20200109221225-a4f60165b7a3 h1:2EQbDt6zX40xsToT0KdsmAm4wfA+fPJfv9Yk/PwTxZk= 115 | github.com/docker/cli v0.0.0-20200109221225-a4f60165b7a3/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= 116 | github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 117 | github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 118 | github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7 h1:Cvj7S8I4Xpx78KAl6TwTmMHuHlZ/0SM60NUneGJQ7IE= 119 | github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 120 | github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ= 121 | github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= 122 | github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= 123 | github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 124 | github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= 125 | github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s= 126 | github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= 127 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 128 | github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 129 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 130 | github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= 131 | github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= 132 | github.com/elazarl/goproxy v0.0.0-20190911111923-ecfe977594f1 h1:yY9rWGoXv1U5pl4gxqlULARMQD7x0QG85lqEXTWysik= 133 | github.com/elazarl/goproxy v0.0.0-20190911111923-ecfe977594f1/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= 134 | github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= 135 | github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= 136 | github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 137 | github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 138 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 139 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 140 | github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 141 | github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 142 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 143 | github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= 144 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 145 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 146 | github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 147 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 148 | github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= 149 | github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0 h1:skJKxRtNmevLqnayafdLe2AsenqRupVmzZSqrvb5caU= 150 | github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= 151 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 152 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 153 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 154 | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= 155 | github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY= 156 | github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= 157 | github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= 158 | github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= 159 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 160 | github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= 161 | github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= 162 | github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= 163 | github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= 164 | github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= 165 | github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= 166 | github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 167 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 168 | github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= 169 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 170 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 171 | github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= 172 | github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 173 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 174 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 175 | github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 176 | github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= 177 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 178 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 179 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 180 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 181 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 182 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA= 183 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 184 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 185 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 186 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 187 | github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 188 | github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 189 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 190 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 191 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 192 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 193 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 194 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 195 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 196 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 197 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 198 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 199 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= 200 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 201 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 202 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 203 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 204 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 205 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 206 | github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= 207 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 208 | github.com/google/go-containerregistry v0.0.0-20200110202235-f4fb41bf00a3 h1:lvfj3UTMJS9ZD1T4mUjvZTe24RqIe9fYUqpeOza+0Hs= 209 | github.com/google/go-containerregistry v0.0.0-20200110202235-f4fb41bf00a3/go.mod h1:2wIuQute9+hhWqvL3vEI7YB0EKluF4WcPzI1eAliazk= 210 | github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 211 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 212 | github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= 213 | github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 214 | github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= 215 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 216 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 217 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 218 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 219 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 220 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 221 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 222 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 223 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 224 | github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= 225 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 226 | github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 227 | github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 228 | github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= 229 | github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= 230 | github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= 231 | github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 232 | github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 233 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 234 | github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 235 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 236 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 237 | github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 238 | github.com/gruntwork-io/go-commons v0.8.0 h1:k/yypwrPqSeYHevLlEDmvmgQzcyTwrlZGRaxEM6G0ro= 239 | github.com/gruntwork-io/go-commons v0.8.0/go.mod h1:gtp0yTtIBExIZp7vyIV9I0XQkVwiQZze678hvDXof78= 240 | github.com/gruntwork-io/terratest v0.37.5 h1:W93EhxcxDApa5Xyj/TOpDXx+i5IssTopy5LbEUJkPuA= 241 | github.com/gruntwork-io/terratest v0.37.5/go.mod h1:CSHpZNJdqYQ+TUrigM100jcahRUV5X6w7K2kZJ8iylY= 242 | github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= 243 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 244 | github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= 245 | github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= 246 | github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw= 247 | github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 248 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 249 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 250 | github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 251 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 252 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 253 | github.com/hashicorp/hcl/v2 v2.8.2 h1:wmFle3D1vu0okesm8BTLVDyJ6/OL9DCLUwn0b2OptiY= 254 | github.com/hashicorp/hcl/v2 v2.8.2/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY= 255 | github.com/hashicorp/terraform-json v0.12.0 h1:8czPgEEWWPROStjkWPUnTQDXmpmZPlkQAwYYLETaTvw= 256 | github.com/hashicorp/terraform-json v0.12.0/go.mod h1:pmbq9o4EuL43db5+0ogX10Yofv1nozM+wskr/bGFJpI= 257 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 258 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 259 | github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 260 | github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= 261 | github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 262 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 263 | github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o= 264 | github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= 265 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 266 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 267 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 268 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 269 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 270 | github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= 271 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 272 | github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 273 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 274 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 275 | github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 276 | github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= 277 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 278 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 279 | github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= 280 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 281 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 282 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 283 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 284 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 285 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 286 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 287 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 288 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 289 | github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= 290 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 291 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 292 | github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= 293 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 294 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 295 | github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= 296 | github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= 297 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 298 | github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 299 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 300 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 301 | github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= 302 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 303 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 304 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 305 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 306 | github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= 307 | github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 308 | github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= 309 | github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326 h1:ofNAzWCcyTALn2Zv40+8XitdzCgXY6e9qvXwN9W0YXg= 310 | github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= 311 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 312 | github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= 313 | github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= 314 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= 315 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 316 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 317 | github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= 318 | github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 319 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 320 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 321 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 322 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 323 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 324 | github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 325 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 326 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 327 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 328 | github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 329 | github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 330 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 331 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 332 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 333 | github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= 334 | github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 335 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 336 | github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 337 | github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 338 | github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 339 | github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 340 | github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 341 | github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 342 | github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= 343 | github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= 344 | github.com/oracle/oci-go-sdk v7.1.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= 345 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 346 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 347 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 348 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 349 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 350 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 351 | github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 352 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 353 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 354 | github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= 355 | github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok= 356 | github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= 357 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 358 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 359 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 360 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 361 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 362 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 363 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 364 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 365 | github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= 366 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 367 | github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= 368 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 369 | github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= 370 | github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= 371 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 372 | github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= 373 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 374 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 375 | github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= 376 | github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= 377 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 378 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= 379 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 380 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 381 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 382 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 383 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 384 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 385 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 386 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 387 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 388 | github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= 389 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 390 | github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 391 | github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 392 | github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 393 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 394 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 395 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 396 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 397 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 398 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 399 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 400 | github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 401 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 402 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 403 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 404 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 405 | github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 406 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 407 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 408 | github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 409 | github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= 410 | github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 411 | github.com/vdemeester/k8s-pkg-credentialprovider v0.0.0-20200107171650-7c61ffa44238/go.mod h1:JwQJCMWpUDqjZrB5jpw0f5VbN7U95zxFy1ZDpoEarGo= 412 | github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= 413 | github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= 414 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 415 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 416 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 417 | github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= 418 | github.com/zclconf/go-cty v1.2.1 h1:vGMsygfmeCl4Xb6OA5U5XVAaQZ69FvoG7X2jUtQujb8= 419 | github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= 420 | go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 421 | go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= 422 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 423 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 424 | go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= 425 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 426 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 427 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 428 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 429 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 430 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 431 | golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 432 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 433 | golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 434 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 435 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 436 | golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 437 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 438 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 439 | golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 440 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= 441 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 442 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 443 | golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 444 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 445 | golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 446 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 447 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 448 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299 h1:zQpM52jfKHG6II1ISZY1ZcpygvuSFZpLwfluuF89XOg= 449 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 450 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 451 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 452 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 453 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 454 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 455 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 456 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 457 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 458 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= 459 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 460 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 461 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 462 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 463 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 464 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 465 | golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= 466 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 467 | golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 468 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 469 | golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 470 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 471 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 472 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 473 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 474 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 475 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 476 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 477 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 478 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 479 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 480 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 481 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 482 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 483 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 484 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 485 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 486 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 487 | golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 488 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 489 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 490 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 491 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 492 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= 493 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 494 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 495 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 496 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 497 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 498 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= 499 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 500 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 501 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 502 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 503 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 504 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 505 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 506 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= 507 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 508 | golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 509 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 510 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 511 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 512 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 513 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 514 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 515 | golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 516 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 517 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 518 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 519 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 520 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 521 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 522 | golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 523 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 524 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 525 | golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 526 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 527 | golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 528 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 529 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 530 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 531 | golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 532 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 533 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 534 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 535 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 536 | golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 537 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= 538 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 539 | golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 540 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 541 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 542 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 543 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 544 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 545 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 546 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 547 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 548 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= 549 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 550 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 551 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 552 | golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 553 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 554 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 555 | golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 556 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 557 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 558 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 559 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 560 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 561 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 562 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 563 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 564 | golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 565 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 566 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 567 | golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= 568 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 569 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 570 | golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 571 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 572 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 573 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 574 | golang.org/x/tools v0.0.0-20191205215504-7b8c8591a921/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 575 | golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 576 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 577 | golang.org/x/tools v0.0.0-20201110201400-7099162a900a h1:5E6TPwSBG74zT8xSrVc8W59K4ch4NFobVTnh2BYzHyU= 578 | golang.org/x/tools v0.0.0-20201110201400-7099162a900a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 579 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 580 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 581 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 582 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 583 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 584 | gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= 585 | gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= 586 | gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= 587 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 588 | google.golang.org/api v0.6.1-0.20190607001116-5213b8090861/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= 589 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 590 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 591 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 592 | google.golang.org/api v0.15.0 h1:yzlyyDW/J0w8yNFJIhiAJy4kq74S+1DOLdawELNxFMA= 593 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 594 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 595 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 596 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 597 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 598 | google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= 599 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 600 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 601 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 602 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 603 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 604 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 605 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 606 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 607 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 608 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 609 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= 610 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 611 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 612 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 613 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 614 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 615 | google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 616 | google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= 617 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 618 | google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= 619 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 620 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 621 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 622 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 623 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 624 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 625 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 626 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 627 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 628 | google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= 629 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 630 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 631 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 632 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 633 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 634 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 635 | gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= 636 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 637 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 638 | gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= 639 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 640 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 641 | gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= 642 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 643 | gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 644 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 645 | gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 646 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 647 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 648 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 649 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 650 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 651 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 652 | gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= 653 | gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= 654 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 655 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 656 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 657 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 658 | honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= 659 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 660 | k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI= 661 | k8s.io/api v0.19.3 h1:GN6ntFnv44Vptj/b+OnMW7FmzkpDoIDLZRvKX3XH9aU= 662 | k8s.io/api v0.19.3/go.mod h1:VF+5FT1B74Pw3KxMdKyinLo+zynBaMBiAfGMuldcNDs= 663 | k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= 664 | k8s.io/apimachinery v0.19.3 h1:bpIQXlKjB4cB/oNpnNnV+BybGPR7iP5oYpsOTEJ4hgc= 665 | k8s.io/apimachinery v0.19.3/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= 666 | k8s.io/apiserver v0.17.0/go.mod h1:ABM+9x/prjINN6iiffRVNCBR2Wk7uY4z+EtEGZD48cg= 667 | k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= 668 | k8s.io/client-go v0.19.3 h1:ctqR1nQ52NUs6LpI0w+a5U+xjYwflFwA13OJKcicMxg= 669 | k8s.io/client-go v0.19.3/go.mod h1:+eEMktZM+MG0KO+PTkci8xnbCZHvj9TqR6Q1XDUIJOM= 670 | k8s.io/cloud-provider v0.17.0/go.mod h1:Ze4c3w2C0bRsjkBUoHpFi+qWe3ob1wI2/7cUn+YQIDE= 671 | k8s.io/code-generator v0.0.0-20191121015212-c4c8f8345c7e/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= 672 | k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc= 673 | k8s.io/csi-translation-lib v0.17.0/go.mod h1:HEF7MEz7pOLJCnxabi45IPkhSsE/KmxPQksuCrHKWls= 674 | k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 675 | k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 676 | k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 677 | k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 678 | k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 679 | k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= 680 | k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= 681 | k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= 682 | k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A= 683 | k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= 684 | k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= 685 | k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= 686 | k8s.io/legacy-cloud-providers v0.17.0/go.mod h1:DdzaepJ3RtRy+e5YhNtrCYwlgyK87j/5+Yfp0L9Syp8= 687 | k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= 688 | k8s.io/utils v0.0.0-20200729134348-d5654de09c73 h1:uJmqzgNWG7XyClnU/mLPBWwfKKF1K8Hf8whTseBgJcg= 689 | k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= 690 | modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= 691 | modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= 692 | modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= 693 | modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= 694 | modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= 695 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 696 | sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= 697 | sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU= 698 | sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= 699 | sigs.k8s.io/structured-merge-diff/v4 v4.0.1 h1:YXTMot5Qz/X1iBRJhAt+vI+HVttY0WkSqqhKxQ0xVbA= 700 | sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= 701 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 702 | sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= 703 | sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= 704 | -------------------------------------------------------------------------------- /test/management_network_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "strings" 7 | "testing" 8 | "time" 9 | 10 | "github.com/gruntwork-io/terratest/modules/gcp" 11 | "github.com/gruntwork-io/terratest/modules/random" 12 | "github.com/gruntwork-io/terratest/modules/retry" 13 | "github.com/gruntwork-io/terratest/modules/ssh" 14 | "github.com/gruntwork-io/terratest/modules/terraform" 15 | test_structure "github.com/gruntwork-io/terratest/modules/test-structure" 16 | ) 17 | 18 | func TestNetworkManagement(t *testing.T) { 19 | t.Parallel() 20 | 21 | //os.Setenv("SKIP_bootstrap", "true") 22 | //os.Setenv("SKIP_deploy", "true") 23 | //os.Setenv("SKIP_validate_outputs", "true") 24 | //os.Setenv("SKIP_ssh_tests", "true") 25 | //os.Setenv("SKIP_teardown", "true") 26 | 27 | _examplesDir := test_structure.CopyTerraformFolderToTemp(t, "../", "examples") 28 | exampleDir := filepath.Join(_examplesDir, "network-management") 29 | 30 | test_structure.RunTestStage(t, "bootstrap", func() { 31 | projectId := gcp.GetGoogleProjectIDFromEnvVar(t) 32 | region := getRandomRegion(t, projectId) 33 | terraformOptions := createNetworkManagementTerraformOptions(t, strings.ToLower(random.UniqueId()), projectId, region, exampleDir) 34 | 35 | test_structure.SaveTerraformOptions(t, exampleDir, terraformOptions) 36 | test_structure.SaveString(t, exampleDir, KEY_PROJECT, projectId) 37 | }) 38 | 39 | // At the end of the test, run `terraform destroy` to clean up any resources that were created 40 | defer test_structure.RunTestStage(t, "teardown", func() { 41 | terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir) 42 | terraform.Destroy(t, terraformOptions) 43 | }) 44 | 45 | test_structure.RunTestStage(t, "deploy", func() { 46 | terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir) 47 | terraform.InitAndApply(t, terraformOptions) 48 | }) 49 | 50 | /* 51 | Test Outputs 52 | */ 53 | // Guarantee that we see expected values from state 54 | test_structure.RunTestStage(t, "validate_outputs", func() { 55 | terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir) 56 | 57 | var stateValues = []struct { 58 | outputKey string 59 | expectedValue string 60 | 61 | // With two string insertion points 62 | message string 63 | }{ 64 | // Testing the cidr block itself is just reading the value out of the Terraform config; 65 | // by testing the gateway addresses, we've confirmed that the API had allocated the correct 66 | // block, although not necessarily the correct size. 67 | {"public_subnetwork_gateway", "10.0.0.1", "expected a public gateway of %s but saw %s"}, 68 | {"private_subnetwork_gateway", "10.0.16.1", "expected a public gateway of %s but saw %s"}, 69 | 70 | // Network tags as interpolation targets 71 | {"public", "public", "expected a tag of %s but saw %s"}, 72 | {"private", "private", "expected a tag of %s but saw %s"}, 73 | {"private_persistence", "private-persistence", "expected a tag of %s but saw %s"}, 74 | } 75 | 76 | for _, tt := range stateValues { 77 | t.Run(tt.outputKey, func(t *testing.T) { 78 | value, err := terraform.OutputE(t, terraformOptions, tt.outputKey) 79 | if err != nil { 80 | t.Errorf("could not find %s in outputs: %s", tt.outputKey, err) 81 | } 82 | 83 | if value != tt.expectedValue { 84 | t.Errorf(tt.message, tt.expectedValue, value) 85 | } 86 | }) 87 | } 88 | }) 89 | 90 | /* 91 | Test SSH 92 | */ 93 | test_structure.RunTestStage(t, "ssh_tests", func() { 94 | project := test_structure.LoadString(t, exampleDir, KEY_PROJECT) 95 | terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir) 96 | 97 | external := FetchFromOutput(t, terraformOptions, project, "instance_default_network") 98 | publicWithIp := FetchFromOutput(t, terraformOptions, project, "instance_public_with_ip") 99 | publicWithoutIp := FetchFromOutput(t, terraformOptions, project, "instance_public_without_ip") 100 | privatePublic := FetchFromOutput(t, terraformOptions, project, "instance_private_public") 101 | private := FetchFromOutput(t, terraformOptions, project, "instance_private") 102 | privatePersistence := FetchFromOutput(t, terraformOptions, project, "instance_private_persistence") 103 | 104 | keyPair := ssh.GenerateRSAKeyPair(t, 2048) 105 | sshUsername := "terratest" 106 | 107 | // Attach the SSH Key to each instances so we can access them at will later 108 | for _, v := range []*gcp.Instance{external, publicWithIp, publicWithoutIp, privatePublic, private, privatePersistence} { 109 | // Adding instance metadata uses a shared fingerprint per-project, and it's (slightly) eventually consistent. 110 | // This means we'll get an error on mismatch, so we can try a few times and make sure we get it right. 111 | retry.DoWithRetry(t, "Adding SSH Key", 20, 1*time.Second, func() (string, error) { 112 | err := v.AddSshKeyE(t, sshUsername, keyPair.PublicKey) 113 | return "", err 114 | }) 115 | } 116 | 117 | // "external internet" settings pulled from the instance in the default network 118 | externalHost := ssh.Host{ 119 | Hostname: external.GetPublicIp(t), 120 | SshKeyPair: keyPair, 121 | SshUserName: sshUsername, 122 | } 123 | 124 | // We can SSH to the public instance w/ an IP 125 | publicWithIpHost := ssh.Host{ 126 | Hostname: publicWithIp.GetPublicIp(t), 127 | SshKeyPair: keyPair, 128 | SshUserName: sshUsername, 129 | } 130 | 131 | // The public instance w/ no IP can't be accessed directly but can through a bastion 132 | if _, err := publicWithoutIp.GetPublicIpE(t); err == nil { 133 | t.Errorf("Found an external IP on %s when it should have had none", publicWithoutIp.Name) 134 | } 135 | 136 | publicWithoutIpHost := ssh.Host{ 137 | Hostname: publicWithoutIp.Name, 138 | SshKeyPair: keyPair, 139 | SshUserName: sshUsername, 140 | } 141 | 142 | // The private instance tagged public w/ no IP can't be accessed directly but can through a bastion 143 | if _, err := privatePublic.GetPublicIpE(t); err == nil { 144 | t.Errorf("Found an external IP on %s when it should have had none", privatePublic.Name) 145 | } 146 | 147 | privatePublicHost := ssh.Host{ 148 | Hostname: privatePublic.Name, 149 | SshKeyPair: keyPair, 150 | SshUserName: sshUsername, 151 | } 152 | 153 | // The private instance [in a private subnetwork] w/ no IP can't be accessed directly but can through a bastion 154 | if _, err := private.GetPublicIpE(t); err == nil { 155 | t.Errorf("Found an external IP on %s when it should have had none", private.Name) 156 | } 157 | 158 | privateHost := ssh.Host{ 159 | Hostname: private.Name, 160 | SshKeyPair: keyPair, 161 | SshUserName: sshUsername, 162 | } 163 | 164 | // The private-persistence instance [in a private subnetwork] w/ no IP can't be accessed directly but can through a bastion from a private instance 165 | if _, err := privatePersistence.GetPublicIpE(t); err == nil { 166 | t.Errorf("Found an external IP on %s when it should have had none", privatePersistence.Name) 167 | } 168 | 169 | privatePersistenceHost := ssh.Host{ 170 | Hostname: privatePersistence.Name, 171 | SshKeyPair: keyPair, 172 | SshUserName: sshUsername, 173 | } 174 | 175 | sshChecks := []SSHCheck{ 176 | // Success 177 | {"public", func(t *testing.T) { testSSHOn1Host(t, ExpectSuccess, publicWithIpHost) }}, 178 | {"public to external", func(t *testing.T) { testSSHOn2Hosts(t, ExpectSuccess, publicWithIpHost, externalHost) }}, 179 | {"public to public-no-ip", func(t *testing.T) { testSSHOn2Hosts(t, ExpectSuccess, publicWithIpHost, publicWithoutIpHost) }}, 180 | {"public to private-public", func(t *testing.T) { testSSHOn2Hosts(t, ExpectSuccess, publicWithIpHost, privatePublicHost) }}, 181 | {"public to private", func(t *testing.T) { testSSHOn2Hosts(t, ExpectSuccess, publicWithIpHost, privateHost) }}, 182 | // TODO: Add a third jump to terratest to test the following: 183 | // {"public to privatePublic to external", func(t *testing.T) { testSSHOn3Hosts(t, ExpectSuccess, publicWithIpHost, privatePublicHost, externalHost)} }, 184 | // {"public to private to private-persistence", func(t *testing.T) { testSSHOn3Hosts(t, ExpectSuccess, publicWithIpHost, privateHost, privatePersistenceHost)} }, 185 | 186 | // Failure 187 | {"public-no-ip", func(t *testing.T) { testSSHOn1Host(t, ExpectFailure, publicWithoutIpHost) }}, 188 | {"private-public", func(t *testing.T) { testSSHOn1Host(t, ExpectFailure, privatePublicHost) }}, 189 | {"private", func(t *testing.T) { testSSHOn1Host(t, ExpectFailure, privateHost) }}, 190 | {"public to private-persistence", func(t *testing.T) { testSSHOn2Hosts(t, ExpectFailure, publicWithIpHost, privatePersistenceHost) }}, 191 | // TODO: Add a third jump to terratest to test the following: 192 | // {"public to private to external", func(t *testing.T) { testSSHOn3Hosts(t, ExpectFailure, publicWithIpHost, privateHost, externalHost)} }, 193 | } 194 | 195 | // We need to run a series of parallel funcs inside a serial func in order to ensure that defer statements are ran after they've all completed 196 | t.Run("sshConnections", func(t *testing.T) { 197 | for _, check := range sshChecks { 198 | check := check // capture variable in local scope 199 | 200 | t.Run(check.Name, func(t *testing.T) { 201 | t.Parallel() 202 | check.Check(t) 203 | }) 204 | } 205 | }) 206 | }) 207 | 208 | } 209 | 210 | type SSHCheck struct { 211 | Name string 212 | Check func(t *testing.T) 213 | } 214 | 215 | func doWithRetryAndTimeoutE(t *testing.T, description string, maxRetries int, sshSleepBetweenRetries time.Duration, timeoutPerRetry time.Duration, action func() (string, error)) (string, error) { 216 | return retry.DoWithRetryE(t, description, maxRetries, sshSleepBetweenRetries, func() (string, error) { 217 | return retry.DoWithTimeoutE(t, description, timeoutPerRetry, action) 218 | }) 219 | } 220 | 221 | func testSSHOn1Host(t *testing.T, expectSuccess bool, host ssh.Host) { 222 | maxRetries := SSHMaxRetries 223 | if !expectSuccess { 224 | maxRetries = SSHMaxRetriesExpectError 225 | } 226 | 227 | _, err := doWithRetryAndTimeoutE(t, "Attempting to SSH", maxRetries, SSHSleepBetweenRetries, SSHTimeout, func() (string, error) { 228 | output, err := ssh.CheckSshCommandE(t, host, fmt.Sprintf("echo '%s'", SSHEchoText)) 229 | if err != nil { 230 | return "", err 231 | } 232 | 233 | if strings.TrimSpace(SSHEchoText) != strings.TrimSpace(output) { 234 | return "", fmt.Errorf("Expected: %s. Got: %s\n", SSHEchoText, output) 235 | } 236 | 237 | return "", nil 238 | }) 239 | 240 | if err != nil && expectSuccess { 241 | t.Fatalf("Expected success but saw: %s", err) 242 | } 243 | 244 | if err == nil && !expectSuccess { 245 | t.Fatalf("Expected an error but saw none.") 246 | } 247 | } 248 | 249 | func testSSHOn2Hosts(t *testing.T, expectSuccess bool, publicHost, secondHost ssh.Host) { 250 | maxRetries := SSHMaxRetries 251 | if !expectSuccess { 252 | maxRetries = SSHMaxRetriesExpectError 253 | } 254 | 255 | _, err := doWithRetryAndTimeoutE(t, "Attempting to SSH", maxRetries, SSHSleepBetweenRetries, SSHTimeout, func() (string, error) { 256 | output, err := ssh.CheckPrivateSshConnectionE(t, publicHost, secondHost, fmt.Sprintf("echo '%s'", SSHEchoText)) 257 | if err != nil { 258 | return "", err 259 | } 260 | 261 | if strings.TrimSpace(SSHEchoText) != strings.TrimSpace(output) { 262 | return "", fmt.Errorf("Expected: %s. Got: %s\n", SSHEchoText, output) 263 | } 264 | 265 | return "", nil 266 | }) 267 | 268 | if err != nil && expectSuccess { 269 | t.Fatalf("Expected success but saw: %s", err) 270 | } 271 | 272 | if err == nil && !expectSuccess { 273 | t.Fatalf("Expected an error but saw none.") 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /test/network_helpers.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | "time" 7 | 8 | "github.com/gruntwork-io/terratest/modules/gcp" 9 | "github.com/gruntwork-io/terratest/modules/terraform" 10 | ) 11 | 12 | const KEY_PROJECT = "project" 13 | 14 | var ( 15 | ExpectSuccess = true 16 | ExpectFailure = false 17 | 18 | SSHMaxRetries = 20 19 | // we don't want to retry for too long, but we should do it at least a few times to make sure the instance is up 20 | SSHMaxRetriesExpectError = 3 21 | SSHSleepBetweenRetries = 5 * time.Second 22 | SSHTimeout = 15 * time.Second 23 | SSHEchoText = "Hello World" 24 | ) 25 | 26 | // Convenience method to fetch an instance from a reference in the output 27 | // TODO: remove the need for project and pull it from self link directly 28 | func FetchFromOutput(t *testing.T, options *terraform.Options, project, key string) *gcp.Instance { 29 | selfLink := terraform.Output(t, options, key) 30 | return gcp.FetchInstance(t, project, GetResourceNameFromSelfLink(selfLink)) 31 | } 32 | 33 | // Get a name from a GCP self link 34 | func GetResourceNameFromSelfLink(link string) string { 35 | parts := strings.Split(link, "/") 36 | return parts[len(parts)-1] 37 | } 38 | 39 | func getRandomRegion(t *testing.T, projectID string) string { 40 | approvedRegions := []string{"europe-north1", "europe-west1", "europe-west2", "europe-west3", "us-central1", "us-east1", "us-west1"} 41 | return gcp.GetRandomRegion(t, projectID, approvedRegions, []string{}) 42 | } 43 | -------------------------------------------------------------------------------- /test/terratest_options.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/gruntwork-io/terratest/modules/terraform" 8 | ) 9 | 10 | func createNetworkManagementTerraformOptions( 11 | t *testing.T, 12 | uniqueId string, 13 | project string, 14 | region string, 15 | templatePath string, 16 | ) *terraform.Options { 17 | terraformVars := map[string]interface{}{ 18 | "name_prefix": fmt.Sprintf("management-%s", uniqueId), 19 | "region": region, 20 | "project": project, 21 | } 22 | 23 | terratestOptions := terraform.Options{ 24 | TerraformDir: templatePath, 25 | Vars: terraformVars, 26 | } 27 | 28 | return &terratestOptions 29 | 30 | } 31 | 32 | func createBastionHostTerraformOptions( 33 | t *testing.T, 34 | uniqueId string, 35 | project string, 36 | region string, 37 | zone string, 38 | templatePath string, 39 | ) *terraform.Options { 40 | terraformVars := map[string]interface{}{ 41 | "name_prefix": fmt.Sprintf("bastion-%s", uniqueId), 42 | "region": region, 43 | "zone": zone, 44 | "project": project, 45 | } 46 | 47 | terratestOptions := terraform.Options{ 48 | TerraformDir: templatePath, 49 | Vars: terraformVars, 50 | } 51 | 52 | return &terratestOptions 53 | 54 | } 55 | -------------------------------------------------------------------------------- /test/validation/validate_all_modules_and_examples_test.go: -------------------------------------------------------------------------------- 1 | package testvalidate 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | 8 | test_structure "github.com/gruntwork-io/terratest/modules/test-structure" 9 | 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | // TestValidateAllTerraformModulesAndExamples recursively finds all modules and examples (by default) subdirectories in 14 | // the repo and runs Terraform InitAndValidate on them to flush out missing variables, typos, unused vars, etc 15 | func TestValidateAllTerraformModulesAndExamples(t *testing.T) { 16 | t.Parallel() 17 | 18 | cwd, err := os.Getwd() 19 | require.NoError(t, err) 20 | 21 | opts, optsErr := test_structure.NewValidationOptions(filepath.Join(cwd, "../.."), []string{}, []string{}) 22 | require.NoError(t, optsErr) 23 | 24 | test_structure.ValidateAllTerraformModules(t, opts) 25 | } 26 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | # --------------------------------------------------------------------------------------------------------------------- 2 | # REQUIRED PARAMETERS 3 | # These parameters must be supplied when consuming this module. 4 | # --------------------------------------------------------------------------------------------------------------------- 5 | 6 | variable "project" { 7 | description = "The name of the GCP Project where all resources will be launched." 8 | type = string 9 | } 10 | 11 | variable "region" { 12 | description = "The Region in which all GCP resources will be launched." 13 | type = string 14 | } 15 | 16 | # --------------------------------------------------------------------------------------------------------------------- 17 | # OPTIONAL PARAMETERS 18 | # These parameters have reasonable defaults. 19 | # --------------------------------------------------------------------------------------------------------------------- 20 | 21 | variable "name_prefix" { 22 | description = "A name prefix used in resource names to ensure uniqueness across a project." 23 | type = string 24 | default = "management" 25 | } 26 | 27 | --------------------------------------------------------------------------------