Merge pull request #1 from hashicorp/initial-review
Initial commit of Google Cloud dynamic host catalog plugin
This commit is contained in:
28 files changed
+3120
No files matched your search
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Let us know about a bug!
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- Please reserve GitHub issues for bug reports and feature requests.
|
||||||
|
|
||||||
|
For questions, the best place to get answers is on our [discussion forum](https://discuss.hashicorp.com/c/boundary), as they will get more visibility from experienced users than the issue tracker.
|
||||||
|
|
||||||
|
Please note: We take Boundary's security and our users' trust very seriously. If you believe you have found a security issue in Boundary, please responsibly disclose by contacting us at security@hashicorp.com. Our PGP key is available at [our security page](https://www.hashicorp.com/security/).
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Run `boundary authenticate ...`
|
||||||
|
2. Run `boundary connect ...`
|
||||||
|
3. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
contact_links:
|
||||||
|
- name: Ask a question
|
||||||
|
url: https://discuss.hashicorp.com/c/boundary
|
||||||
|
about: For increased visibility, please post questions on the discussion forum.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest something!
|
||||||
|
title: ''
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Explain any additional use-cases**
|
||||||
|
If there are any use-cases that would help us understand the use/need/value please share them as they can help us decide on acceptance and prioritization.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
name: test
|
||||||
|
|
||||||
|
on:
|
||||||
|
- workflow_dispatch
|
||||||
|
- push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
- name: Set up Go Environment
|
||||||
|
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
|
||||||
|
with:
|
||||||
|
go-version-file: 'go.mod'
|
||||||
|
cache: true
|
||||||
|
- name: Run Tests
|
||||||
|
run: go test -v ./...
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
### IntelliJ IDEA project files ###
|
||||||
|
.idea
|
||||||
|
*.ipr
|
||||||
|
*.iml
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
### Visual Studio ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### macOS ###
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
### TF files ###
|
||||||
|
.terraform*
|
||||||
|
terraform.tfstate*
|
||||||
|
plan.tfplan
|
||||||
|
|
||||||
|
### Secrets used in testing ###
|
||||||
|
secrets/
|
||||||
|
secrets.json
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# These owners will be the default owners for everything in
|
||||||
|
# the repo, unless a later match takes precedence.
|
||||||
|
|
||||||
|
@hashicorp/boundary
|
||||||
@@ -0,0 +1,375 @@
|
|||||||
|
Copyright (c) 2021 HashiCorp, Inc.
|
||||||
|
|
||||||
|
Mozilla Public License Version 2.0
|
||||||
|
==================================
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
--------------
|
||||||
|
|
||||||
|
1.1. "Contributor"
|
||||||
|
means each individual or legal entity that creates, contributes to
|
||||||
|
the creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. "Contributor Version"
|
||||||
|
means the combination of the Contributions of others (if any) used
|
||||||
|
by a Contributor and that particular Contributor's Contribution.
|
||||||
|
|
||||||
|
1.3. "Contribution"
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. "Covered Software"
|
||||||
|
means Source Code Form to which the initial Contributor has attached
|
||||||
|
the notice in Exhibit A, the Executable Form of such Source Code
|
||||||
|
Form, and Modifications of such Source Code Form, in each case
|
||||||
|
including portions thereof.
|
||||||
|
|
||||||
|
1.5. "Incompatible With Secondary Licenses"
|
||||||
|
means
|
||||||
|
|
||||||
|
(a) that the initial Contributor has attached the notice described
|
||||||
|
in Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
(b) that the Covered Software was made available under the terms of
|
||||||
|
version 1.1 or earlier of the License, but not also under the
|
||||||
|
terms of a Secondary License.
|
||||||
|
|
||||||
|
1.6. "Executable Form"
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. "Larger Work"
|
||||||
|
means a work that combines Covered Software with other material, in
|
||||||
|
a separate file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. "License"
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. "Licensable"
|
||||||
|
means having the right to grant, to the maximum extent possible,
|
||||||
|
whether at the time of the initial grant or subsequently, any and
|
||||||
|
all of the rights conveyed by this License.
|
||||||
|
|
||||||
|
1.10. "Modifications"
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
(a) any file in Source Code Form that results from an addition to,
|
||||||
|
deletion from, or modification of the contents of Covered
|
||||||
|
Software; or
|
||||||
|
|
||||||
|
(b) any new file in Source Code Form that contains any Covered
|
||||||
|
Software.
|
||||||
|
|
||||||
|
1.11. "Patent Claims" of a Contributor
|
||||||
|
means any patent claim(s), including without limitation, method,
|
||||||
|
process, and apparatus claims, in any patent Licensable by such
|
||||||
|
Contributor that would be infringed, but for the grant of the
|
||||||
|
License, by the making, using, selling, offering for sale, having
|
||||||
|
made, import, or transfer of either its Contributions or its
|
||||||
|
Contributor Version.
|
||||||
|
|
||||||
|
1.12. "Secondary License"
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU
|
||||||
|
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||||
|
Public License, Version 3.0, or any later versions of those
|
||||||
|
licenses.
|
||||||
|
|
||||||
|
1.13. "Source Code Form"
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. "You" (or "Your")
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, "You" includes any entity that
|
||||||
|
controls, is controlled by, or is under common control with You. For
|
||||||
|
purposes of this definition, "control" means (a) the power, direct
|
||||||
|
or indirect, to cause the direction or management of such entity,
|
||||||
|
whether by contract or otherwise, or (b) ownership of more than
|
||||||
|
fifty percent (50%) of the outstanding shares or beneficial
|
||||||
|
ownership of such entity.
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
(a) under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or
|
||||||
|
as part of a Larger Work; and
|
||||||
|
|
||||||
|
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||||
|
for sale, have made, import, and otherwise transfer either its
|
||||||
|
Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution
|
||||||
|
become effective for each Contribution on the date the Contributor first
|
||||||
|
distributes such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under
|
||||||
|
this License. No additional rights or licenses will be implied from the
|
||||||
|
distribution or licensing of Covered Software under this License.
|
||||||
|
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||||
|
Contributor:
|
||||||
|
|
||||||
|
(a) for any code that a Contributor has removed from Covered Software;
|
||||||
|
or
|
||||||
|
|
||||||
|
(b) for infringements caused by: (i) Your and any other third party's
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||||
|
its Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks,
|
||||||
|
or logos of any Contributor (except as may be necessary to comply with
|
||||||
|
the notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this
|
||||||
|
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||||
|
permitted under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its
|
||||||
|
Contributions are its original creation(s) or it has sufficient rights
|
||||||
|
to grant the rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under
|
||||||
|
applicable copyright doctrines of fair use, fair dealing, or other
|
||||||
|
equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||||
|
in Section 2.1.
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under
|
||||||
|
the terms of this License. You must inform recipients that the Source
|
||||||
|
Code Form of the Covered Software is governed by the terms of this
|
||||||
|
License, and how they can obtain a copy of this License. You may not
|
||||||
|
attempt to alter or restrict the recipients' rights in the Source Code
|
||||||
|
Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
(a) such Covered Software must also be made available in Source Code
|
||||||
|
Form, as described in Section 3.1, and You must inform recipients of
|
||||||
|
the Executable Form how they can obtain a copy of such Source Code
|
||||||
|
Form by reasonable means in a timely manner, at a charge no more
|
||||||
|
than the cost of distribution to the recipient; and
|
||||||
|
|
||||||
|
(b) You may distribute such Executable Form under the terms of this
|
||||||
|
License, or sublicense it under different terms, provided that the
|
||||||
|
license for the Executable Form does not attempt to limit or alter
|
||||||
|
the recipients' rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for
|
||||||
|
the Covered Software. If the Larger Work is a combination of Covered
|
||||||
|
Software with a work governed by one or more Secondary Licenses, and the
|
||||||
|
Covered Software is not Incompatible With Secondary Licenses, this
|
||||||
|
License permits You to additionally distribute such Covered Software
|
||||||
|
under the terms of such Secondary License(s), so that the recipient of
|
||||||
|
the Larger Work may, at their option, further distribute the Covered
|
||||||
|
Software under the terms of either this License or such Secondary
|
||||||
|
License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices
|
||||||
|
(including copyright notices, patent notices, disclaimers of warranty,
|
||||||
|
or limitations of liability) contained within the Source Code Form of
|
||||||
|
the Covered Software, except that You may alter any license notices to
|
||||||
|
the extent required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on
|
||||||
|
behalf of any Contributor. You must make it absolutely clear that any
|
||||||
|
such warranty, support, indemnity, or liability obligation is offered by
|
||||||
|
You alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this
|
||||||
|
License with respect to some or all of the Covered Software due to
|
||||||
|
statute, judicial order, or regulation then You must: (a) comply with
|
||||||
|
the terms of this License to the maximum extent possible; and (b)
|
||||||
|
describe the limitations and the code they affect. Such description must
|
||||||
|
be placed in a text file included with all distributions of the Covered
|
||||||
|
Software under this License. Except to the extent prohibited by statute
|
||||||
|
or regulation, such description must be sufficiently detailed for a
|
||||||
|
recipient of ordinary skill to be able to understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
--------------
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically
|
||||||
|
if You fail to comply with any of its terms. However, if You become
|
||||||
|
compliant, then the rights granted under this License from a particular
|
||||||
|
Contributor are reinstated (a) provisionally, unless and until such
|
||||||
|
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||||
|
ongoing basis, if such Contributor fails to notify You of the
|
||||||
|
non-compliance by some reasonable means prior to 60 days after You have
|
||||||
|
come back into compliance. Moreover, Your grants from a particular
|
||||||
|
Contributor are reinstated on an ongoing basis if such Contributor
|
||||||
|
notifies You of the non-compliance by some reasonable means, this is the
|
||||||
|
first time You have received notice of non-compliance with this License
|
||||||
|
from such Contributor, and You become compliant prior to 30 days after
|
||||||
|
Your receipt of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions,
|
||||||
|
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||||
|
directly or indirectly infringes any patent, then the rights granted to
|
||||||
|
You by any and all Contributors for the Covered Software under Section
|
||||||
|
2.1 of this License shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||||
|
end user license agreements (excluding distributors and resellers) which
|
||||||
|
have been validly granted by You or Your distributors under this License
|
||||||
|
prior to termination shall survive termination.
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 6. Disclaimer of Warranty *
|
||||||
|
* ------------------------- *
|
||||||
|
* *
|
||||||
|
* Covered Software is provided under this License on an "as is" *
|
||||||
|
* basis, without warranty of any kind, either expressed, implied, or *
|
||||||
|
* statutory, including, without limitation, warranties that the *
|
||||||
|
* Covered Software is free of defects, merchantable, fit for a *
|
||||||
|
* particular purpose or non-infringing. The entire risk as to the *
|
||||||
|
* quality and performance of the Covered Software is with You. *
|
||||||
|
* Should any Covered Software prove defective in any respect, You *
|
||||||
|
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||||
|
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||||
|
* essential part of this License. No use of any Covered Software is *
|
||||||
|
* authorized under this License except under this disclaimer. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 7. Limitation of Liability *
|
||||||
|
* -------------------------- *
|
||||||
|
* *
|
||||||
|
* Under no circumstances and under no legal theory, whether tort *
|
||||||
|
* (including negligence), contract, or otherwise, shall any *
|
||||||
|
* Contributor, or anyone who distributes Covered Software as *
|
||||||
|
* permitted above, be liable to You for any direct, indirect, *
|
||||||
|
* special, incidental, or consequential damages of any character *
|
||||||
|
* including, without limitation, damages for lost profits, loss of *
|
||||||
|
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||||
|
* and all other commercial damages or losses, even if such party *
|
||||||
|
* shall have been informed of the possibility of such damages. This *
|
||||||
|
* limitation of liability shall not apply to liability for death or *
|
||||||
|
* personal injury resulting from such party's negligence to the *
|
||||||
|
* extent applicable law prohibits such limitation. Some *
|
||||||
|
* jurisdictions do not allow the exclusion or limitation of *
|
||||||
|
* incidental or consequential damages, so this exclusion and *
|
||||||
|
* limitation may not apply to You. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the
|
||||||
|
courts of a jurisdiction where the defendant maintains its principal
|
||||||
|
place of business and such litigation shall be governed by laws of that
|
||||||
|
jurisdiction, without reference to its conflict-of-law provisions.
|
||||||
|
Nothing in this Section shall prevent a party's ability to bring
|
||||||
|
cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
----------------
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject
|
||||||
|
matter hereof. If any provision of this License is held to be
|
||||||
|
unenforceable, such provision shall be reformed only to the extent
|
||||||
|
necessary to make it enforceable. Any law or regulation which provides
|
||||||
|
that the language of a contract shall be construed against the drafter
|
||||||
|
shall not be used to construe this License against a Contributor.
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version
|
||||||
|
of the License under which You originally received the Covered Software,
|
||||||
|
or under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a
|
||||||
|
modified version of this License if you rename the license and remove
|
||||||
|
any references to the name of the license steward (except to note that
|
||||||
|
such modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||||
|
Licenses
|
||||||
|
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular
|
||||||
|
file, then You may include the notice in a location (such as a LICENSE
|
||||||
|
file in a relevant directory) where a recipient would be likely to look
|
||||||
|
for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||||
|
---------------------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
defined by the Mozilla Public License, v. 2.0.
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# Google Plugin for HashiCorp Boundary
|
||||||
|
|
||||||
|
This repo contains the google plugin for [HashiCorp
|
||||||
|
Boundary](https://www.boundaryproject.io/).
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> This is a prototype. Not officially supported by HashiCorp.
|
||||||
|
|
||||||
|
## Credentials
|
||||||
|
|
||||||
|
This plugin uses [Application Default Credentials (ADC)](https://cloud.google.com/docs/authentication/provide-credentials-adc)
|
||||||
|
to authenticate to Google and retrieve a list of instances and instance groups.
|
||||||
|
|
||||||
|
As a result, it does not support rotation of the credentials since they are inferred
|
||||||
|
from the environment.
|
||||||
|
|
||||||
|
## Dynamic Hosts
|
||||||
|
|
||||||
|
This plugin supports dynamically sourcing instances from Google Compute Engine.
|
||||||
|
|
||||||
|
Host sets created with this plugin define filters or instance groups
|
||||||
|
which select and group like instances within Google; these host sets can in turn be
|
||||||
|
added to targets within Boundary as host sources.
|
||||||
|
|
||||||
|
At creation, update or deletion of a host catalog of this type, configuration of the
|
||||||
|
plugin is performed via the attribute/secret values passed to the create, update, or
|
||||||
|
delete calls actions. The values passed in to the plugin here are the attributes set
|
||||||
|
on on a host catalog in Boundary.
|
||||||
|
|
||||||
|
### Google IAM permissions
|
||||||
|
|
||||||
|
The plugin requires the following permissions:
|
||||||
|
|
||||||
|
- `compute.instances.get`
|
||||||
|
- `compute.instances.list`
|
||||||
|
- `compute.instanceGroups.get`
|
||||||
|
- `compute.instanceGroups.list`
|
||||||
|
|
||||||
|
### Attributes
|
||||||
|
|
||||||
|
### Host Catalog
|
||||||
|
|
||||||
|
The following attributes are valid on a Google host catalog resource:
|
||||||
|
|
||||||
|
- `project` (string): required. Project ID of the instances you want to add to host catalog.
|
||||||
|
- `zone` (string): required. Zone of the instances you want to add to host catalog.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ boundary host-catalogs create plugin -scope-id p_1234567890 -plugin-name google -attr zone=us-central1-a -attr project=$GOOGLE_PROJECT
|
||||||
|
```
|
||||||
|
|
||||||
|
### Host Set
|
||||||
|
|
||||||
|
The following attributes are valid on a Google host set resource:
|
||||||
|
|
||||||
|
- `filter` (string): Google Cloud [filter expression](https://cloud.google.com/sdk/gcloud/reference/topic/filters)
|
||||||
|
to filter instances.
|
||||||
|
|
||||||
|
- `instance_group` (string): Name of instance group to get a list of instances.
|
||||||
|
|
||||||
|
You can only set a `filter` or `instance_group` attribute, you cannot set both.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ boundary host-sets create plugin -host-catalog-id $HOST_CATALOG_ID -name "filter-example" -description "example using filters" -attr filter="status=RUNNING"
|
||||||
|
|
||||||
|
$ boundary host-sets create plugin -host-catalog-id $HOST_CATALOG_ID -name "group-example" -description "example using instance groups" -attr instance_group="instance-group-name"
|
||||||
|
```
|
||||||
|
|
||||||
|
After generating the host set, create a target.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ boundary targets create tcp -name google -description "Google Cloud" -scope-id p_1234567890 -default-port 22
|
||||||
|
$ boundary targets add-host-sources -id $TARGET_ID -host-source $HOST_SET_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
You will need to build your own Boundary binary, as Boundary imports the plugins as a dependency.
|
||||||
|
|
||||||
|
As a reference, you can review [this fork](https://github.com/joatmon08/boundary) for changes.
|
||||||
|
|
||||||
|
To build the fork, go into the repository.
|
||||||
|
|
||||||
|
1. Run `make build`.
|
||||||
|
1. Authenticate to Google Cloud using `gcloud auth application-default login`.
|
||||||
|
1. Run Boundary with `./bin/boundary dev`.
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
module github.com/hashicorp/boundary-plugin-google
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/hashicorp/boundary/sdk v0.0.47
|
||||||
|
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8
|
||||||
|
google.golang.org/api v0.188.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
cloud.google.com/go/auth v0.7.0 // indirect
|
||||||
|
cloud.google.com/go/auth/oauth2adapt v0.2.3 // indirect
|
||||||
|
cloud.google.com/go/compute/metadata v0.5.0 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
|
github.com/google/s2a-go v0.1.7 // indirect
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||||
|
github.com/googleapis/gax-go/v2 v2.12.5 // indirect
|
||||||
|
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
|
||||||
|
github.com/hashicorp/go-sockaddr v1.0.5 // indirect
|
||||||
|
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||||
|
go.opencensus.io v0.24.0 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.28.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.28.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
||||||
|
golang.org/x/oauth2 v0.21.0 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
cloud.google.com/go/compute v1.27.3
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
|
github.com/hashicorp/eventlogger v0.2.9 // indirect
|
||||||
|
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0 // indirect
|
||||||
|
github.com/hashicorp/go-kms-wrapping/v2 v2.0.16 // indirect
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
|
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||||
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
|
github.com/mitchellh/pointerstructure v1.2.1 // indirect
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/stretchr/testify v1.9.0
|
||||||
|
golang.org/x/crypto v0.25.0 // indirect
|
||||||
|
golang.org/x/net v0.27.0 // indirect
|
||||||
|
golang.org/x/sys v0.22.0 // indirect
|
||||||
|
golang.org/x/text v0.16.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d // indirect
|
||||||
|
google.golang.org/grpc v1.65.0
|
||||||
|
google.golang.org/protobuf v1.34.2
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
@@ -0,0 +1,297 @@
|
|||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
|
||||||
|
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
|
||||||
|
cloud.google.com/go/auth v0.7.0 h1:kf/x9B3WTbBUHkC+1VS8wwwli9TzhSt0vSTVBmMR8Ts=
|
||||||
|
cloud.google.com/go/auth v0.7.0/go.mod h1:D+WqdrpcjmiCgWrXmLLxOVq1GACoE36chW6KXoEvuIw=
|
||||||
|
cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI=
|
||||||
|
cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I=
|
||||||
|
cloud.google.com/go/compute v1.27.3 h1:GFfoejA5qGeFYpKAfgoVzvgJ3D8upFKI/UR7MdiPdS0=
|
||||||
|
cloud.google.com/go/compute v1.27.3/go.mod h1:5GuDo3l1k9CFhfIHK1sXqlqOW/iWX4/eBlO5FtxDhvQ=
|
||||||
|
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
|
||||||
|
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||||
|
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||||
|
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
|
||||||
|
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||||
|
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
|
||||||
|
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
|
||||||
|
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||||
|
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
|
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
|
||||||
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||||
|
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
|
||||||
|
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||||
|
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||||
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||||
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E=
|
||||||
|
github.com/hashicorp/boundary/sdk v0.0.47 h1:h5AXOASS2duHkCYEmNKnI9AR6YBZxD7VbFPV8BoE0z0=
|
||||||
|
github.com/hashicorp/boundary/sdk v0.0.47/go.mod h1:9iOT7kDM6mYcSkKxNuZlv8rP7U5BG1kXoevjLLL8lNQ=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||||
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/eventlogger v0.2.9 h1:QO8hPXNngadMp72FVNTwIduLAG9fcLP7t59bSFd7gDY=
|
||||||
|
github.com/hashicorp/eventlogger v0.2.9/go.mod h1://CHt6/j+Q2lc0NlUB5af4aS2M0c0aVBg9/JfcpAyhM=
|
||||||
|
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0 h1:iAb287bq0TaWTnhDYuN/zVqdD2EwanQg9ncVelC60Xc=
|
||||||
|
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0/go.mod h1:tMywUTIvdB/FXhwm6HMTt61C8/eODY6gitCHhXtyojg=
|
||||||
|
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
|
||||||
|
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||||
|
github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.5 h1:jrnDfQm2hCQ0/hEselgqzV4fK16gpZoY0OWGZpVPNHM=
|
||||||
|
github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.5/go.mod h1:psh1qKep5ukvuNobFY/hCybuudlkkACpmazOsCgX5Rg=
|
||||||
|
github.com/hashicorp/go-kms-wrapping/v2 v2.0.16 h1:WZeXfD26QMWYC35at25KgE021SF9L3u9UMHK8fJAdV0=
|
||||||
|
github.com/hashicorp/go-kms-wrapping/v2 v2.0.16/go.mod h1:ZiKZctjRTLEppuRwrttWkp71VYMbTTCkazK4xT7U/NQ=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
|
github.com/hashicorp/go-plugin v1.5.2 h1:aWv8eimFqWlsEiMrYZdPYl+FdHaBJSN4AWwGWfT1G2Y=
|
||||||
|
github.com/hashicorp/go-plugin v1.5.2/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4=
|
||||||
|
github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
|
||||||
|
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng=
|
||||||
|
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
|
||||||
|
github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.11 h1:uPW2Wn0YlmI9RGSkZpcIplnVRwJ7BCiGpk1vnF2TMw4=
|
||||||
|
github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.11/go.mod h1:uis9dCmOzXuOaRyXq+1Foh31kcvXKoWogjNnhfjHfW8=
|
||||||
|
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.9 h1:0S0ctJ7Ra8O7ap+/3fZUnzJ3VzJyirWS/WnNCuOYtZY=
|
||||||
|
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.9/go.mod h1:TNNdgtjLgVDbrgFcyCKrlAicIl3dZF94swJltyGUX2M=
|
||||||
|
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 h1:iBt4Ew4XEGLfh6/bPk4rSYmuZJGizr6/x/AEizP0CQc=
|
||||||
|
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8/go.mod h1:aiJI+PIApBRQG7FZTEBx5GiiX+HbOHilUdNxUZi4eV0=
|
||||||
|
github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.6 h1:ZYv2XA+tEfFXIToR2jmBgVqQU9gERt0APbWqmUoNGnY=
|
||||||
|
github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.6/go.mod h1:ggFN8dlaLWS2R1gymBbCrvXM/bkZP7hEAa4seqDwhyg=
|
||||||
|
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 h1:SMGUnbpAcat8rIKHkBPjfv81yC46a8eCNZ2hsR2l1EI=
|
||||||
|
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1/go.mod h1:Ch/bf00Qnx77MZd49JRgHYqHQjtEmTgGU2faufpVZb0=
|
||||||
|
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U=
|
||||||
|
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
|
||||||
|
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
|
||||||
|
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3 h1:xbrxd0U9XQW8qL1BAz2XrAjAF/P2vcqUTAues9c24B8=
|
||||||
|
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3/go.mod h1:LWq2Sy8UoKKuK4lFuCNWSjJj57MhNNf2zzBWMtkAIX4=
|
||||||
|
github.com/hashicorp/go-sockaddr v1.0.5 h1:dvk7TIXCZpmfOlM+9mlcrWmWjw/wlKT+VDq2wMvfPJU=
|
||||||
|
github.com/hashicorp/go-sockaddr v1.0.5/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||||
|
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||||
|
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||||
|
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
|
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
||||||
|
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||||
|
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f h1:E87tDTVS5W65euzixn7clSzK66puSt1H4I5SC0EmHH4=
|
||||||
|
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f/go.mod h1:3J2qVK16Lq8V+wfiL2lPeDZ7UWMxk5LemerHa1p6N00=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mitchellh/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng=
|
||||||
|
github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4=
|
||||||
|
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||||
|
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||||
|
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
|
||||||
|
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
|
||||||
|
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw=
|
||||||
|
github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4=
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
|
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
|
||||||
|
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
|
||||||
|
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
||||||
|
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||||
|
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||||
|
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
|
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||||
|
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||||
|
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
|
||||||
|
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
|
||||||
|
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
|
||||||
|
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
|
||||||
|
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
|
||||||
|
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
|
||||||
|
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
||||||
|
go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||||
|
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
|
||||||
|
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||||
|
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||||
|
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
|
||||||
|
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||||
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||||
|
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/api v0.188.0 h1:51y8fJ/b1AaaBRJr4yWm96fPcuxSo0JcegXE3DaHQHw=
|
||||||
|
google.golang.org/api v0.188.0/go.mod h1:VR0d+2SIiWOYG3r/jdm7adPW9hI2aRv9ETOSCQ9Beag=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d h1:/hmn0Ku5kWij/kjGsrcJeC1T/MrJi2iNWwgAqrihFwc=
|
||||||
|
google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d h1:JU0iKnSg02Gmb5ZdV8nYsKEKsP6o/FGVWTrw4i1DA9A=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
|
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||||
|
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
|
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
|
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48=
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package credential
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/boundary-plugin-google/internal/errors"
|
||||||
|
"github.com/hashicorp/boundary-plugin-google/internal/values"
|
||||||
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CredentialAttributes contain attributes used for authenticating to Google Cloud
|
||||||
|
// and accessing a list of instances
|
||||||
|
type CredentialAttributes struct {
|
||||||
|
Project string
|
||||||
|
Zone string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCredentialAttributes checks attributes required by Google Cloud to access
|
||||||
|
// a list of instances and populate them into Boundary's host catalog
|
||||||
|
func GetCredentialAttributes(in *structpb.Struct) (*CredentialAttributes, error) {
|
||||||
|
badFields := make(map[string]string)
|
||||||
|
|
||||||
|
project, err := values.GetStringValue(in, ConstProject, true)
|
||||||
|
if err != nil {
|
||||||
|
badFields[fmt.Sprintf("attributes.%s", ConstProject)] = err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
zone, err := values.GetStringValue(in, ConstZone, true)
|
||||||
|
if err != nil {
|
||||||
|
badFields[fmt.Sprintf("attributes.%s", ConstZone)] = err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(badFields) > 0 {
|
||||||
|
return nil, errors.InvalidArgumentError("Error in the attributes provider", badFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CredentialAttributes{
|
||||||
|
Project: project,
|
||||||
|
Zone: zone,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package credential
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetCredentialAttributes(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
in map[string]any
|
||||||
|
expected *CredentialAttributes
|
||||||
|
expectedErrContains string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing project",
|
||||||
|
in: map[string]any{},
|
||||||
|
expectedErrContains: "missing required value \"project\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing zone",
|
||||||
|
in: map[string]any{},
|
||||||
|
expectedErrContains: "missing required value \"zone\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid project and zone",
|
||||||
|
in: map[string]any{
|
||||||
|
ConstProject: "test-project",
|
||||||
|
ConstZone: "us-central-1",
|
||||||
|
},
|
||||||
|
expected: &CredentialAttributes{
|
||||||
|
Project: "test-project",
|
||||||
|
Zone: "us-central-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
input, err := structpb.NewStruct(tc.in)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
actual, err := GetCredentialAttributes(input)
|
||||||
|
if tc.expectedErrContains != "" {
|
||||||
|
require.Error(err)
|
||||||
|
require.Contains(err.Error(), tc.expectedErrContains)
|
||||||
|
require.Equal(status.Code(err), codes.InvalidArgument)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(err)
|
||||||
|
require.EqualValues(tc.expected.Project, actual.Project)
|
||||||
|
require.EqualValues(tc.expected.Zone, actual.Zone)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package credential
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ConstProject defines the attribute name for a Google Cloud project
|
||||||
|
ConstProject = "project"
|
||||||
|
|
||||||
|
// ConstZone defines the attribute name for a Google Cloud zone
|
||||||
|
ConstZone = "zone"
|
||||||
|
)
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InvalidArgumentError returns an grpc invalid argument status error.
|
||||||
|
func InvalidArgumentError(msg string, f map[string]string) error {
|
||||||
|
var fieldMsgs []string
|
||||||
|
for field, val := range f {
|
||||||
|
fieldMsgs = append(fieldMsgs, fmt.Sprintf("%s: %s", field, val))
|
||||||
|
}
|
||||||
|
if len(fieldMsgs) > 0 {
|
||||||
|
sort.Strings(fieldMsgs)
|
||||||
|
msg = fmt.Sprintf("%s: [%s]", msg, strings.Join(fieldMsgs, ", "))
|
||||||
|
}
|
||||||
|
return status.Error(codes.InvalidArgument, msg)
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInvalidArgumentError(t *testing.T) {
|
||||||
|
// No error message.
|
||||||
|
err := InvalidArgumentError("", map[string]string{
|
||||||
|
"foo_key": "foo_value",
|
||||||
|
"bar_key": "bar_value",
|
||||||
|
"baz_key": "baz_value",
|
||||||
|
})
|
||||||
|
require.EqualError(t, err, "rpc error: code = InvalidArgument desc = : [bar_key: bar_value, baz_key: baz_value, foo_key: foo_value]")
|
||||||
|
|
||||||
|
// Nil fields
|
||||||
|
err = InvalidArgumentError("error message", nil)
|
||||||
|
require.EqualError(t, err, "rpc error: code = InvalidArgument desc = error message")
|
||||||
|
|
||||||
|
// Empty fields.
|
||||||
|
err = InvalidArgumentError("error message", map[string]string{})
|
||||||
|
require.EqualError(t, err, "rpc error: code = InvalidArgument desc = error message")
|
||||||
|
|
||||||
|
// Everything.
|
||||||
|
err = InvalidArgumentError("error message", map[string]string{
|
||||||
|
"foo_key": "foo_value",
|
||||||
|
"bar_key": "bar_value",
|
||||||
|
"baz_key": "baz_value",
|
||||||
|
})
|
||||||
|
require.EqualError(t, err, "rpc error: code = InvalidArgument desc = error message: [bar_key: bar_value, baz_key: baz_value, foo_key: foo_value]")
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package values
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetStringValue returns a string value and no error if the given key
|
||||||
|
// is found in the provided proto struct input. An error is returned
|
||||||
|
// if the key is not found or the value type is not a string. An error is
|
||||||
|
// returned if the parameter argument "required" is set to true
|
||||||
|
// and the value from the proto struct input is empty.
|
||||||
|
func GetStringValue(in *structpb.Struct, k string, required bool) (string, error) {
|
||||||
|
mv := in.GetFields()
|
||||||
|
v, ok := mv[k]
|
||||||
|
if !ok {
|
||||||
|
if required {
|
||||||
|
return "", fmt.Errorf("missing required value %q", k)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s, ok := v.AsInterface().(string)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("unexpected type for value %q: want string, got %T", k, v.AsInterface())
|
||||||
|
}
|
||||||
|
|
||||||
|
if s == "" && required {
|
||||||
|
return "", fmt.Errorf("value %q cannot be empty", k)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBoolValue returns a boolean value and no error if the given key
|
||||||
|
// is found in the provided proto struct input. An error is returned
|
||||||
|
// if the key is not found or the value type is not a bool.
|
||||||
|
func GetBoolValue(in *structpb.Struct, k string, required bool) (bool, error) {
|
||||||
|
mv := in.GetFields()
|
||||||
|
v, ok := mv[k]
|
||||||
|
if !ok {
|
||||||
|
if required {
|
||||||
|
return false, fmt.Errorf("missing required value %q", k)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
b, ok := v.AsInterface().(bool)
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("unexpected type for value %q: want bool, got %T", k, v.AsInterface())
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTimeValue returns a time.Time value and no error if the given key
|
||||||
|
// is found in the provided proto struct input. An error is returned
|
||||||
|
// if the key is not found or the value type is not a parsable. The
|
||||||
|
// expected time layout is RFC3339Nano.
|
||||||
|
func GetTimeValue(in *structpb.Struct, k string) (time.Time, error) {
|
||||||
|
mv := in.GetFields()
|
||||||
|
v, ok := mv[k]
|
||||||
|
if !ok {
|
||||||
|
return time.Time{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tRaw, ok := v.AsInterface().(string)
|
||||||
|
if !ok {
|
||||||
|
return time.Time{}, fmt.Errorf("unexpected type for value %q: want string, got %T", k, v.AsInterface())
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := time.Parse(time.RFC3339Nano, tRaw)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, fmt.Errorf("could not parse time in value %q: %w", k, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMapStringString returns a map[string]string value and no error if the given key
|
||||||
|
// is found in the provided proto struct input. An error is returned if the key
|
||||||
|
// is not found or the value type is not map[string]string.
|
||||||
|
func GetMapStringString(in *structpb.Struct, k string, required bool) (map[string]string, error) {
|
||||||
|
mv := in.GetFields()
|
||||||
|
v, ok := mv[k]
|
||||||
|
if !ok {
|
||||||
|
if required {
|
||||||
|
return nil, fmt.Errorf("missing required value %q", k)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
result := map[string]string{}
|
||||||
|
m, ok := v.AsInterface().(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected type for value %q: want map[string]string, got %T", k, v.AsInterface())
|
||||||
|
}
|
||||||
|
for mapKey, mapValue := range m {
|
||||||
|
e, ok := mapValue.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected type for value in map[%q]: want string, got %T", mapKey, mapValue)
|
||||||
|
}
|
||||||
|
result[mapKey] = e
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructFields returns a map[string]struct{} of the
|
||||||
|
// proto struct input.
|
||||||
|
func StructFields(s *structpb.Struct) map[string]struct{} {
|
||||||
|
m := make(map[string]struct{}, len(s.GetFields()))
|
||||||
|
for k := range s.GetFields() {
|
||||||
|
m[k] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package values
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetStringValue(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
in map[string]any
|
||||||
|
key string
|
||||||
|
required bool
|
||||||
|
expected string
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "required missing",
|
||||||
|
in: map[string]any{},
|
||||||
|
key: "foo",
|
||||||
|
required: true,
|
||||||
|
expectedErr: "missing required value \"foo\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "optional missing",
|
||||||
|
in: map[string]any{},
|
||||||
|
key: "foo",
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-string value",
|
||||||
|
in: map[string]any{
|
||||||
|
"foo": 1,
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
expectedErr: "unexpected type for value \"foo\": want string, got float64",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "required empty",
|
||||||
|
in: map[string]any{
|
||||||
|
"foo": "",
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
required: true,
|
||||||
|
expectedErr: "value \"foo\" cannot be empty",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "good",
|
||||||
|
in: map[string]any{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
expected: "bar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
input, err := structpb.NewStruct(tc.in)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
actual, err := GetStringValue(input, tc.key, tc.required)
|
||||||
|
if tc.expectedErr != "" {
|
||||||
|
require.EqualError(err, tc.expectedErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal(tc.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetBoolValue(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
in map[string]any
|
||||||
|
key string
|
||||||
|
required bool
|
||||||
|
expected bool
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "required missing",
|
||||||
|
in: map[string]any{},
|
||||||
|
key: "foo",
|
||||||
|
required: true,
|
||||||
|
expectedErr: "missing required value \"foo\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "optional missing",
|
||||||
|
in: map[string]any{},
|
||||||
|
key: "foo",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-bool value",
|
||||||
|
in: map[string]any{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
expectedErr: "unexpected type for value \"foo\": want bool, got string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "good",
|
||||||
|
in: map[string]any{
|
||||||
|
"foo": true,
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
input, err := structpb.NewStruct(tc.in)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
actual, err := GetBoolValue(input, tc.key, tc.required)
|
||||||
|
if tc.expectedErr != "" {
|
||||||
|
require.EqualError(err, tc.expectedErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal(tc.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTimeValue(t *testing.T) {
|
||||||
|
staticTime := time.Now()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
in map[string]any
|
||||||
|
key string
|
||||||
|
expected time.Time
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing",
|
||||||
|
in: map[string]any{},
|
||||||
|
key: "foo",
|
||||||
|
expected: time.Time{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-time value",
|
||||||
|
in: map[string]any{
|
||||||
|
"foo": 1,
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
expectedErr: "unexpected type for value \"foo\": want string, got float64",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad parse",
|
||||||
|
in: map[string]any{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
expectedErr: "could not parse time in value \"foo\": parsing time \"bar\" as \"2006-01-02T15:04:05.999999999Z07:00\": cannot parse \"bar\" as \"2006\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "good",
|
||||||
|
in: map[string]any{
|
||||||
|
"foo": staticTime.Format(time.RFC3339Nano),
|
||||||
|
},
|
||||||
|
key: "foo",
|
||||||
|
expected: func() time.Time {
|
||||||
|
u, err := time.Parse(time.RFC3339Nano, staticTime.Format(time.RFC3339Nano))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return u
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
input, err := structpb.NewStruct(tc.in)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
actual, err := GetTimeValue(input, tc.key)
|
||||||
|
if tc.expectedErr != "" {
|
||||||
|
require.EqualError(err, tc.expectedErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal(tc.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
computepb "cloud.google.com/go/compute/apiv1/computepb"
|
||||||
|
cred "github.com/hashicorp/boundary-plugin-google/internal/credential"
|
||||||
|
"github.com/hashicorp/boundary-plugin-google/internal/errors"
|
||||||
|
"github.com/hashicorp/boundary-plugin-google/internal/values"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CatalogAttributes defines a set of attributes for the host catalog
|
||||||
|
type CatalogAttributes struct {
|
||||||
|
*cred.CredentialAttributes
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCatalogAttributes(in *structpb.Struct) (*CatalogAttributes, error) {
|
||||||
|
unknownFields := values.StructFields(in)
|
||||||
|
badFields := make(map[string]string)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
credAttributes, err := cred.GetCredentialAttributes(in)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for s := range unknownFields {
|
||||||
|
switch s {
|
||||||
|
// Ignore knownFields from CredentialAttributes
|
||||||
|
case cred.ConstProject:
|
||||||
|
continue
|
||||||
|
case cred.ConstZone:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
badFields[fmt.Sprintf("attributes.%s", s)] = "unrecognized field"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(badFields) > 0 {
|
||||||
|
return nil, errors.InvalidArgumentError("Invalid arguments in catalog attributes", badFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CatalogAttributes{
|
||||||
|
CredentialAttributes: credAttributes,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAttributes defines attributes fro the host set
|
||||||
|
type SetAttributes struct {
|
||||||
|
Filter string `mapstructure:"filter"`
|
||||||
|
InstanceGroup string `mapstructure:"instance_group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSetAttributes(in *structpb.Struct) (*SetAttributes, error) {
|
||||||
|
var setAttrs SetAttributes
|
||||||
|
|
||||||
|
badFields := make(map[string]string)
|
||||||
|
unknownFields := values.StructFields(in)
|
||||||
|
|
||||||
|
if _, filter := unknownFields[ConstListInstancesFilter]; filter {
|
||||||
|
if _, ig := unknownFields[ConstInstanceGroup]; ig {
|
||||||
|
return nil, errors.InvalidArgumentError("Error in the attributes provided, cannot define both filter and instance group", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(unknownFields, ConstListInstancesFilter)
|
||||||
|
delete(unknownFields, ConstInstanceGroup)
|
||||||
|
|
||||||
|
for a := range unknownFields {
|
||||||
|
badFields[fmt.Sprintf("attributes.%s", a)] = "unrecognized field"
|
||||||
|
}
|
||||||
|
if len(badFields) > 0 {
|
||||||
|
return nil, errors.InvalidArgumentError("Error in the attributes provided", badFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mapstructure complains if it expects a slice as output and sees a scalar
|
||||||
|
// value. Rather than use WeakDecode and risk unintended consequences, I'm
|
||||||
|
// manually making this change if necessary.
|
||||||
|
inMap := in.AsMap()
|
||||||
|
if filtersRaw, ok := inMap[ConstListInstancesFilter]; ok {
|
||||||
|
switch filterVal := filtersRaw.(type) {
|
||||||
|
case string:
|
||||||
|
inMap[ConstListInstancesFilter] = string(filterVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if instanceGroupRaw, ok := inMap[ConstInstanceGroup]; ok {
|
||||||
|
switch instanceGroupValue := instanceGroupRaw.(type) {
|
||||||
|
case string:
|
||||||
|
inMap[ConstInstanceGroup] = string(instanceGroupValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mapstructure.Decode(inMap, &setAttrs); err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "error decoding set attributes: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &setAttrs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildListInstancesRequest(attributes *SetAttributes, catalog *CatalogAttributes) *computepb.ListInstancesRequest {
|
||||||
|
request := &computepb.ListInstancesRequest{
|
||||||
|
Project: catalog.Project,
|
||||||
|
Zone: catalog.Zone,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(attributes.Filter) > 1 {
|
||||||
|
request.Filter = &attributes.Filter
|
||||||
|
}
|
||||||
|
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildListInstanceGroupsRequest(attributes *SetAttributes, catalog *CatalogAttributes) *computepb.ListInstancesInstanceGroupsRequest {
|
||||||
|
request := &computepb.ListInstancesInstanceGroupsRequest{
|
||||||
|
InstanceGroup: attributes.InstanceGroup,
|
||||||
|
Project: catalog.Project,
|
||||||
|
Zone: catalog.Zone,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(attributes.Filter) > 1 {
|
||||||
|
request.Filter = &attributes.Filter
|
||||||
|
}
|
||||||
|
|
||||||
|
return request
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
cred "github.com/hashicorp/boundary-plugin-google/internal/credential"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetCatalogAttributes(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
in *structpb.Struct
|
||||||
|
expected *CatalogAttributes
|
||||||
|
expectedErrContains string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing project and zone",
|
||||||
|
in: &structpb.Struct{
|
||||||
|
Fields: make(map[string]*structpb.Value),
|
||||||
|
},
|
||||||
|
expectedErrContains: "attributes.project: missing required value \"project\", attributes.zone: missing required value \"zone\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown fields",
|
||||||
|
in: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
"project": structpb.NewStringValue("test-12345"),
|
||||||
|
"zone": structpb.NewStringValue("us-central-1a"),
|
||||||
|
"foo": structpb.NewBoolValue(true),
|
||||||
|
"bar": structpb.NewBoolValue(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErrContains: "attributes.bar: unrecognized field, attributes.foo: unrecognized field",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid project and zone",
|
||||||
|
in: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
"project": structpb.NewStringValue("test-12345"),
|
||||||
|
"zone": structpb.NewStringValue("us-central-1a"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &CatalogAttributes{&cred.CredentialAttributes{
|
||||||
|
Project: "test-12345",
|
||||||
|
Zone: "us-central-1a",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
actual, err := getCatalogAttributes(tc.in)
|
||||||
|
if tc.expectedErrContains != "" {
|
||||||
|
require.Error(err)
|
||||||
|
require.Contains(err.Error(), tc.expectedErrContains)
|
||||||
|
require.Equal(status.Code(err), codes.InvalidArgument)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal(tc.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSetAttributes(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
in map[string]any
|
||||||
|
normalized map[string]any
|
||||||
|
expected *SetAttributes
|
||||||
|
expectedErrContains string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing",
|
||||||
|
in: map[string]any{},
|
||||||
|
expected: &SetAttributes{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid example filter",
|
||||||
|
in: map[string]any{
|
||||||
|
ConstListInstancesFilter: "tags.items=my-tag AND -tags.items=my-other-tag) OR tags.items=alternative-tag",
|
||||||
|
},
|
||||||
|
expected: &SetAttributes{
|
||||||
|
Filter: "tags.items=my-tag AND -tags.items=my-other-tag) OR tags.items=alternative-tag",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid example instance group",
|
||||||
|
in: map[string]any{
|
||||||
|
ConstInstanceGroup: "test",
|
||||||
|
},
|
||||||
|
expected: &SetAttributes{
|
||||||
|
InstanceGroup: "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown fields",
|
||||||
|
in: map[string]any{
|
||||||
|
"foo": true,
|
||||||
|
"bar": true,
|
||||||
|
},
|
||||||
|
expectedErrContains: "attributes.bar: unrecognized field, attributes.foo: unrecognized field",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid, cannot have both instance filter and group defined",
|
||||||
|
in: map[string]any{
|
||||||
|
ConstListInstancesFilter: "test",
|
||||||
|
ConstInstanceGroup: "test",
|
||||||
|
},
|
||||||
|
expectedErrContains: "Error in the attributes provided, cannot define both filter and instance group",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
input, err := structpb.NewStruct(tc.in)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
actual, err := getSetAttributes(input)
|
||||||
|
if tc.expectedErrContains != "" {
|
||||||
|
require.Error(err)
|
||||||
|
require.Contains(err.Error(), tc.expectedErrContains)
|
||||||
|
require.Equal(status.Code(err), codes.InvalidArgument)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal(tc.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
compute "cloud.google.com/go/compute/apiv1"
|
||||||
|
computepb "cloud.google.com/go/compute/apiv1/computepb"
|
||||||
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
|
"google.golang.org/api/iterator"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
NumberMaxResults = uint32(100)
|
||||||
|
)
|
||||||
|
|
||||||
|
// GoogleClient contains clients and attributes for authenticating to Google Cloud
|
||||||
|
// and finding instances
|
||||||
|
type GoogleClient struct {
|
||||||
|
InstancesClient *compute.InstancesClient
|
||||||
|
InstanceGroupClient *compute.InstanceGroupsClient
|
||||||
|
Context context.Context
|
||||||
|
Project string
|
||||||
|
Zone string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GoogleClient) getInstances(request *computepb.ListInstancesRequest) ([]*computepb.Instance, error) {
|
||||||
|
hosts := []*computepb.Instance{}
|
||||||
|
it := c.InstancesClient.List(c.Context, request)
|
||||||
|
for {
|
||||||
|
resp, err := it.Next()
|
||||||
|
if err == iterator.Done {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "error listing instances: %s", err)
|
||||||
|
}
|
||||||
|
hosts = append(hosts, resp)
|
||||||
|
}
|
||||||
|
return hosts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GoogleClient) getInstancesForInstanceGroup(request *computepb.ListInstancesInstanceGroupsRequest) ([]*computepb.Instance, error) {
|
||||||
|
hosts := []*computepb.Instance{}
|
||||||
|
instances := c.InstanceGroupClient.ListInstances(c.Context, request)
|
||||||
|
|
||||||
|
for {
|
||||||
|
resp, err := instances.Next()
|
||||||
|
if err == iterator.Done {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "error listing instances for instance group %s: %s", request.InstanceGroup, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
instance, err := c.InstancesClient.Get(c.Context, &computepb.GetInstanceRequest{
|
||||||
|
Instance: path.Base(resp.GetInstance()),
|
||||||
|
Project: request.Project,
|
||||||
|
Zone: request.Zone,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "error getting instance %s for instance group %s: %s", resp.GetInstance(), request.InstanceGroup, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hosts = append(hosts, instance)
|
||||||
|
}
|
||||||
|
return hosts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func instanceToHost(instance *computepb.Instance) (*pb.ListHostsResponseHost, error) {
|
||||||
|
if instance.GetSelfLink() == "" {
|
||||||
|
return nil, errors.New("response integrity error: missing instance self-link")
|
||||||
|
}
|
||||||
|
|
||||||
|
if instance.GetName() == "" {
|
||||||
|
return nil, errors.New("response integrity error: missing instance name")
|
||||||
|
}
|
||||||
|
|
||||||
|
result := new(pb.ListHostsResponseHost)
|
||||||
|
|
||||||
|
result.ExternalId = instance.GetSelfLink()
|
||||||
|
result.ExternalName = instance.GetName()
|
||||||
|
|
||||||
|
// Now go through all of the interfaces and log the IP address of
|
||||||
|
// every interface.
|
||||||
|
for _, iface := range instance.GetNetworkInterfaces() {
|
||||||
|
// Populate default IP addresses/DNS name similar to how we do
|
||||||
|
// for the entire instance.
|
||||||
|
result.IpAddresses = appendDistinct(result.IpAddresses, iface.NetworkIP)
|
||||||
|
|
||||||
|
for _, external := range iface.AccessConfigs {
|
||||||
|
result.IpAddresses = appendDistinct(result.IpAddresses, external.NatIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the IPv6 addresses.
|
||||||
|
result.IpAddresses = appendDistinct(result.IpAddresses, iface.Ipv6Address)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendDistinct will append the elements to the slice
|
||||||
|
// if an element is not nil, empty, and does not exist in slice.
|
||||||
|
func appendDistinct(slice []string, elems ...*string) []string {
|
||||||
|
for _, e := range elems {
|
||||||
|
if e == nil || *e == "" || stringInSlice(slice, *e) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
slice = append(slice, *e)
|
||||||
|
}
|
||||||
|
return slice
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringInSlice(s []string, x string) bool {
|
||||||
|
for _, y := range s {
|
||||||
|
if x == y {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"cloud.google.com/go/compute/apiv1/computepb"
|
||||||
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInstanceToHost(t *testing.T) {
|
||||||
|
exampleId := "https://www.googleapis.com/compute/v1/projects/test-project/zones/us-central1-a/instances/test-instance"
|
||||||
|
examplePrivateIp := "10.0.0.1"
|
||||||
|
examplePrivateIp2 := "10.0.0.2"
|
||||||
|
examplePublicIp := "1.1.1.1"
|
||||||
|
examplePublicIp2 := "1.1.1.2"
|
||||||
|
exampleIPv6 := "some::fake::address"
|
||||||
|
exampleName := "test-instance"
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
instance *computepb.Instance
|
||||||
|
expected *pb.ListHostsResponseHost
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing instance self-link",
|
||||||
|
instance: &computepb.Instance{
|
||||||
|
Name: &exampleName,
|
||||||
|
},
|
||||||
|
expectedErr: "response integrity error: missing instance self-link",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing instance name",
|
||||||
|
instance: &computepb.Instance{
|
||||||
|
SelfLink: &exampleId,
|
||||||
|
},
|
||||||
|
expectedErr: "response integrity error: missing instance name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "good, single private IP with public IP address",
|
||||||
|
instance: &computepb.Instance{
|
||||||
|
SelfLink: &exampleId,
|
||||||
|
Name: &exampleName,
|
||||||
|
NetworkInterfaces: []*computepb.NetworkInterface{
|
||||||
|
{
|
||||||
|
NetworkIP: &examplePrivateIp,
|
||||||
|
AccessConfigs: []*computepb.AccessConfig{
|
||||||
|
{
|
||||||
|
NatIP: &examplePublicIp,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &pb.ListHostsResponseHost{
|
||||||
|
ExternalId: exampleId,
|
||||||
|
ExternalName: exampleName,
|
||||||
|
IpAddresses: []string{examplePrivateIp, examplePublicIp},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "good, single private IP address",
|
||||||
|
instance: &computepb.Instance{
|
||||||
|
SelfLink: &exampleId,
|
||||||
|
Name: &exampleName,
|
||||||
|
NetworkInterfaces: []*computepb.NetworkInterface{
|
||||||
|
{
|
||||||
|
NetworkIP: &examplePrivateIp,
|
||||||
|
AccessConfigs: []*computepb.AccessConfig{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &pb.ListHostsResponseHost{
|
||||||
|
ExternalId: exampleId,
|
||||||
|
ExternalName: exampleName,
|
||||||
|
IpAddresses: []string{examplePrivateIp},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "good, multiple interfaces",
|
||||||
|
instance: &computepb.Instance{
|
||||||
|
SelfLink: &exampleId,
|
||||||
|
Name: &exampleName,
|
||||||
|
NetworkInterfaces: []*computepb.NetworkInterface{
|
||||||
|
{
|
||||||
|
NetworkIP: &examplePrivateIp,
|
||||||
|
AccessConfigs: []*computepb.AccessConfig{
|
||||||
|
{
|
||||||
|
NatIP: &examplePublicIp,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NetworkIP: &examplePrivateIp2,
|
||||||
|
AccessConfigs: []*computepb.AccessConfig{
|
||||||
|
{
|
||||||
|
NatIP: &examplePublicIp2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &pb.ListHostsResponseHost{
|
||||||
|
ExternalId: exampleId,
|
||||||
|
ExternalName: exampleName,
|
||||||
|
IpAddresses: []string{examplePrivateIp, examplePublicIp, examplePrivateIp2, examplePublicIp2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "good, single private IP address with IPv6",
|
||||||
|
instance: &computepb.Instance{
|
||||||
|
SelfLink: &exampleId,
|
||||||
|
Name: &exampleName,
|
||||||
|
NetworkInterfaces: []*computepb.NetworkInterface{
|
||||||
|
{
|
||||||
|
NetworkIP: &examplePrivateIp,
|
||||||
|
Ipv6Address: &exampleIPv6,
|
||||||
|
AccessConfigs: []*computepb.AccessConfig{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &pb.ListHostsResponseHost{
|
||||||
|
ExternalId: exampleId,
|
||||||
|
ExternalName: exampleName,
|
||||||
|
IpAddresses: []string{examplePrivateIp, exampleIPv6},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
actual, err := instanceToHost(tc.instance)
|
||||||
|
if tc.expectedErr != "" {
|
||||||
|
require.EqualError(err, tc.expectedErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal(tc.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ConstListInstancesFilter refers to a Google Cloud SDK filter to search for instances
|
||||||
|
ConstListInstancesFilter = "filter"
|
||||||
|
|
||||||
|
// ConstInstanceGroup refers to the name of a Google Cloud instance group
|
||||||
|
ConstInstanceGroup = "instance_group"
|
||||||
|
)
|
||||||
|
|
||||||
|
var allowedSetFields = map[string]struct{}{
|
||||||
|
ConstListInstancesFilter: {},
|
||||||
|
ConstInstanceGroup: {},
|
||||||
|
}
|
||||||
@@ -0,0 +1,285 @@
|
|||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
compute "cloud.google.com/go/compute/apiv1"
|
||||||
|
computepb "cloud.google.com/go/compute/apiv1/computepb"
|
||||||
|
errors "github.com/hashicorp/boundary-plugin-google/internal/errors"
|
||||||
|
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets"
|
||||||
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GooglePlugin implements the GooglePluginServiceServer interface for the
|
||||||
|
// Google host service plugin.
|
||||||
|
type GooglePlugin struct {
|
||||||
|
pb.UnimplementedHostPluginServiceServer
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ pb.HostPluginServiceServer = (*GooglePlugin)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// OnCreateCatalog is called when a dynamic host catalog is created.
|
||||||
|
func (p *GooglePlugin) OnCreateCatalog(_ context.Context, req *pb.OnCreateCatalogRequest) (*pb.OnCreateCatalogResponse, error) {
|
||||||
|
catalog := req.GetCatalog()
|
||||||
|
if catalog == nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "catalog is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs := catalog.GetAttributes()
|
||||||
|
if attrs == nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "attributes are required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := getCatalogAttributes(attrs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.OnCreateCatalogResponse{
|
||||||
|
Persisted: &pb.HostCatalogPersisted{
|
||||||
|
Secrets: nil,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnUpdateCatalog is called when a dynamic host catalog is updated.
|
||||||
|
func (p *GooglePlugin) OnUpdateCatalog(_ context.Context, req *pb.OnUpdateCatalogRequest) (*pb.OnUpdateCatalogResponse, error) {
|
||||||
|
currentCatalog := req.GetCurrentCatalog()
|
||||||
|
if currentCatalog == nil {
|
||||||
|
return nil, status.Error(codes.FailedPrecondition, "current catalog is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets := req.GetNewCatalog().GetSecrets()
|
||||||
|
if secrets == nil {
|
||||||
|
// If new secrets weren't passed in, don't rotate what we have on
|
||||||
|
// update.
|
||||||
|
return &pb.OnUpdateCatalogResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.OnUpdateCatalogResponse{
|
||||||
|
Persisted: &pb.HostCatalogPersisted{
|
||||||
|
Secrets: nil,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnDeleteCatalog is called when a dynamic host catalog is deleted.
|
||||||
|
func (p *GooglePlugin) OnDeleteCatalog(ctx context.Context, req *pb.OnDeleteCatalogRequest) (*pb.OnDeleteCatalogResponse, error) {
|
||||||
|
catalog := req.GetCatalog()
|
||||||
|
if catalog == nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "new catalog is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs := catalog.GetAttributes()
|
||||||
|
if attrs == nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "new catalog missing attributes")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := getCatalogAttributes(attrs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.OnDeleteCatalogResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnCreateSet is called when a dynamic host set is created.
|
||||||
|
func (p *GooglePlugin) OnCreateSet(_ context.Context, req *pb.OnCreateSetRequest) (*pb.OnCreateSetResponse, error) {
|
||||||
|
if err := validateSet(req.GetSet()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &pb.OnCreateSetResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnUpdateSet is called when a dynamic host set is updated.
|
||||||
|
func (p *GooglePlugin) OnUpdateSet(_ context.Context, req *pb.OnUpdateSetRequest) (*pb.OnUpdateSetResponse, error) {
|
||||||
|
if err := validateSet(req.GetNewSet()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &pb.OnUpdateSetResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnDeleteSet is called when a dynamic host set is deleted.
|
||||||
|
func (p *GooglePlugin) OnDeleteSet(ctx context.Context, req *pb.OnDeleteSetRequest) (*pb.OnDeleteSetResponse, error) {
|
||||||
|
return &pb.OnDeleteSetResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListHosts returns the list of instances based on filter or instance group name.
|
||||||
|
func (p *GooglePlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*pb.ListHostsResponse, error) {
|
||||||
|
catalog := req.GetCatalog()
|
||||||
|
if catalog == nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "catalog is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
catalogAttrsRaw := catalog.GetAttributes()
|
||||||
|
if catalogAttrsRaw == nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "catalog missing attributes")
|
||||||
|
}
|
||||||
|
|
||||||
|
catalogAttributes, err := getCatalogAttributes(catalogAttrsRaw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sets := req.GetSets()
|
||||||
|
if sets == nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "sets is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
type hostSetQuery struct {
|
||||||
|
Id string
|
||||||
|
InputInstances *computepb.ListInstancesRequest
|
||||||
|
InputGroups *computepb.ListInstancesInstanceGroupsRequest
|
||||||
|
Project string
|
||||||
|
Zone string
|
||||||
|
Output []*computepb.Instance
|
||||||
|
OutputHosts []*pb.ListHostsResponseHost
|
||||||
|
}
|
||||||
|
|
||||||
|
queries := make([]hostSetQuery, len(sets))
|
||||||
|
for i, set := range sets {
|
||||||
|
// Validate Id since we use it in output
|
||||||
|
if set.GetId() == "" {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "set missing id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if set.GetAttributes() == nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "set missing attributes")
|
||||||
|
}
|
||||||
|
setAttrs, err := getSetAttributes(set.GetAttributes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if setAttrs.InstanceGroup != "" {
|
||||||
|
queries[i] = hostSetQuery{
|
||||||
|
Id: set.GetId(),
|
||||||
|
InputGroups: buildListInstanceGroupsRequest(setAttrs, catalogAttributes),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
queries[i] = hostSetQuery{
|
||||||
|
Id: set.GetId(),
|
||||||
|
InputInstances: buildListInstancesRequest(setAttrs, catalogAttributes),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instancesClient, err := compute.NewInstancesRESTClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "error creating NewInstancesRESTClient: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceGroupsClient, err := compute.NewInstanceGroupsRESTClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "error creating NewInstanceGroupsRESTClient: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gclient := GoogleClient{
|
||||||
|
InstancesClient: instancesClient,
|
||||||
|
InstanceGroupClient: instanceGroupsClient,
|
||||||
|
Context: ctx,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run all queries now and assemble output.
|
||||||
|
var maxLen int
|
||||||
|
for i, query := range queries {
|
||||||
|
var output []*computepb.Instance
|
||||||
|
if query.InputGroups != nil {
|
||||||
|
output, err = gclient.getInstancesForInstanceGroup(query.InputGroups)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "error running getInstancesForInstanceGroup for host set id %q: %s", query.Id, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
output, err = gclient.getInstances(query.InputInstances)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "error running getInstances for host set id %q: %s", query.Id, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queries[i].Output = output
|
||||||
|
|
||||||
|
// Process the output here, we will normalize this into a single
|
||||||
|
// set of hosts afterwards (possibly removing duplicates).
|
||||||
|
for _, instance := range output {
|
||||||
|
host, err := instanceToHost(instance)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "error processing host results for host set id %q: %s", query.Id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
queries[i].OutputHosts = append(queries[i].OutputHosts, host)
|
||||||
|
maxLen++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now de-duplicate the hosts for the output. Maintain two sets:
|
||||||
|
// * A slice of hosts that will be used in the output
|
||||||
|
// * A map of hosts indexed by their external ID
|
||||||
|
//
|
||||||
|
// The map will is used in de-duplication to determine whether or
|
||||||
|
// not we've seen the host before to simply add the query's set ID
|
||||||
|
// to the list of set IDs that the host was seen in.
|
||||||
|
hostResultSlice := make([]*pb.ListHostsResponseHost, 0, maxLen)
|
||||||
|
hostResultMap := make(map[string]*pb.ListHostsResponseHost)
|
||||||
|
for _, query := range queries {
|
||||||
|
for _, host := range query.OutputHosts {
|
||||||
|
if existingHost, ok := hostResultMap[host.ExternalId]; ok {
|
||||||
|
// Existing host, just add the set ID to the list of seen IDs
|
||||||
|
// and continue
|
||||||
|
existingHost.SetIds = append(existingHost.SetIds, query.Id)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will be the first seen entry, so append the set ID to
|
||||||
|
// this host, and add it.
|
||||||
|
host.SetIds = append(host.SetIds, query.Id)
|
||||||
|
hostResultSlice = append(hostResultSlice, host)
|
||||||
|
hostResultMap[host.ExternalId] = host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.ListHostsResponse{
|
||||||
|
Hosts: hostResultSlice,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSet(s *hostsets.HostSet) error {
|
||||||
|
if s == nil {
|
||||||
|
return status.Error(codes.InvalidArgument, "set is nil")
|
||||||
|
}
|
||||||
|
var attrs SetAttributes
|
||||||
|
attrMap := s.GetAttributes().AsMap()
|
||||||
|
if err := mapstructure.Decode(attrMap, &attrs); err != nil {
|
||||||
|
return status.Errorf(codes.InvalidArgument, "error decoding set attributes: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
badFields := make(map[string]string)
|
||||||
|
_, filterSet := attrMap[ConstListInstancesFilter]
|
||||||
|
_, instanceGroupSet := attrMap[ConstInstanceGroup]
|
||||||
|
|
||||||
|
if instanceGroupSet && filterSet {
|
||||||
|
badFields["attributes"] = "must set instance group or filter, cannot set both"
|
||||||
|
} else if instanceGroupSet && len(attrs.InstanceGroup) == 0 {
|
||||||
|
badFields[fmt.Sprintf("attributes.%s", ConstInstanceGroup)] = "must not be empty."
|
||||||
|
} else if filterSet && len(attrs.Filter) == 0 {
|
||||||
|
badFields[fmt.Sprintf("attributes.%s", ConstListInstancesFilter)] = "must not be empty."
|
||||||
|
}
|
||||||
|
|
||||||
|
for f := range attrMap {
|
||||||
|
if _, ok := allowedSetFields[f]; !ok {
|
||||||
|
badFields[fmt.Sprintf("attributes.%s", f)] = "Unrecognized field."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(badFields) > 0 {
|
||||||
|
return errors.InvalidArgumentError("Invalid arguments in the new set", badFields)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,701 @@
|
|||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
cred "github.com/hashicorp/boundary-plugin-google/internal/credential"
|
||||||
|
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs"
|
||||||
|
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets"
|
||||||
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
|
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func wrapMap(t *testing.T, in map[string]interface{}) *structpb.Struct {
|
||||||
|
t.Helper()
|
||||||
|
out, err := structpb.NewStruct(in)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListHosts(t *testing.T) {
|
||||||
|
t.Skip("TODO: this needs a secrets file to run - maybe only manually?")
|
||||||
|
ctx := context.Background()
|
||||||
|
p := &GooglePlugin{}
|
||||||
|
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, wd)
|
||||||
|
project, err := parseutil.ParsePath("file://" + filepath.Join(wd, "secrets", "project"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
zone, err := parseutil.ParsePath("file://" + filepath.Join(wd, "secrets", "zone"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
hostCatalogAttributes := &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: wrapMap(t, map[string]interface{}{
|
||||||
|
cred.ConstProject: project,
|
||||||
|
cred.ConstZone: zone,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
req *pb.ListHostsRequest
|
||||||
|
expected []*pb.ListHostsResponseHost
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil catalog",
|
||||||
|
req: &pb.ListHostsRequest{},
|
||||||
|
expectedErr: "catalog is nil",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "project not defined",
|
||||||
|
req: &pb.ListHostsRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: wrapMap(t, map[string]interface{}{
|
||||||
|
cred.ConstZone: zone,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: "attributes.project: missing required value \"project\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get all three instances",
|
||||||
|
req: &pb.ListHostsRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: hostCatalogAttributes,
|
||||||
|
},
|
||||||
|
Sets: []*hostsets.HostSet{
|
||||||
|
{
|
||||||
|
Id: "get-all-instances",
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{
|
||||||
|
Attributes: wrapMap(t, map[string]interface{}{}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []*pb.ListHostsResponseHost{
|
||||||
|
{
|
||||||
|
Name: "boundary-0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "boundary-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "boundary-2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get one instance by name",
|
||||||
|
req: &pb.ListHostsRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: hostCatalogAttributes,
|
||||||
|
},
|
||||||
|
Sets: []*hostsets.HostSet{
|
||||||
|
{
|
||||||
|
Id: "get-one-instance-by-name",
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{
|
||||||
|
Attributes: wrapMap(t, map[string]interface{}{
|
||||||
|
ConstListInstancesFilter: "name = boundary-1",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []*pb.ListHostsResponseHost{
|
||||||
|
{
|
||||||
|
Name: "boundary-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get instance group",
|
||||||
|
req: &pb.ListHostsRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: hostCatalogAttributes,
|
||||||
|
},
|
||||||
|
Sets: []*hostsets.HostSet{
|
||||||
|
{
|
||||||
|
Id: "get-instance-group",
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{
|
||||||
|
Attributes: wrapMap(t, map[string]interface{}{
|
||||||
|
ConstInstanceGroup: "boundary-servers",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []*pb.ListHostsResponseHost{
|
||||||
|
{
|
||||||
|
Name: "boundary-0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "boundary-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "boundary-2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get two specific instances with two host sets",
|
||||||
|
req: &pb.ListHostsRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: hostCatalogAttributes,
|
||||||
|
},
|
||||||
|
Sets: []*hostsets.HostSet{
|
||||||
|
{
|
||||||
|
Id: "get-one-instance-by-name-0",
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{
|
||||||
|
Attributes: wrapMap(t, map[string]interface{}{
|
||||||
|
ConstListInstancesFilter: "name = boundary-0",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "get-one-instance-by-name-2",
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{
|
||||||
|
Attributes: wrapMap(t, map[string]interface{}{
|
||||||
|
ConstListInstancesFilter: "name = boundary-2",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []*pb.ListHostsResponseHost{
|
||||||
|
{
|
||||||
|
Name: "boundary-0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "boundary-2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get one instance and an instance group in two host sets",
|
||||||
|
req: &pb.ListHostsRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: hostCatalogAttributes,
|
||||||
|
},
|
||||||
|
Sets: []*hostsets.HostSet{
|
||||||
|
{
|
||||||
|
Id: "get-one-instance-by-name",
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{
|
||||||
|
Attributes: wrapMap(t, map[string]interface{}{
|
||||||
|
ConstListInstancesFilter: "name = boundary-1",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "get-instance-group",
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{
|
||||||
|
Attributes: wrapMap(t, map[string]interface{}{
|
||||||
|
ConstInstanceGroup: "boundary-servers",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []*pb.ListHostsResponseHost{
|
||||||
|
{
|
||||||
|
Name: "boundary-0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "boundary-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "boundary-2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "get an instance group and one instance in two host sets",
|
||||||
|
req: &pb.ListHostsRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: hostCatalogAttributes,
|
||||||
|
},
|
||||||
|
Sets: []*hostsets.HostSet{
|
||||||
|
{
|
||||||
|
Id: "get-instance-group",
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{
|
||||||
|
Attributes: wrapMap(t, map[string]interface{}{
|
||||||
|
ConstInstanceGroup: "boundary-servers",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "get-one-instance-by-name",
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{
|
||||||
|
Attributes: wrapMap(t, map[string]interface{}{
|
||||||
|
ConstListInstancesFilter: "name = boundary-2",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []*pb.ListHostsResponseHost{
|
||||||
|
{
|
||||||
|
Name: "boundary-0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "boundary-1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "boundary-2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid filter",
|
||||||
|
req: &pb.ListHostsRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: hostCatalogAttributes,
|
||||||
|
},
|
||||||
|
Sets: []*hostsets.HostSet{
|
||||||
|
{
|
||||||
|
Id: "invalid-filter",
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{
|
||||||
|
Attributes: wrapMap(t, map[string]interface{}{
|
||||||
|
ConstListInstancesFilter: "not-a-filter",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: "Invalid list filter expression",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
actual, err := p.ListHosts(ctx, tc.req)
|
||||||
|
if tc.expectedErr != "" {
|
||||||
|
require.Contains(err.Error(), tc.expectedErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal(len(tc.expected), len(actual.GetHosts()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateCatalog(t *testing.T) {
|
||||||
|
t.Skip("TODO: this needs a secrets file to run - maybe only manually?")
|
||||||
|
ctx := context.Background()
|
||||||
|
p := &GooglePlugin{}
|
||||||
|
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, wd)
|
||||||
|
project, err := parseutil.ParsePath("file://" + filepath.Join(wd, "secrets", "project"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
zone, err := parseutil.ParsePath("file://" + filepath.Join(wd, "secrets", "zone"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
hostCatalogAttributes := &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: wrapMap(t, map[string]interface{}{
|
||||||
|
cred.ConstProject: project,
|
||||||
|
cred.ConstZone: zone,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
req *pb.OnCreateCatalogRequest
|
||||||
|
expected *pb.HostCatalogPersisted
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil catalog",
|
||||||
|
req: &pb.OnCreateCatalogRequest{},
|
||||||
|
expectedErr: "catalog is nil",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil attributes",
|
||||||
|
req: &pb.OnCreateCatalogRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{},
|
||||||
|
},
|
||||||
|
expectedErr: "attributes are required",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "error reading attributes",
|
||||||
|
req: &pb.OnCreateCatalogRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: new(structpb.Struct),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: "attributes.project: missing required value \"project\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "do not persist secrets, use gcloud application default creds",
|
||||||
|
req: &pb.OnCreateCatalogRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: hostCatalogAttributes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &pb.HostCatalogPersisted{
|
||||||
|
Secrets: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
actual, err := p.OnCreateCatalog(ctx, tc.req)
|
||||||
|
if tc.expectedErr != "" {
|
||||||
|
require.Contains(err.Error(), tc.expectedErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(err)
|
||||||
|
require.Nil(actual.GetPersisted().Secrets)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateCatalog(t *testing.T) {
|
||||||
|
t.Skip("TODO: this needs a secrets file to run - maybe only manually?")
|
||||||
|
ctx := context.Background()
|
||||||
|
p := &GooglePlugin{}
|
||||||
|
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, wd)
|
||||||
|
project, err := parseutil.ParsePath("file://" + filepath.Join(wd, "secrets", "project"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
zone, err := parseutil.ParsePath("file://" + filepath.Join(wd, "secrets", "zone"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
hostCatalogAttributes := &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: wrapMap(t, map[string]interface{}{
|
||||||
|
cred.ConstProject: project,
|
||||||
|
cred.ConstZone: zone,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
req *pb.OnCreateCatalogRequest
|
||||||
|
expected *pb.HostCatalogPersisted
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil catalog",
|
||||||
|
req: &pb.OnCreateCatalogRequest{},
|
||||||
|
expectedErr: "catalog is nil",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil attributes",
|
||||||
|
req: &pb.OnCreateCatalogRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{},
|
||||||
|
},
|
||||||
|
expectedErr: "attributes are required",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "error reading attributes",
|
||||||
|
req: &pb.OnCreateCatalogRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: new(structpb.Struct),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: "attributes.project: missing required value \"project\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "do not persist secrets, use gcloud ADC",
|
||||||
|
req: &pb.OnCreateCatalogRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: hostCatalogAttributes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &pb.HostCatalogPersisted{
|
||||||
|
Secrets: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
actual, err := p.OnCreateCatalog(ctx, tc.req)
|
||||||
|
if tc.expectedErr != "" {
|
||||||
|
require.Contains(err.Error(), tc.expectedErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal(actual.GetPersisted().Secrets, tc.expected.GetSecrets())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateSet(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
p := &GooglePlugin{}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
req *pb.OnCreateSetRequest
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil set",
|
||||||
|
req: &pb.OnCreateSetRequest{},
|
||||||
|
expectedErr: "set is nil",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil set",
|
||||||
|
req: &pb.OnCreateSetRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
cred.ConstProject: structpb.NewStringValue("test-project"),
|
||||||
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: "set is nil",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "only allow instance group or filter, not both",
|
||||||
|
req: &pb.OnCreateSetRequest{
|
||||||
|
Set: &hostsets.HostSet{
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstListInstancesFilter: structpb.NewStringValue("status=RUNNING"),
|
||||||
|
ConstInstanceGroup: structpb.NewStringValue("test"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: "attributes: must set instance group or filter",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty filter",
|
||||||
|
req: &pb.OnCreateSetRequest{
|
||||||
|
Set: &hostsets.HostSet{
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstListInstancesFilter: structpb.NewStringValue(""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: "attributes.filter: must not be empty",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty instance group",
|
||||||
|
req: &pb.OnCreateSetRequest{
|
||||||
|
Set: &hostsets.HostSet{
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstInstanceGroup: structpb.NewStringValue(""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: "attributes.instance_group: must not be empty",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "good filter",
|
||||||
|
req: &pb.OnCreateSetRequest{
|
||||||
|
Set: &hostsets.HostSet{
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstInstanceGroup: structpb.NewStringValue("status=RUNNING"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "good instance group",
|
||||||
|
req: &pb.OnCreateSetRequest{
|
||||||
|
Set: &hostsets.HostSet{
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstInstanceGroup: structpb.NewStringValue("test"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
_, err := p.OnCreateSet(ctx, tc.req)
|
||||||
|
if tc.expectedErr != "" {
|
||||||
|
require.Contains(err.Error(), tc.expectedErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateSet(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
p := &GooglePlugin{}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
req *pb.OnUpdateSetRequest
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil set",
|
||||||
|
req: &pb.OnUpdateSetRequest{},
|
||||||
|
expectedErr: "set is nil",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil set",
|
||||||
|
req: &pb.OnUpdateSetRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
cred.ConstProject: structpb.NewStringValue("test-project"),
|
||||||
|
cred.ConstZone: structpb.NewStringValue("us-central1-a"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: "set is nil",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "only allow instance group or filter, not both",
|
||||||
|
req: &pb.OnUpdateSetRequest{
|
||||||
|
NewSet: &hostsets.HostSet{
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstListInstancesFilter: structpb.NewStringValue("status=RUNNING"),
|
||||||
|
ConstInstanceGroup: structpb.NewStringValue("test"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: "attributes: must set instance group or filter",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty filter",
|
||||||
|
req: &pb.OnUpdateSetRequest{
|
||||||
|
NewSet: &hostsets.HostSet{
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstListInstancesFilter: structpb.NewStringValue(""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: "attributes.filter: must not be empty",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty instance group",
|
||||||
|
req: &pb.OnUpdateSetRequest{
|
||||||
|
NewSet: &hostsets.HostSet{
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstInstanceGroup: structpb.NewStringValue(""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: "attributes.instance_group: must not be empty",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "good filter",
|
||||||
|
req: &pb.OnUpdateSetRequest{
|
||||||
|
NewSet: &hostsets.HostSet{
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstInstanceGroup: structpb.NewStringValue("status=RUNNING"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "good instance group",
|
||||||
|
req: &pb.OnUpdateSetRequest{
|
||||||
|
NewSet: &hostsets.HostSet{
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstInstanceGroup: structpb.NewStringValue("test"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
_, err := p.OnUpdateSet(ctx, tc.req)
|
||||||
|
if tc.expectedErr != "" {
|
||||||
|
require.Contains(err.Error(), tc.expectedErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# Copyright (c) HashiCorp, Inc.
|
||||||
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
data "google_compute_zones" "available" {}
|
||||||
|
|
||||||
|
resource "google_compute_instance" "default" {
|
||||||
|
count = var.num_instances
|
||||||
|
name = "boundary-${count.index}"
|
||||||
|
machine_type = "n2-standard-2"
|
||||||
|
zone = data.google_compute_zones.available.names[0]
|
||||||
|
|
||||||
|
labels = var.labels
|
||||||
|
|
||||||
|
boot_disk {
|
||||||
|
initialize_params {
|
||||||
|
image = "ubuntu-os-cloud/ubuntu-2204-lts"
|
||||||
|
labels = var.labels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
network_interface {
|
||||||
|
network = "default"
|
||||||
|
|
||||||
|
access_config {
|
||||||
|
// Ephemeral public IP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata = {
|
||||||
|
ssh-keys = "ubuntu:${tls_private_key.ssh.public_key_openssh}"
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic "service_account" {
|
||||||
|
for_each = var.service_account_email != null ? [var.service_account_email] : []
|
||||||
|
content {
|
||||||
|
email = service_account.value
|
||||||
|
scopes = ["cloud-platform"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_compute_instance_group" "webservers" {
|
||||||
|
name = "boundary-servers"
|
||||||
|
description = "Boundary server group"
|
||||||
|
|
||||||
|
instances = google_compute_instance.default.*.id
|
||||||
|
|
||||||
|
zone = data.google_compute_zones.available.names[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
# RSA key of size 4096 bits
|
||||||
|
resource "tls_private_key" "ssh" {
|
||||||
|
algorithm = "RSA"
|
||||||
|
rsa_bits = 4096
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_compute_firewall" "rules" {
|
||||||
|
name = "allow-ssh"
|
||||||
|
network = "default"
|
||||||
|
|
||||||
|
allow {
|
||||||
|
protocol = "tcp"
|
||||||
|
ports = ["22"]
|
||||||
|
}
|
||||||
|
source_ranges = [var.client_cidr_block]
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Copyright (c) HashiCorp, Inc.
|
||||||
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
resource "local_file" "ssh_key" {
|
||||||
|
content = tls_private_key.ssh.private_key_openssh
|
||||||
|
filename = "${path.module}/secrets/id_rsa.pem"
|
||||||
|
file_permission = "0400"
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Copyright (c) HashiCorp, Inc.
|
||||||
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
terraform {
|
||||||
|
required_providers {
|
||||||
|
google = {
|
||||||
|
source = "hashicorp/google"
|
||||||
|
version = "~> 5.22.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "google" {
|
||||||
|
region = var.region
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Copyright (c) HashiCorp, Inc.
|
||||||
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
variable "labels" {
|
||||||
|
type = map(string)
|
||||||
|
default = {
|
||||||
|
repository = "boundary-plugin-google"
|
||||||
|
purpose = "testing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "region" {
|
||||||
|
type = string
|
||||||
|
default = "us-central1"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "num_instances" {
|
||||||
|
type = number
|
||||||
|
default = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "service_account_email" {
|
||||||
|
type = string
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "client_cidr_block" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
Reference in new issue
Block a user