Compare commits

..
8 Commits
Author SHA1 Message Date
tf-release-bot 42cd1f8c4b v0.1.0 2020-10-14 15:39:12 +00:00
Paul Tyng 1c02ed9115 Vendor temporarily 2020-10-14 11:34:14 -04:00
Paul Tyng dd8e532d18 add fmtcheck target 2020-10-14 11:28:28 -04:00
Paul Tyng 505f302c96 make executable 2020-10-14 11:26:16 -04:00
Paul Tyng f9dc3e5abf Add file 2020-10-14 11:24:19 -04:00
Paul Tyng b58719b697 Remove file 2020-10-14 11:23:20 -04:00
Paul Tyng a3f0e90300 Create changelog-links.sh 2020-10-14 11:19:50 -04:00
Paul Tyng fb5b42171d Update Makefile 2020-10-14 11:17:57 -04:00
4795 changed files with 1228047 additions and 8026 deletions

No files matched your search

+3 -12
View File
@@ -1,25 +1,16 @@
version: 2.1
jobs:
build:
resource_class: large
machine:
image: 'ubuntu-2004:202107-02'
image: 'ubuntu-1604:201903-01'
working_directory: ~/terraform-provider-watchtower
steps:
- add_ssh_keys:
fingerprints:
# hc-github-team-secure-boundary github user,TF_ACC=1 ~/.go/bin/go test ./... -v $(TESTARGS) -timeout 120m to read private modules
- "d4:7f:b2:f7:6f:4d:e7:21:cc:d2:b7:6a:8b:68:e6:d4"
# hashicorp-ci github "machine" user, for private modules
- "c6:96:98:82:dc:04:6c:39:dd:ac:83:05:e3:15:1c:98"
- checkout
- run:
name: "Install go"
command: |
unset GOPATH # ci image has done strange things with GOPATH, unset it so install-go can fix it
make install-go
source ~/.bashrc
go mod download
- run:
name: "Run Acceptance Tests"
command: |
source ~/.bashrc
make testacc-ci
-17
View File
@@ -1,17 +0,0 @@
# See GitHub's docs for more information on this file:
# https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
# Maintain dependencies for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
# Check for updates to GitHub Actions every weekday
interval: "daily"
# Maintain dependencies for Go modules
- package-ecosystem: "gomod"
directory: "/"
schedule:
# Check for updates to Go modules every weekday
interval: "daily"
+6 -5
View File
@@ -48,13 +48,14 @@ jobs:
- name: Create ticket
if: github.event.action == 'opened' && !steps.boundary-team-role.outputs.role
uses: tomhjp/gh-action-jira-create@v0.2.0
uses: tomhjp/gh-action-jira-create@v0.1.0
with:
project: ICU
issuetype: "GH Issue"
summary: "${{ github.event.repository.name }} [${{ steps.set-ticket-type.outputs.type }} #${{ github.event.issue.number || github.event.pull_request.number }}]: ${{ github.event.issue.title || github.event.pull_request.title }}"
description: "${{ github.event.issue.body || github.event.pull_request.body }}\n\n_Created from GitHub Action for ${{ github.event.issue.html_url || github.event.pull_request.html_url }} from ${{ github.actor }}_"
description: "${{ github.event.issue.body || github.event.pull_request.body }}\n\n_Created from GitHub Action for ${{ github.event.issue.html_url || github.event.pull_request.html_url }}, from ${{ github.actor }}_"
# customfield_10089 is Issue Link custom field
# customfield_10091 is team custom field
extraFields: '{"customfield_10089": "${{ github.event.issue.html_url || github.event.pull_request.html_url }}"}'
- name: Search
@@ -67,21 +68,21 @@ jobs:
- name: Sync comment
if: github.event.action == 'created' && steps.search.outputs.issue
uses: tomhjp/gh-action-jira-comment@v0.2.0
uses: atlassian/gajira-comment@v2.0.1
with:
issue: ${{ steps.search.outputs.issue }}
comment: "${{ github.actor }} ${{ github.event.review.state || 'commented' }}:\n\n${{ github.event.comment.body || github.event.review.body }}\n\n${{ github.event.comment.html_url || github.event.review.html_url }}"
- name: Close ticket
if: (github.event.action == 'closed' || github.event.action == 'deleted') && steps.search.outputs.issue
uses: atlassian/gajira-transition@v2.0.2
uses: atlassian/gajira-transition@v2.0.1
with:
issue: ${{ steps.search.outputs.issue }}
transition: Done
- name: Reopen ticket
if: github.event.action == 'reopened' && steps.search.outputs.issue
uses: atlassian/gajira-transition@v2.0.2
uses: atlassian/gajira-transition@v2.0.1
with:
issue: ${{ steps.search.outputs.issue }}
transition: "To Do"
+1 -1
View File
@@ -1 +1 @@
1.17.5
1.15.2
-89
View File
@@ -1,92 +1,3 @@
## 1.0.6 (January 21, 2022)
### New and Improved
* Adds dynamic host plugin catalog/set
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/159)).
* Adds support for insecure TLS to Boundary
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/163)).
### Deprecations/Changes
* Removes fields `host_set_ids` and `application_credential_library_ids` of the
`target` resource, which were deprecated in 1.0.5
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/150)).
## 1.0.5 (September 09, 2021)
### Deprecations/Changes
* Deprecate fields `host_set_ids` and `application_credential_library_ids` of the
`target` resource. See boundary 0.5.0 [changelog](https://github.com/hashicorp/boundary/blob/main/CHANGELOG.md#deprecationschanges) for more detail on the deprecation.
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/134)).
## 1.0.4 (August 19, 2021)
### New and Improved
* Adds managed groups resource
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/118)).
## 1.0.3 (June 30, 2021)
### New and Improved
* Adds credential library resource for Vault
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/114)).
* Adds credential store resource for Vault
([PR 1](https://github.com/hashicorp/terraform-provider-boundary/pull/114)),
([PR 2](https://github.com/hashicorp/terraform-provider-boundary/pull/125)).
* Adds claim scopes attribute to OIDC auth method
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/112)).
* Adds account claim maps attribute to OIDC auth method
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/111)).
### Bug Fixes
* Make OIDC account attribute for subject ForceNew
([Issue](https://github.com/hashicorp/terraform-provider-boundary/issues/119)),
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/122)).
* Update static type attribute for host catalog resource
([Issue](https://github.com/hashicorp/terraform-provider-boundary/issues/115)),
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/121)).
## 1.0.2 (May 06, 2021)
### New and Improved
* Adds OIDC account resource
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/105)).
* Adds OIDC auth method resource
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/105)).
### Deprecations/Changes
* Deprecates fields on `resource_auth_method` that will be replaced in the future with generic `attributes` attribute.
## 1.0.1 (February 02, 2021)
### New and Improved
* Adds worker filter to target resource
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/76)).
## 1.0.0 (January 20, 2021)
We are bumping the version of the Boundary Terraform provider to v1.0.0 and will release new versions of the provider at its own cadence instead of keeping it in lockstep with Boundary.
### Bug Fixes
* During `terraform apply`, do not update existing user account passwords when the password field is updated in the tf file.
([Issue](https://github.com/hashicorp/terraform-provider-boundary/issues/71)),
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/70)).
## 0.1.4 (January 14, 2021)
### New and Improved
Update provider to handle new domain errors ([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/63)).
## 0.1.0 (October 14, 2020)
Initial release!
-6
View File
@@ -1,6 +0,0 @@
# Contributing to Boundary
Thank you for contributing to Boundary! Here you can find common questions around reporting issues and opening
pull requests to our project.
When contributing in any way to the Boundary project (new issue, PR, etc), please be aware that our team identifies with many gender pronouns and to use non-binary pronouns when addressing our team. For more reading on our code of conduct, please see the [HashiCorp community guidelines](https://www.hashicorp.com/community-guidelines).
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.17
FROM golang:1.15
WORKDIR /go/src/terraform-provider-boundary
COPY . .
+5 -45
View File
@@ -1,4 +1,4 @@
default: testacc
default: testacc
GOOS=$(shell go env GOOS)
GOARCH=$(shell go env GOARCH)
INSTALL_PATH=~/.local/share/terraform/plugins/localhost/providers/boundary/0.0.1/linux_$(GOARCH)
@@ -11,16 +11,6 @@ ifeq ($(GOOS), "windows")
INSTALL_PATH=%APPDATA%/HashiCorp/Terraform/plugins/localhost/providers/boundary/0.0.1/windows_$(GOARCH)
endif
REGISTRY_NAME?=docker.io/hashicorpboundary
IMAGE_NAME=postgres
IMAGE_TAG ?= $(REGISTRY_NAME)/$(IMAGE_NAME):11-alpine
DOCKER_ARGS ?= -d
PG_OPTS ?=
TEST_DB_PORT ?= 5432
BOUNDARY_VERSION = $(shell go mod edit -json | jq -r '.["Require"][] | select(.Path=="github.com/hashicorp/boundary") | .["Version"]')
GOPATH ?= ~/go
GOMODCACHE ?= $(GOPATH)/pkg/mod
tools:
go generate -tags tools tools/tools.go
@@ -34,17 +24,15 @@ test:
testacc:
TF_ACC=1 go test ./... -v $(TESTARGS) -timeout 120m
testacc-ci:
testacc-ci: install-go
git config --global --add url."git@github.com:".insteadOf "https://github.com/"
TF_ACC=1 ~/.go/bin/go test ./... -v $(TESTARGS) -timeout 120m
install-go: ~/.go/bin/go
~/.go/bin/go:
install-go:
./ci/goinstall.sh
dev:
mkdir -p $(INSTALL_PATH)
mkdir -p $(INSTALL_PATH)
go build -o $(INSTALL_PATH)/terraform-provider-boundary main.go
all:
@@ -53,35 +41,7 @@ all:
GOOS=windows go build -o $(BUILD_ALL_PATH)/terraform-provider-boundary_windows-amd64 main.go
GOOS=linux go build -o $(BUILD_ALL_PATH)/terraform-provider-boundary_linux-amd64 main.go
docs:
go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs
rm-id-flag-from-docs:
find docs/ -name "*.md" -type f | xargs sed -i -e '/- \*\*id\*\*/d'
test-database-up:
@echo "Using image: $(IMAGE_TAG)"
@echo "Additional postgres configuration: $(PG_OPTS)"
@echo "Using volume: $(GOMODCACHE)/github.com/hashicorp/boundary@$(BOUNDARY_VERSION)/internal/db/schema/migrations:/migrations"
@docker run \
$(DOCKER_ARGS) \
--name boundary-sql-tests \
-p $(TEST_DB_PORT):5432 \
-e POSTGRES_PASSWORD=boundary \
-e POSTGRES_USER=boundary \
-e POSTGRES_DB=boundary \
-e PGDATA=/pgdata \
--mount type=tmpfs,destination=/pgdata \
-v "$(GOMODCACHE)/github.com/hashicorp/boundary@$(BOUNDARY_VERSION)/internal/db/schema/migrations":/migrations \
$(IMAGE_TAG) \
-c 'config_file=/etc/postgresql/postgresql.conf' \
$(PG_OPTS) 1> /dev/null
@echo "Test database available at: 127.0.0.1:$(TEST_DB_PORT)"
@echo "For database logs run:"
@echo " docker logs boundary-sql-tests"
test-database-down:
docker stop boundary-sql-tests || true
docker rm -v boundary-sql-tests || true
.PHONY: testacc tools docs test-database-up test-database-down
.PHONY: testacc tools
+3 -16
View File
@@ -3,13 +3,11 @@
Terraform Provider Boundary
==================
Available in the [Terraform Registry](https://registry.terraform.io/providers/hashicorp/boundary/latest).
Requirements
------------
- [Terraform](https://www.terraform.io/downloads.html) >= 0.12.x
- [Go](https://golang.org/doc/install) >= 1.17
- [Go](https://golang.org/doc/install) >= 1.15
Building The Provider
---------------------
@@ -53,15 +51,7 @@ If you wish to work on the provider, you'll first need [Go](http://www.golang.or
To compile the provider, run `go install`. This will build the provider and put the provider binary in the `$GOPATH/bin` directory.
In order to run the full suite of Acceptance tests,
a postgres docker container must be started first:
```sh
$ go mod download # ensure boundary is installed, files are used by the docker image
$ make test-database-up
```
Once the test database is ready the tests can be run using `make testacc`.
In order to run the full suite of Acceptance tests, run `make testacc`.
*Note:* Acceptance tests create real resources, and often cost money to run.
@@ -69,9 +59,6 @@ Once the test database is ready the tests can be run using `make testacc`.
$ make testacc
```
For more details on the docker image and troubleshooting see the
[boundary testing doc](https://github.com/hashicorp/boundary/blob/main/CONTRIBUTING.md#testing).
Generating Docs
----------------------
@@ -140,7 +127,7 @@ resource "boundary_user" "users" {
scope_id = boundary_scope.corp.id
}
// organization level group for readonly users
// organiation level group for readonly users
resource "boundary_group" "readonly" {
name = "readonly"
description = "Organization group for readonly users"
+1 -1
View File
@@ -1,7 +1,7 @@
#!/bin/bash
set -e
VERSION="1.17.5"
VERSION="1.15"
[ -z "$GOROOT" ] && GOROOT="$HOME/.go"
[ -z "$GOPATH" ] && GOPATH="$HOME/go"
+6 -8
View File
@@ -22,18 +22,16 @@ provider "boundary" {
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **addr** (String) The base url of the Boundary API, e.g. "http://127.0.0.1:9200". If not set, it will be read from the "BOUNDARY_ADDR" env var.
- **addr** (String, Required) The base url of the Boundary API, e.g. "http://127.0.0.1". If not set, it will be read from the "BOUNDARY_ADDR" env var.
### Optional
- **auth_method_id** (String) The auth method ID e.g. ampw_1234567890
- **password_auth_method_login_name** (String) The auth method login name for password-style auth methods
- **password_auth_method_password** (String) The auth method password for password-style auth methods
- **recovery_kms_hcl** (String) Can be a heredoc string or a path on disk. If set, the string/file will be parsed as HCL and used with the recovery KMS mechanism. While this is set, it will override any other authentication information; the KMS mechanism will always be used. See Boundary's KMS docs for examples: https://boundaryproject.io/docs/configuration/kms
- **tls_insecure** (Boolean) When set to true, does not validate the Boundary API endpoint certificate
- **token** (String) The Boundary token to use, as a string or path on disk containing just the string. If set, the token read here will be used in place of authenticating with the auth method specified in "auth_method_id", although the recovery KMS mechanism will still override this. Can also be set with the BOUNDARY_TOKEN environment variable.
- **auth_method_id** (String, Optional) The auth method ID e.g. ampw_1234567890
- **password_auth_method_login_name** (String, Optional) The auth method login name for password-style auth methods
- **password_auth_method_password** (String, Optional) The auth method password for password-style auth methods
- **recovery_kms_hcl** (String, Optional) Can be a heredoc string or a path on disk. If set, the string/file will be parsed as HCL and used with the recovery KMS mechanism. While this is set, it will override any other authentication information; the KMS mechanism will always be used. See Boundary's KMS docs for examples: https://boundaryproject.io/docs/configuration/kms
- **token** (String, Optional) The Boundary token to use, as a string or path on disk containing just the string. If set, the token read here will be used in place of authenticating with the auth method specified in "auth_method_id", although the recovery KMS mechanism will still override this. Can also be set with the BOUNDARY_TOKEN environment variable.
+7 -13
View File
@@ -1,12 +1,11 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "boundary_account Resource - terraform-provider-boundary"
subcategory: ""
description: |-
The account resource allows you to configure a Boundary account.
---
# boundary_account (Resource)
# Resource `boundary_account`
The account resource allows you to configure a Boundary account.
@@ -34,24 +33,19 @@ resource "boundary_account" "jeff" {
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **auth_method_id** (String) The resource ID for the auth method.
- **type** (String) The resource type.
- **auth_method_id** (String, Required) The resource ID for the auth method.
- **type** (String, Required) The resource type.
### Optional
- **description** (String) The account description.
- **login_name** (String, Deprecated) The login name for this account.
- **name** (String) The account name. Defaults to the resource name.
- **password** (String, Deprecated) The account password. Only set on create, changes will not be reflected when updating account.
### Read-Only
- **id** (String) The ID of the account.
- **description** (String, Optional) The account description.
- **login_name** (String, Optional) The login name for this account.
- **name** (String, Optional) The account name. Defaults to the resource name.
- **password** (String, Optional) The account password.
## Import
-33
View File
@@ -1,33 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "boundary_account_oidc Resource - terraform-provider-boundary"
subcategory: ""
description: |-
The account resource allows you to configure a Boundary account.
---
# boundary_account_oidc (Resource)
The account resource allows you to configure a Boundary account.
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **auth_method_id** (String) The resource ID for the auth method.
### Optional
- **description** (String) The account description.
- **issuer** (String) The OIDC issuer.
- **name** (String) The account name. Defaults to the resource name.
- **subject** (String) The OIDC subject.
### Read-Only
- **id** (String) The ID of the account.
-34
View File
@@ -1,34 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "boundary_account_password Resource - terraform-provider-boundary"
subcategory: ""
description: |-
The account resource allows you to configure a Boundary account.
---
# boundary_account_password (Resource)
The account resource allows you to configure a Boundary account.
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **auth_method_id** (String) The resource ID for the auth method.
- **type** (String) The resource type.
### Optional
- **description** (String) The account description.
- **login_name** (String, Deprecated) The login name for this account.
- **name** (String) The account name. Defaults to the resource name.
- **password** (String, Deprecated) The account password. Only set on create, changes will not be reflected when updating account.
### Read-Only
- **id** (String) The ID of the account.
+7 -13
View File
@@ -1,12 +1,11 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "boundary_auth_method Resource - terraform-provider-boundary"
subcategory: ""
description: |-
The auth method resource allows you to configure a Boundary auth_method.
---
# boundary_auth_method (Resource)
# Resource `boundary_auth_method`
The auth method resource allows you to configure a Boundary auth_method.
@@ -27,24 +26,19 @@ resource "boundary_auth_method" "password" {
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **scope_id** (String) The scope ID.
- **type** (String) The resource type.
- **scope_id** (String, Required) The scope ID.
- **type** (String, Required) The resource type.
### Optional
- **description** (String) The auth method description.
- **min_login_name_length** (Number, Deprecated) The minimum login name length.
- **min_password_length** (Number, Deprecated) The minimum password length.
- **name** (String) The auth method name. Defaults to the resource name.
### Read-Only
- **id** (String) The ID of the account.
- **description** (String, Optional) The auth method description.
- **min_login_name_length** (Number, Optional) The minimum login name length.
- **min_password_length** (Number, Optional) The minimum password length.
- **name** (String, Optional) The auth method name. Defaults to the resource name.
## Import
-47
View File
@@ -1,47 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "boundary_auth_method_oidc Resource - terraform-provider-boundary"
subcategory: ""
description: |-
The OIDC auth method resource allows you to configure a Boundary authmethodoidc.
---
# boundary_auth_method_oidc (Resource)
The OIDC auth method resource allows you to configure a Boundary auth_method_oidc.
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **scope_id** (String) The scope ID.
### Optional
- **account_claim_maps** (List of String) Account claim maps for the to_claim of sub.
- **allowed_audiences** (List of String) Audiences for which the provider responses will be allowed
- **api_url_prefix** (String) The API prefix to use when generating callback URLs for the provider. Should be set to an address at which the provider can reach back to the controller.
- **callback_url** (String) The URL that should be provided to the IdP for callbacks.
- **claims_scopes** (List of String) Claims scopes.
- **client_id** (String) The client ID assigned to this auth method from the provider.
- **client_secret** (String) The secret key assigned to this auth method from the provider. Once set, only the hash will be kept and the original value can be removed from configuration.
- **client_secret_hmac** (String) The HMAC of the client secret returned by the Boundary controller, which is used for comparison after initial setting of the value.
- **description** (String) The auth method description.
- **disable_discovered_config_validation** (Boolean) Disables validation logic ensuring that the OIDC provider's information from its discovery endpoint matches the information here. The validation is only performed at create or update time.
- **idp_ca_certs** (List of String) A list of CA certificates to trust when validating the IdP's token signatures.
- **is_primary_for_scope** (Boolean) When true, makes this auth method the primary auth method for the scope in which it resides. The primary auth method for a scope means the the user will be automatically created when they login using an OIDC account.
- **issuer** (String) The issuer corresponding to the provider, which must match the issuer field in generated tokens.
- **max_age** (Number) The max age to provide to the provider, indicating how much time is allowed to have passed since the last authentication before the user is challenged again.
- **name** (String) The auth method name. Defaults to the resource name.
- **signing_algorithms** (List of String) Allowed signing algorithms for the provider's issued tokens.
- **state** (String) Can be one of 'inactive', 'active-private', or 'active-public'. Currently automatically set to active-public.
- **type** (String) The type of auth method; hardcoded.
### Read-Only
- **id** (String) The ID of the auth method.
-34
View File
@@ -1,34 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "boundary_auth_method_password Resource - terraform-provider-boundary"
subcategory: ""
description: |-
The auth method resource allows you to configure a Boundary authmethodpassword.
---
# boundary_auth_method_password (Resource)
The auth method resource allows you to configure a Boundary auth_method_password.
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **scope_id** (String) The scope ID.
### Optional
- **description** (String) The auth method description.
- **min_login_name_length** (Number) The minimum login name length.
- **min_password_length** (Number) The minimum password length.
- **name** (String) The auth method name. Defaults to the resource name.
- **type** (String) The resource type, hardcoded per resource
### Read-Only
- **id** (String) The ID of the account.
@@ -1,86 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "boundary_credential_library_vault Resource - terraform-provider-boundary"
subcategory: ""
description: |-
The credential library for Vault resource allows you to configure a Boundary credential library for Vault.
---
# boundary_credential_library_vault (Resource)
The credential library for Vault resource allows you to configure a Boundary credential library for Vault.
## Example Usage
```terraform
resource "boundary_scope" "org" {
name = "organization_one"
description = "My first scope!"
scope_id = "global"
auto_create_admin_role = true
auto_create_default_role = true
}
resource "boundary_scope" "project" {
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
}
resource "boundary_credential_store_vault" "foo" {
name = "foo"
description = "My first Vault credential store!"
address = "http://127.0.0.1:8200" # change to Vault address
token = "s.0ufRo6XEGU2jOqnIr7OlFYP5" # change to valid Vault token
scope_id = boundary_scope.project.id
}
resource "boundary_credential_library_vault" "foo" {
name = "foo"
description = "My first Vault credential library!"
credential_store_id = boundary_credential_store_vault.foo.id
path = "my/secret/foo" # change to Vault backend path
http_method = "GET"
}
resource "boundary_credential_library_vault" "bar" {
name = "bar"
description = "My second Vault credential library!"
credential_store_id = boundary_credential_store_vault.foo.id
path = "my/secret/bar" # change to Vault backend path
http_method = "POST"
http_request_body = <<EOT
{
"key": "Value",
}
EOT
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **credential_store_id** (String) The ID of the credential store that this library belongs to.
- **path** (String) The path in Vault to request credentials from.
### Optional
- **description** (String) The Vault credential library description.
- **http_method** (String) The HTTP method the library uses when requesting credentials from Vault. Defaults to 'GET'
- **http_request_body** (String) The body of the HTTP request the library sends to Vault when requesting credentials. Only valid if `http_method` is set to `POST`.
- **name** (String) The Vault credential library name. Defaults to the resource name.
### Read-Only
- **id** (String) The ID of the Vault credential library.
## Import
Import is supported using the following syntax:
```shell
terraform import boundary_credential_library_vault.foo <my-id>
```
-72
View File
@@ -1,72 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "boundary_credential_store_vault Resource - terraform-provider-boundary"
subcategory: ""
description: |-
The credential store for Vault resource allows you to configure a Boundary credential store for Vault.
---
# boundary_credential_store_vault (Resource)
The credential store for Vault resource allows you to configure a Boundary credential store for Vault.
## Example Usage
```terraform
resource "boundary_scope" "org" {
name = "organization_one"
description = "My first scope!"
scope_id = "global"
auto_create_admin_role = true
auto_create_default_role = true
}
resource "boundary_scope" "project" {
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
}
resource "boundary_credential_store_vault" "example" {
name = "foo"
description = "My first Vault credential store!"
address = "http://127.0.0.1:8200" # change to Vault address
token = "s.0ufRo6XEGU2jOqnIr7OlFYP5" # change to valid Vault token
scope_id = boundary_scope.project.id
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **address** (String) The address to Vault server. This should be a complete URL such as 'https://127.0.0.1:8200'
- **scope_id** (String) The scope for this credential store.
- **token** (String, Sensitive) A token used for accessing Vault.
### Optional
- **ca_cert** (String) A PEM-encoded CA certificate to verify the Vault server's TLS certificate.
- **client_certificate** (String) A PEM-encoded client certificate to use for TLS authentication to the Vault server.
- **client_certificate_key** (String, Sensitive) A PEM-encoded private key matching the client certificate from 'client_certificate'.
- **description** (String) The Vault credential store description.
- **name** (String) The Vault credential store name. Defaults to the resource name.
- **namespace** (String) The namespace within Vault to use.
- **tls_server_name** (String) Name to use as the SNI host when connecting to Vault via TLS.
- **tls_skip_verify** (Boolean) Whether or not to skip TLS verification.
### Read-Only
- **client_certificate_key_hmac** (String) The Vault client certificate key hmac.
- **id** (String) The ID of the Vault credential store.
- **token_hmac** (String) The Vault token hmac.
## Import
Import is supported using the following syntax:
```shell
terraform import boundary_credential_store_vault.foo <my-id>
```
+8 -13
View File
@@ -45,10 +45,10 @@ resource "boundary_scope" "org" {
}
resource "boundary_scope" "project" {
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
}
resource "boundary_user" "foo" {
@@ -64,22 +64,17 @@ resource "boundary_group" "example" {
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **scope_id** (String) The scope ID in which the resource is created. Defaults to the provider's `default_scope` if unset.
- **scope_id** (String, Required) The scope ID in which the resource is created. Defaults to the provider's `default_scope` if unset.
### Optional
- **description** (String) The group description.
- **member_ids** (Set of String) Resource IDs for group members, these are most likely boundary users.
- **name** (String) The group name. Defaults to the resource name.
### Read-Only
- **id** (String) The ID of the group.
- **description** (String, Optional) The group description.
- **member_ids** (Set of String, Optional) Resource IDs for group members, these are most likely boundary users.
- **name** (String, Optional) The group name. Defaults to the resource name.
## Import
+16 -29
View File
@@ -1,14 +1,13 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "boundary_host Resource - terraform-provider-boundary"
subcategory: ""
description: |-
Deprecated: use resource_host_static instead.
The host resource allows you to configure a Boundary static host. Hosts are always part of a project, so a project resource should be used inline or you should have the project ID in hand to successfully configure a host.
---
# boundary_host (Resource)
# Resource `boundary_host`
Deprecated: use `resource_host_static` instead.
The host resource allows you to configure a Boundary static host. Hosts are always part of a project, so a project resource should be used inline or you should have the project ID in hand to successfully configure a host.
## Example Usage
@@ -22,44 +21,32 @@ resource "boundary_scope" "org" {
}
resource "boundary_scope" "project" {
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
}
resource "boundary_host_catalog" "static" {
name = "My catalog"
description = "My first host catalog!"
type = "static"
scope_id = boundary_scope.project.id
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
}
resource "boundary_host" "example" {
name = "example_host"
description = "My first host!"
address = "10.0.0.1"
host_catalog_id = boundary_host_catalog.static.id
name = "example_host"
description = "My first host!"
address = "10.0.0.1"
scope_id = boundary_scope.project.id
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **host_catalog_id** (String)
- **type** (String) The type of host
- **host_catalog_id** (String, Required)
- **type** (String, Required)
### Optional
- **address** (String) The static address of the host resource as `<IP>` (note: port assignment occurs in the target resource definition, do not add :port here) or a domain name.
- **description** (String) The host description.
- **name** (String) The host name. Defaults to the resource name.
### Read-Only
- **id** (String) The ID of the host.
- **address** (String, Optional) The static address of the host resource as `<IP>` (note: port assignment occurs in the target resource definition, do not add :port here) or a domain name.
- **description** (String, Optional) The host description.
- **name** (String, Optional) The host name. Defaults to the resource name.
## Import
+11 -17
View File
@@ -1,14 +1,13 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "boundary_host_catalog Resource - terraform-provider-boundary"
subcategory: ""
description: |-
Deprecated: use resource_host_catalog_static instead.
The host catalog resource allows you to configure a Boundary host catalog. Host catalogs are always part of a project, so a project resource should be used inline or you should have the project ID in hand to successfully configure a host catalog.
---
# boundary_host_catalog (Resource)
# Resource `boundary_host_catalog`
Deprecated: use `resource_host_catalog_static` instead.
The host catalog resource allows you to configure a Boundary host catalog. Host catalogs are always part of a project, so a project resource should be used inline or you should have the project ID in hand to successfully configure a host catalog.
## Example Usage
@@ -22,10 +21,10 @@ resource "boundary_scope" "org" {
}
resource "boundary_scope" "project" {
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
}
resource "boundary_host_catalog" "example" {
@@ -36,22 +35,17 @@ resource "boundary_host_catalog" "example" {
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **scope_id** (String) The scope ID in which the resource is created.
- **type** (String) The host catalog type. Only `static` is supported.
- **scope_id** (String, Required) The scope ID in which the resource is created.
- **type** (String, Required) The host catalog type. Only `Static` (yes, title case) is supported.
### Optional
- **description** (String) The host catalog description.
- **name** (String) The host catalog name. Defaults to the resource name.
### Read-Only
- **id** (String) The ID of the host catalog.
- **description** (String, Optional) The host catalog description.
- **name** (String, Optional) The host catalog name. Defaults to the resource name.
## Import
-39
View File
@@ -1,39 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "boundary_host_catalog_plugin Resource - terraform-provider-boundary"
subcategory: ""
description: |-
The host catalog resource allows you to configure a Boundary plugin-type host catalog. Host catalogs are always part of a project, so a project resource should be used inline or you should have the project ID in hand to successfully configure a host catalog.
---
# boundary_host_catalog_plugin (Resource)
The host catalog resource allows you to configure a Boundary plugin-type host catalog. Host catalogs are always part of a project, so a project resource should be used inline or you should have the project ID in hand to successfully configure a host catalog.
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **scope_id** (String) The scope ID in which the resource is created.
### Optional
- **attributes_json** (String) The attributes for the host catalog. Either values encoded with the "jsonencode" function, pre-escaped JSON string, or a file:// or env:// path. Set to a string "null" or remove the block to clear all attributes in the host catalog.
- **description** (String) The host catalog description.
- **internal_force_update** (String) Internal only. Used to force update so that we can always check the value of secrets.
- **internal_hmac_used_for_secrets_config_hmac** (String) Internal only. The Boundary-provided HMAC used to calculate the current value of the HMAC'd config. Used for drift detection.
- **internal_secrets_config_hmac** (String) Internal only. HMAC of (serverSecretsHmac + config secrets). Used for proper secrets handling.
- **name** (String) The host catalog name. Defaults to the resource name.
- **plugin_id** (String) The ID of the plugin that should back the resource. This or plugin_name must be defined.
- **plugin_name** (String) The name of the plugin that should back the resource. This or plugin_id must be defined.
- **secrets_hmac** (String) The HMAC'd secrets value returned from the server.
- **secrets_json** (String, Sensitive) The secrets for the host catalog. Either values encoded with the "jsonencode" function, pre-escaped JSON string, or a file:// or env:// path. Set to a string "null" to clear any existing values. NOTE: Unlike "attributes_json", removing this block will NOT clear secrets from the host catalog; this allows injecting secrets for one call, then removing them for storage.
### Read-Only
- **id** (String) The ID of the host catalog.
-31
View File
@@ -1,31 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "boundary_host_catalog_static Resource - terraform-provider-boundary"
subcategory: ""
description: |-
The static host catalog resource allows you to configure a Boundary static-type host catalog. Host catalogs are always part of a project, so a project resource should be used inline or you should have the project ID in hand to successfully configure a host catalog.
---
# boundary_host_catalog_static (Resource)
The static host catalog resource allows you to configure a Boundary static-type host catalog. Host catalogs are always part of a project, so a project resource should be used inline or you should have the project ID in hand to successfully configure a host catalog.
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **scope_id** (String) The scope ID in which the resource is created.
### Optional
- **description** (String) The host catalog description.
- **name** (String) The host catalog name. Defaults to the resource name.
### Read-Only
- **id** (String) The ID of the host catalog.
+12 -19
View File
@@ -1,14 +1,13 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "boundary_host_set Resource - terraform-provider-boundary"
subcategory: ""
description: |-
Deprecated: use resource_host_set_static instead.
The host_set resource allows you to configure a Boundary host set. Host sets are always part of a host catalog, so a host catalog resource should be used inline or you should have the host catalog ID in hand to successfully configure a host set.
---
# boundary_host_set (Resource)
# Resource `boundary_host_set`
Deprecated: use `resource_host_set_static` instead.
The host_set resource allows you to configure a Boundary host set. Host sets are always part of a host catalog, so a host catalog resource should be used inline or you should have the host catalog ID in hand to successfully configure a host set.
## Example Usage
@@ -22,10 +21,10 @@ resource "boundary_scope" "org" {
}
resource "boundary_scope" "project" {
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
}
resource "boundary_host_catalog" "static" {
@@ -50,7 +49,6 @@ resource "boundary_host" "2" {
resource "boundary_host_set" "web" {
host_catalog_id = boundary_host_catalog.static.id
type = "static"
host_ids = [
boundary_host.1.id,
boundary_host.2.id,
@@ -58,23 +56,18 @@ resource "boundary_host_set" "web" {
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **host_catalog_id** (String) The catalog for the host set.
- **type** (String) The type of host set
- **host_catalog_id** (String, Required) The catalog for the hostset.
- **type** (String, Required)
### Optional
- **description** (String) The host set description.
- **host_ids** (Set of String) The list of host IDs contained in this set.
- **name** (String) The host set name. Defaults to the resource name.
### Read-Only
- **id** (String) The ID of the host set.
- **description** (String, Optional) The hostset description.
- **host_ids** (Set of String, Optional) The list of host IDs contained in this set.
- **name** (String, Optional) The hostset name. Defaults to the resource name.
## Import
-35
View File
@@ -1,35 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "boundary_host_set_plugin Resource - terraform-provider-boundary"
subcategory: ""
description: |-
The hostsetplugin resource allows you to configure a Boundary host set. Host sets are always part of a host catalog, so a host catalog resource should be used inline or you should have the host catalog ID in hand to successfully configure a host set.
---
# boundary_host_set_plugin (Resource)
The host_set_plugin resource allows you to configure a Boundary host set. Host sets are always part of a host catalog, so a host catalog resource should be used inline or you should have the host catalog ID in hand to successfully configure a host set.
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **host_catalog_id** (String) The catalog for the host set.
### Optional
- **attributes_json** (String) The attributes for the host set. Either values encoded with the "jsonencode" function, pre-escaped JSON string, or a file:// or env:// path. Set to a string "null" or remove the block to clear all attributes in the host set.
- **description** (String) The host set description.
- **name** (String) The host set name. Defaults to the resource name.
- **preferred_endpoints** (List of String) The ordered list of preferred endpoints.
- **sync_interval_seconds** (Number) The value to set for the sync interval seconds.
- **type** (String) The type of host set
### Read-Only
- **id** (String) The ID of the host set.
-33
View File
@@ -1,33 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "boundary_host_set_static Resource - terraform-provider-boundary"
subcategory: ""
description: |-
The hostsetstatic resource allows you to configure a Boundary host set. Host sets are always part of a host catalog, so a host catalog resource should be used inline or you should have the host catalog ID in hand to successfully configure a host set.
---
# boundary_host_set_static (Resource)
The host_set_static resource allows you to configure a Boundary host set. Host sets are always part of a host catalog, so a host catalog resource should be used inline or you should have the host catalog ID in hand to successfully configure a host set.
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **host_catalog_id** (String) The catalog for the host set.
### Optional
- **description** (String) The host set description.
- **host_ids** (Set of String) The list of host IDs contained in this set.
- **name** (String) The host set name. Defaults to the resource name.
- **type** (String) The type of host set
### Read-Only
- **id** (String) The ID of the host set.
-33
View File
@@ -1,33 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "boundary_host_static Resource - terraform-provider-boundary"
subcategory: ""
description: |-
The static host resource allows you to configure a Boundary static host. Hosts are always part of a project, so a project resource should be used inline or you should have the project ID in hand to successfully configure a host.
---
# boundary_host_static (Resource)
The static host resource allows you to configure a Boundary static host. Hosts are always part of a project, so a project resource should be used inline or you should have the project ID in hand to successfully configure a host.
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **host_catalog_id** (String)
### Optional
- **address** (String) The static address of the host resource as `<IP>` (note: port assignment occurs in the target resource definition, do not add :port here) or a domain name.
- **description** (String) The host description.
- **name** (String) The host name. Defaults to the resource name.
- **type** (String) The type of host
### Read-Only
- **id** (String) The ID of the host.
-32
View File
@@ -1,32 +0,0 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "boundary_managed_group Resource - terraform-provider-boundary"
subcategory: ""
description: |-
The managed group resource allows you to configure a Boundary group.
---
# boundary_managed_group (Resource)
The managed group resource allows you to configure a Boundary group.
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **auth_method_id** (String) The resource ID for the auth method.
- **filter** (String) Boolean expression to filter the workers for this managed group.
### Optional
- **description** (String) The managed group description.
- **name** (String) The managed group name. Defaults to the resource name.
### Read-Only
- **id** (String) The ID of the group.
+25 -30
View File
@@ -51,10 +51,10 @@ resource "boundary_user" "bar" {
}
resource "boundary_role" "example" {
name = "My role"
description = "My first role!"
principal_ids = [boundary_user.foo.id, boundary_user.bar.id]
scope_id = boundary_scope.org.id
name = "My role"
description = "My first role!"
principals = [boundary_user.foo.id, boundary_user.bar.id]
scope_id = boundary_scope.org.id
}
```
@@ -76,11 +76,11 @@ resource "boundary_user" "readonly" {
}
resource "boundary_role" "readonly" {
name = "readonly"
description = "A readonly role"
principal_ids = [boundary_user.readonly.id]
grant_strings = ["id=*;type=*;actions=read"]
scope_id = boundary_scope.org.id
name = "readonly"
description = "A readonly role"
principals = [boundary_user.readonly.id]
grants = ["id=*;action=read"]
scope_id = boundary_scope.org.id
}
```
@@ -96,10 +96,10 @@ resource "boundary_scope" "org" {
}
resource "boundary_scope" "project" {
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
}
resource "boundary_user" "readonly" {
@@ -109,33 +109,28 @@ resource "boundary_user" "readonly" {
}
resource "boundary_role" "readonly" {
name = "readonly"
description = "A readonly role"
principal_ids = [boundary_user.readonly.id]
grant_strings = ["id=*;type=*;actions=read"]
scope_id = boundary_scope.project.id
name = "readonly"
description = "A readonly role"
principals = [boundary_user.readonly.id]
grants = ["id=*;action=read"]
scope_id = boundary_scope.project.id
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **scope_id** (String) The scope ID in which the resource is created. Defaults to the provider's `default_scope` if unset.
- **scope_id** (String, Required) The scope ID in which the resource is created. Defaults to the provider's `default_scope` if unset.
### Optional
- **default_role** (Boolean) Indicates that the role containing this value is the default role (that is, has the id 'r_default'), which triggers some specialized behavior to allow it to be imported and managed.
- **description** (String) The role description.
- **grant_scope_id** (String)
- **grant_strings** (Set of String) A list of stringified grants for the role.
- **name** (String) The role name. Defaults to the resource name.
- **principal_ids** (Set of String) A list of principal (user or group) IDs to add as principals on the role.
### Read-Only
- **id** (String) The ID of the role.
- **default_role** (Boolean, Optional) Indicates that the role containing this value is the default role (that is, has the id 'r_default'), which triggers some specialized behavior to allow it to be imported and managed.
- **description** (String, Optional) The role description.
- **grant_scope_id** (String, Optional)
- **grant_strings** (Set of String, Optional) A list of stringified grants for the role.
- **name** (String, Optional) The role name. Defaults to the resource name.
- **principal_ids** (Set of String, Optional) A list of principal (user or group) IDs to add as principals on the role.
## Import
+6 -11
View File
@@ -60,24 +60,19 @@ resource "boundary_role" "org_admin" {
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **scope_id** (String) The scope ID containing the sub scope resource.
- **scope_id** (String, Required) The scope ID containing the sub scope resource.
### Optional
- **auto_create_admin_role** (Boolean) If set, when a new scope is created, the provider will not disable the functionality that automatically creates a role in the new scope and gives permissions to manage the scope to the provider's user. Marking this true makes for simpler HCL but results in role resources that are unmanaged by Terraform.
- **auto_create_default_role** (Boolean) Only relevant when creating an org scope. If set, when a new scope is created, the provider will not disable the functionality that automatically creates a role in the new scope and gives listing of scopes and auth methods and the ability to authenticate to the anonymous user. Marking this true makes for simpler HCL but results in role resources that are unmanaged by Terraform.
- **description** (String) The scope description.
- **global_scope** (Boolean) Indicates that the scope containing this value is the global scope, which triggers some specialized behavior to allow it to be imported and managed.
- **name** (String) The scope name. Defaults to the resource name.
### Read-Only
- **id** (String) The ID of the scope.
- **auto_create_admin_role** (Boolean, Optional) If set, when a new scope is created, the provider will not disable the functionality that automatically creates a role in the new scope and gives permissions to manage the scope to the provider's user. Marking this true makes for simpler HCL but results in role resources that are unmanaged by Terraform.
- **auto_create_default_role** (Boolean, Optional) Only relevant when creating an org scope. If set, when a new scope is created, the provider will not disable the functionality that automatically creates a role in the new scope and gives listing of scopes and auth methods and the ability to authenticate to the anonymous user. Marking this true makes for simpler HCL but results in role resources that are unmanaged by Terraform.
- **description** (String, Optional) The scope description.
- **global_scope** (Boolean, Optional) Indicates that the scope containing this value is the global scope, which triggers some specialized behavior to allow it to be imported and managed.
- **name** (String, Optional) The scope name. Defaults to the resource name.
## Import
+14 -42
View File
@@ -1,12 +1,11 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "boundary_target Resource - terraform-provider-boundary"
subcategory: ""
description: |-
The target resource allows you to configure a Boundary target.
---
# boundary_target (Resource)
# Resource `boundary_target`
The target resource allows you to configure a Boundary target.
@@ -27,26 +26,10 @@ resource "boundary_scope" "org" {
}
resource "boundary_scope" "project" {
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
}
resource "boundary_credential_store_vault" "foo" {
name = "vault_store"
description = "My first Vault credential store!"
address = "http://127.0.0.1:8200" # change to Vault address
token = "s.0ufRo6XEGU2jOqnIr7OlFYP5" # change to valid Vault token
scope_id = boundary_scope.project.id
}
resource "boundary_credential_library_vault" "foo" {
name = "foo"
description = "My first Vault credential library!"
credential_store_id = boundary_credential_store_vault.foo.id
path = "my/secret/foo" # change to Vault backend path
http_method = "GET"
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
}
resource "boundary_host_catalog" "foo" {
@@ -83,40 +66,29 @@ resource "boundary_host_set" "foo" {
resource "boundary_target" "foo" {
name = "foo"
description = "Foo target"
type = "tcp"
default_port = "22"
scope_id = boundary_scope.project.id
host_source_ids = [
host_set_ids = [
boundary_host_set.foo.id
]
application_credential_source_ids = [
boundary_credential_library_vault.foo.id
]
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **scope_id** (String) The scope ID in which the resource is created. Defaults to the provider's `default_scope` if unset.
- **type** (String) The target resource type.
- **scope_id** (String, Required) The scope ID in which the resource is created. Defaults to the provider's `default_scope` if unset.
- **type** (String, Required) The target resource type.
### Optional
- **application_credential_source_ids** (Set of String) A list of application credential source ID's.
- **default_port** (Number) The default port for this target.
- **description** (String) The target description.
- **host_source_ids** (Set of String) A list of host source ID's.
- **name** (String) The target name. Defaults to the resource name.
- **session_connection_limit** (Number)
- **session_max_seconds** (Number)
- **worker_filter** (String) Boolean expression to filter the workers for this target
### Read-Only
- **id** (String) The ID of the target.
- **default_port** (Number, Optional) The default port for this target.
- **description** (String, Optional) The target description.
- **host_set_ids** (Set of String, Optional) A list of host set ID's.
- **name** (String, Optional) The target name. Defaults to the resource name.
- **session_connection_limit** (Number, Optional)
- **session_max_seconds** (Number, Optional)
## Import
+5 -11
View File
@@ -1,12 +1,11 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "boundary_user Resource - terraform-provider-boundary"
subcategory: ""
description: |-
The user resource allows you to configure a Boundary user.
---
# boundary_user (Resource)
# Resource `boundary_user`
The user resource allows you to configure a Boundary user.
@@ -41,22 +40,17 @@ resource "boundary_user" "jeff" {
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **scope_id** (String) The scope ID in which the resource is created. Defaults to the provider's `default_scope` if unset.
- **scope_id** (String, Required) The scope ID in which the resource is created. Defaults to the provider's `default_scope` if unset.
### Optional
- **account_ids** (Set of String) Account ID's to associate with this user resource.
- **description** (String) The user description.
- **name** (String) The username. Defaults to the resource name.
### Read-Only
- **id** (String) The ID of the user.
- **account_ids** (Set of String, Optional) Account ID's to associate with this user resource.
- **description** (String, Optional) The user description.
- **name** (String, Optional) The username. Defaults to the resource name.
## Import
@@ -1 +0,0 @@
terraform import boundary_credential_library_vault.foo <my-id>
@@ -1,43 +0,0 @@
resource "boundary_scope" "org" {
name = "organization_one"
description = "My first scope!"
scope_id = "global"
auto_create_admin_role = true
auto_create_default_role = true
}
resource "boundary_scope" "project" {
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
}
resource "boundary_credential_store_vault" "foo" {
name = "foo"
description = "My first Vault credential store!"
address = "http://127.0.0.1:8200" # change to Vault address
token = "s.0ufRo6XEGU2jOqnIr7OlFYP5" # change to valid Vault token
scope_id = boundary_scope.project.id
}
resource "boundary_credential_library_vault" "foo" {
name = "foo"
description = "My first Vault credential library!"
credential_store_id = boundary_credential_store_vault.foo.id
path = "my/secret/foo" # change to Vault backend path
http_method = "GET"
}
resource "boundary_credential_library_vault" "bar" {
name = "bar"
description = "My second Vault credential library!"
credential_store_id = boundary_credential_store_vault.foo.id
path = "my/secret/bar" # change to Vault backend path
http_method = "POST"
http_request_body = <<EOT
{
"key": "Value",
}
EOT
}
@@ -1 +0,0 @@
terraform import boundary_credential_store_vault.foo <my-id>
@@ -1,22 +0,0 @@
resource "boundary_scope" "org" {
name = "organization_one"
description = "My first scope!"
scope_id = "global"
auto_create_admin_role = true
auto_create_default_role = true
}
resource "boundary_scope" "project" {
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
}
resource "boundary_credential_store_vault" "example" {
name = "foo"
description = "My first Vault credential store!"
address = "http://127.0.0.1:8200" # change to Vault address
token = "s.0ufRo6XEGU2jOqnIr7OlFYP5" # change to valid Vault token
scope_id = boundary_scope.project.id
}
@@ -7,10 +7,10 @@ resource "boundary_scope" "org" {
}
resource "boundary_scope" "project" {
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
}
resource "boundary_user" "foo" {
+8 -15
View File
@@ -7,22 +7,15 @@ resource "boundary_scope" "org" {
}
resource "boundary_scope" "project" {
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
}
resource "boundary_host_catalog" "static" {
name = "My catalog"
description = "My first host catalog!"
type = "static"
scope_id = boundary_scope.project.id
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
}
resource "boundary_host" "example" {
name = "example_host"
description = "My first host!"
address = "10.0.0.1"
host_catalog_id = boundary_host_catalog.static.id
name = "example_host"
description = "My first host!"
address = "10.0.0.1"
scope_id = boundary_scope.project.id
}
@@ -7,10 +7,10 @@ resource "boundary_scope" "org" {
}
resource "boundary_scope" "project" {
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
}
resource "boundary_host_catalog" "example" {
@@ -7,10 +7,10 @@ resource "boundary_scope" "org" {
}
resource "boundary_scope" "project" {
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
}
resource "boundary_host_catalog" "static" {
@@ -35,7 +35,6 @@ resource "boundary_host" "2" {
resource "boundary_host_set" "web" {
host_catalog_id = boundary_host_catalog.static.id
type = "static"
host_ids = [
boundary_host.1.id,
boundary_host.2.id,
@@ -7,10 +7,10 @@ resource "boundary_scope" "org" {
}
resource "boundary_scope" "project" {
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
}
resource "boundary_user" "readonly" {
@@ -20,9 +20,9 @@ resource "boundary_user" "readonly" {
}
resource "boundary_role" "readonly" {
name = "readonly"
description = "A readonly role"
principal_ids = [boundary_user.readonly.id]
grant_strings = ["id=*;type=*;actions=read"]
scope_id = boundary_scope.project.id
name = "readonly"
description = "A readonly role"
principals = [boundary_user.readonly.id]
grants = ["id=*;action=read"]
scope_id = boundary_scope.project.id
}
@@ -13,9 +13,9 @@ resource "boundary_user" "readonly" {
}
resource "boundary_role" "readonly" {
name = "readonly"
description = "A readonly role"
principal_ids = [boundary_user.readonly.id]
grant_strings = ["id=*;type=*;actions=read"]
scope_id = boundary_scope.org.id
name = "readonly"
description = "A readonly role"
principals = [boundary_user.readonly.id]
grants = ["id=*;action=read"]
scope_id = boundary_scope.org.id
}
@@ -17,8 +17,8 @@ resource "boundary_user" "bar" {
}
resource "boundary_role" "example" {
name = "My role"
description = "My first role!"
principal_ids = [boundary_user.foo.id, boundary_user.bar.id]
scope_id = boundary_scope.org.id
name = "My role"
description = "My first role!"
principals = [boundary_user.foo.id, boundary_user.bar.id]
scope_id = boundary_scope.org.id
}
+5 -25
View File
@@ -12,26 +12,10 @@ resource "boundary_scope" "org" {
}
resource "boundary_scope" "project" {
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
}
resource "boundary_credential_store_vault" "foo" {
name = "vault_store"
description = "My first Vault credential store!"
address = "http://127.0.0.1:8200" # change to Vault address
token = "s.0ufRo6XEGU2jOqnIr7OlFYP5" # change to valid Vault token
scope_id = boundary_scope.project.id
}
resource "boundary_credential_library_vault" "foo" {
name = "foo"
description = "My first Vault credential library!"
credential_store_id = boundary_credential_store_vault.foo.id
path = "my/secret/foo" # change to Vault backend path
http_method = "GET"
name = "project_one"
description = "My first scope!"
scope_id = boundary_scope.org.id
auto_create_admin_role = true
}
resource "boundary_host_catalog" "foo" {
@@ -68,13 +52,9 @@ resource "boundary_host_set" "foo" {
resource "boundary_target" "foo" {
name = "foo"
description = "Foo target"
type = "tcp"
default_port = "22"
scope_id = boundary_scope.project.id
host_source_ids = [
host_set_ids = [
boundary_host_set.foo.id
]
application_credential_source_ids = [
boundary_credential_library_vault.foo.id
]
}
+33 -198
View File
@@ -1,206 +1,41 @@
module github.com/hashicorp/terraform-provider-boundary
go 1.17
go 1.15
require (
github.com/hashicorp/boundary v0.7.4
github.com/hashicorp/boundary/api v0.0.21
github.com/hashicorp/boundary/sdk v0.0.13
github.com/hashicorp/cap v0.1.1
github.com/hashicorp/go-cty v1.4.1-0.20200723130312-85980079f637
github.com/hashicorp/go-kms-wrapping v0.6.8
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.2
github.com/hashicorp/terraform-plugin-docs v0.5.1
github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.1
github.com/kr/pretty v0.3.0
github.com/mitchellh/go-homedir v1.1.0
github.com/stretchr/testify v1.7.0
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
)
require (
cloud.google.com/go v0.81.0 // indirect
cloud.google.com/go/storage v1.10.0 // indirect
github.com/Azure/azure-sdk-for-go v58.0.0+incompatible // indirect
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.21 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.14 // indirect
github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
cloud.google.com/go v0.68.0 // indirect
cloud.google.com/go/storage v1.11.0 // indirect
github.com/Azure/azure-sdk-for-go v46.4.0+incompatible // indirect
github.com/Azure/go-autorest/autorest/azure/auth v0.5.3 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
github.com/Microsoft/go-winio v0.4.16 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/agext/levenshtein v1.2.2 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190620160927-9418d7b0cd0f // indirect
github.com/apparentlymart/go-textseg v1.0.0 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/armon/go-metrics v0.3.9 // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/aws/aws-sdk-go v1.40.55 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/bgentry/speakeasy v0.1.0 // indirect
github.com/cenkalti/backoff/v3 v3.0.0 // indirect
github.com/cenkalti/backoff/v4 v4.1.0 // indirect
github.com/containerd/containerd v1.4.3 // indirect
github.com/containerd/continuity v0.0.0-20200709052629-daa8e1ccc0bc // indirect
github.com/coreos/go-oidc/v3 v3.0.0 // indirect
github.com/danieljoos/wincred v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/docker/cli v20.10.7+incompatible // indirect
github.com/docker/docker v20.10.7+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect
github.com/godbus/dbus/v5 v5.0.4 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-migrate/migrate/v4 v4.14.1 // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/gax-go/v2 v2.0.5 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0 // indirect
github.com/hashicorp/dbassert v0.0.0-20210708202608-ecf920cf1ed8 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/eventlogger v0.1.1-0.20211106154408-4ff8da3a890c // indirect
github.com/hashicorp/eventlogger/filters/encrypt v0.1.6-0.20211027211326-5db60a48f239 // indirect
github.com/hashicorp/go-bexpr v0.1.10 // indirect
github.com/hashicorp/go-checkpoint v0.5.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-dbw v0.0.0-20211215222256-2ff0d37184ff // indirect
github.com/hashicorp/go-getter v1.5.3 // indirect
github.com/hashicorp/go-hclog v1.0.0 // indirect
github.com/hashicorp/go-immutable-radix v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.4.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6 // indirect
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 // indirect
github.com/hashicorp/go-secure-stdlib/configutil v0.1.2 // indirect
github.com/hashicorp/go-secure-stdlib/gatedwriter v0.1.1 // indirect
github.com/hashicorp/go-secure-stdlib/kv-builder v0.1.1 // indirect
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.3-0.20211129134915-8f7e6fb52622 // indirect
github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 // indirect
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1 // indirect
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
github.com/hashicorp/go-uuid v1.0.2 // indirect
github.com/hashicorp/go-version v1.3.0 // indirect
github.com/hashicorp/golang-lru v0.5.3 // indirect
github.com/hashicorp/hc-install v0.3.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/hcl/v2 v2.3.0 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-exec v0.15.0 // indirect
github.com/hashicorp/terraform-json v0.13.0 // indirect
github.com/hashicorp/terraform-plugin-go v0.5.0 // indirect
github.com/hashicorp/terraform-plugin-log v0.2.0 // indirect
github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896 // indirect
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect
github.com/hashicorp/vault/api v1.1.1 // indirect
github.com/hashicorp/vault/sdk v0.2.1 // indirect
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.10.1 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.2.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
github.com/jackc/pgtype v1.9.0 // indirect
github.com/jackc/pgx/v4 v4.14.0 // indirect
github.com/jefferai/go-libsecret v0.0.0-20210105015933-d08a58b018bc // indirect
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f // indirect
github.com/jefferai/keyring v1.1.7-0.20210105022822-8749b3d9ce79 // indirect
github.com/jinzhu/gorm v1.9.12 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.3 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.11 // indirect
github.com/jstemmer/go-junit-report v0.9.1 // indirect
github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d // indirect
github.com/klauspost/compress v1.13.5 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lib/pq v1.10.2 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-sqlite3 v2.0.1+incompatible // indirect
github.com/mitchellh/cli v1.1.2 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/Azure/go-autorest/autorest/validation v0.3.0 // indirect
github.com/DataDog/datadog-go v4.0.0+incompatible // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.61.545 // indirect
github.com/aws/aws-sdk-go v1.35.4 // indirect
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
github.com/circonus-labs/circonusllhist v0.1.4 // indirect
github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe // indirect
github.com/golang/snappy v0.0.2 // indirect
github.com/hashicorp/boundary v0.1.0
github.com/hashicorp/boundary/api v0.0.1
github.com/hashicorp/boundary/sdk v0.0.1
github.com/hashicorp/go-immutable-radix v1.3.0 // indirect
github.com/hashicorp/go-kms-wrapping v0.5.16
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/hcl/v2 v2.6.0 // indirect
github.com/hashicorp/terraform-plugin-docs v0.2.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.0.4
github.com/hashicorp/yamux v0.0.0-20200609203250-aecfd211c9ce // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.4.2 // indirect
github.com/mitchellh/pointerstructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/opencontainers/runc v1.0.0-rc9 // indirect
github.com/oracle/oci-go-sdk v13.1.0+incompatible // indirect
github.com/ory/dockertest/v3 v3.7.0 // indirect
github.com/pierrec/lz4 v2.5.2+incompatible // indirect
github.com/pires/go-proxyproto v0.6.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/posener/complete v1.2.3 // indirect
github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 // indirect
github.com/russross/blackfriday v1.6.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/sirupsen/logrus v1.7.0 // indirect
github.com/ulikunitz/xz v0.5.8 // indirect
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xo/dburl v0.9.0 // indirect
github.com/zalando/go-keyring v0.1.1 // indirect
github.com/zclconf/go-cty v1.10.0 // indirect
go.opencensus.io v0.23.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
golang.org/x/mod v0.5.1 // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
golang.org/x/sys v0.0.0-20211210111614-af8b64212486 // indirect
golang.org/x/term v0.0.0-20210916214954-140adaaadfaf // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
golang.org/x/tools v0.1.8-0.20211102182255-bb4add04ddef // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/api v0.44.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83 // indirect
google.golang.org/grpc v1.41.0-dev.0.20210907181116-2f3355d2244e // indirect
google.golang.org/protobuf v1.27.1 // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
github.com/prometheus/common v0.14.0 // indirect
github.com/prometheus/procfs v0.2.0 // indirect
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c // indirect
github.com/zclconf/go-cty v1.6.1 // indirect
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 // indirect
golang.org/x/sys v0.0.0-20201006155630-ac719f4daadf // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
gorm.io/driver/postgres v1.2.2 // indirect
gorm.io/driver/sqlite v1.2.4 // indirect
gorm.io/gorm v1.22.3 // indirect
)
+650 -492
View File
File diff suppressed because it is too large. Load diff
+6 -34
View File
@@ -1,44 +1,16 @@
package provider
const (
// IDKey is used for common SDK ID resource attribute
IDKey = "id"
// NameKey is used for common "name" resource attribute
// Key used for common "name" resource attribute
NameKey = "name"
// DescriptionKey is used for common "description" resource attribute
// Key used for common "description" resource attribute
DescriptionKey = "description"
// ScopeIdKey is used for common "scope_id" resource attribute
// Key used for common "scope_id" resource attribute
ScopeIdKey = "scope_id"
// TypeKey is used for common "type" resource attribute
// Key used for common "type" resource attribute
TypeKey = "type"
// HostCatalogIdKey is used for common "host_catalog_id" resource attribute
// Key used for common "host_catalog_id" resource attribute
HostCatalogIdKey = "host_catalog_id"
// AuthMethodIdKey is used for common "auth_method_id" resource attribute
// Key used for common "auth_method_id" resource attribute
AuthMethodIdKey = "auth_method_id"
// PluginIdKey is used for common "plugin_id" resource attribute
PluginIdKey = "plugin_id"
// PluginNameKey is used for common "plugin_name" resource attribute
PluginNameKey = "plugin_name"
// AttributesJsonKey is used for setting attributes and corresponds to the
// API "attributes" key
AttributesJsonKey = "attributes_json"
// SecretsJsonKey is used for setting secrets and corresponds to the API
// "secrets" key
SecretsJsonKey = "secrets_json"
// SecretsHmacKey is a read-only key used for ensuring we detect if secrets
// have changed
SecretsHmacKey = "secrets_hmac"
// PreferredEndpointsKey is used for setting preferred endpoints
PreferredEndpointsKey = "preferred_endpoints"
// SyncIntervalSecondsKey is used for setting the interval seconds
SyncIntervalSecondsKey = "sync_interval_seconds"
// internalSecretsConfigHmacKey is used for storing an hmac of hmac from server +
// config string
internalSecretsConfigHmacKey = "internal_secrets_config_hmac"
// internalHmacUsedForSecretsConfigHmacKey is used for storing the server-provided
// hmac used when calculating the current value of secretsConfigHmacKey
internalHmacUsedForSecretsConfigHmacKey = "internal_hmac_used_for_secrets_config_hmac"
// internalForceUpdateKey is used to force updates so we can always check
// the value of secrets
internalForceUpdateKey = "internal_force_update"
)
+14 -39
View File
@@ -25,7 +25,7 @@ func New() *schema.Provider {
"addr": {
Type: schema.TypeString,
Required: true,
Description: `The base url of the Boundary API, e.g. "http://127.0.0.1:9200". If not set, it will be read from the "BOUNDARY_ADDR" env var.`,
Description: `The base url of the Boundary API, e.g. "http://127.0.0.1". If not set, it will be read from the "BOUNDARY_ADDR" env var.`,
},
"token": {
Type: schema.TypeString,
@@ -52,35 +52,18 @@ func New() *schema.Provider {
Optional: true,
Description: "The auth method password for password-style auth methods",
},
"tls_insecure": {
Type: schema.TypeBool,
Optional: true,
Description: "When set to true, does not validate the Boundary API endpoint certificate",
},
},
ResourcesMap: map[string]*schema.Resource{
"boundary_account": resourceAccount(),
"boundary_account_password": resourceAccountPassword(),
"boundary_account_oidc": resourceAccountOidc(),
"boundary_auth_method": resourceAuthMethod(),
"boundary_auth_method_password": resourceAuthMethodPassword(),
"boundary_auth_method_oidc": resourceAuthMethodOidc(),
"boundary_credential_library_vault": resourceCredentialLibraryVault(),
"boundary_credential_store_vault": resourceCredentialStoreVault(),
"boundary_managed_group": resourceManagedGroup(),
"boundary_group": resourceGroup(),
"boundary_host": resourceHost(),
"boundary_host_static": resourceHostStatic(),
"boundary_host_catalog": resourceHostCatalog(),
"boundary_host_catalog_static": resourceHostCatalogStatic(),
"boundary_host_catalog_plugin": resourceHostCatalogPlugin(),
"boundary_host_set": resourceHostSet(),
"boundary_host_set_static": resourceHostSetStatic(),
"boundary_host_set_plugin": resourceHostSetPlugin(),
"boundary_role": resourceRole(),
"boundary_scope": resourceScope(),
"boundary_target": resourceTarget(),
"boundary_user": resourceUser(),
"boundary_account": resourceAccount(),
"boundary_auth_method": resourceAuthMethod(),
"boundary_group": resourceGroup(),
"boundary_host": resourceHost(),
"boundary_host_catalog": resourceHostCatalog(),
"boundary_host_set": resourceHostset(),
"boundary_role": resourceRole(),
"boundary_scope": resourceScope(),
"boundary_target": resourceTarget(),
"boundary_user": resourceUser(),
},
}
@@ -150,11 +133,11 @@ func providerAuthenticate(ctx context.Context, d *schema.ResourceData, md *metaD
am := authmethods.NewClient(md.client)
at, err := am.Authenticate(ctx, authMethodId.(string), "login", credentials)
at, err := am.Authenticate(ctx, authMethodId.(string), credentials)
if err != nil {
return err
}
md.client.SetToken(at.Attributes["token"].(string))
md.client.SetToken(at.Item.Token)
default:
return errors.New("no suitable auth method information found")
@@ -171,20 +154,12 @@ func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc {
}
if url, ok := d.GetOk("addr"); ok {
if err := client.SetAddr(url.(string)); err != nil {
return nil, diag.FromErr(err)
}
client.SetAddr(url.(string))
}
if client.Addr() == "" {
return nil, diag.Errorf(`"no valid address could be determined from "addr" or "BOUNDARY_ADDR" env var`)
}
if tlsInsecure, ok := d.GetOk("tls_insecure"); ok {
if client.SetTLSConfig(&api.TLSConfig{Insecure: tlsInsecure.(bool)}) != nil {
return nil, diag.Errorf("could not set insecure tls")
}
}
client.SetLimiter(5, 5)
md := &metaData{
+2 -2
View File
@@ -15,11 +15,11 @@ import (
)
var (
tcLoginName = "testuser"
tcLoginName = "user"
tcPassword = "passpass"
tcPAUM = "ampw_0000000000"
tcConfig = []controller.Option{
controller.WithDefaultPasswordAuthMethodId(tcPAUM),
controller.WithDefaultAuthMethodId(tcPAUM),
controller.WithDefaultLoginName(tcLoginName),
controller.WithDefaultPassword(tcPassword),
}
+102 -61
View File
@@ -10,6 +10,12 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
const (
accountTypePassword = "password"
accountLoginNameKey = "login_name"
accountPasswordKey = "password"
)
func resourceAccount() *schema.Resource {
return &schema.Resource{
Description: "The account resource allows you to configure a Boundary account.",
@@ -23,11 +29,6 @@ func resourceAccount() *schema.Resource {
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the account.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The account name. Defaults to the resource name.",
Type: schema.TypeString,
@@ -54,53 +55,31 @@ func resourceAccount() *schema.Resource {
Description: "The login name for this account.",
Type: schema.TypeString,
Optional: true,
Deprecated: "Will be removed in favor of using attributes parameter",
},
accountPasswordKey: {
Description: "The account password. Only set on create, changes will not be reflected when updating account.",
Description: "The account password.",
Type: schema.TypeString,
Optional: true,
Deprecated: "Will be removed in favor of using attributes parameter",
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
if d.Id() == "" {
// This is a new resource do not suppress password diff
return false
}
return true
},
},
},
}
}
func setFromAccountResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
if err := d.Set(NameKey, raw["name"]); err != nil {
return err
}
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
return err
}
if err := d.Set(AuthMethodIdKey, raw["auth_method_id"]); err != nil {
return err
}
if err := d.Set(TypeKey, raw["type"]); err != nil {
return err
}
func setFromAccountResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
d.Set(NameKey, raw["name"])
d.Set(DescriptionKey, raw["description"])
d.Set(AuthMethodIdKey, raw["auth_method_id"])
d.Set(TypeKey, raw["type"])
// TODO(malnick) - remove after deprecation cycle in favor of attributes
switch raw["type"].(string) {
case accountTypePassword:
if attrsVal, ok := raw["attributes"]; ok {
attrs := attrsVal.(map[string]interface{})
if err := d.Set(accountLoginNameKey, attrs["login_name"]); err != nil {
return err
}
d.Set(accountLoginNameKey, attrs["login_name"])
}
}
d.SetId(raw["id"].(string))
return nil
}
func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
@@ -113,24 +92,49 @@ func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, meta int
return diag.Errorf("no auth method ID provided")
}
var loginName *string
if keyVal, ok := d.GetOk(accountLoginNameKey); ok {
key := keyVal.(string)
loginName = &key
}
var password *string
if keyVal, ok := d.GetOk(accountPasswordKey); ok {
key := keyVal.(string)
password = &key
}
opts := []accounts.Option{}
if nameVal, ok := d.GetOk(NameKey); ok {
opts = append(opts, accounts.WithName(nameVal.(string)))
var typeStr string
if typeVal, ok := d.GetOk(TypeKey); ok {
typeStr = typeVal.(string)
} else {
return diag.Errorf("no type provided")
}
switch typeStr {
case accountTypePassword:
if loginName != nil {
opts = append(opts, accounts.WithPasswordAccountLoginName(*loginName))
}
if password != nil {
opts = append(opts, accounts.WithPasswordAccountPassword(*password))
d.Set(accountPasswordKey, *password)
}
default:
return diag.Errorf("invalid type provided")
}
if descVal, ok := d.GetOk(DescriptionKey); ok {
opts = append(opts, accounts.WithDescription(descVal.(string)))
nameVal, ok := d.GetOk(NameKey)
if ok {
nameStr := nameVal.(string)
opts = append(opts, accounts.WithName(nameStr))
}
// TODO(malnick) - remove after deprecation cycle
if name, ok := d.GetOk(accountLoginNameKey); ok {
opts = append(opts, accounts.WithPasswordAccountLoginName(name.(string)))
}
// TODO(malnick) - remove after deprecation cycle
if pass, ok := d.GetOk(accountPasswordKey); ok {
opts = append(opts, accounts.WithPasswordAccountPassword(pass.(string)))
descVal, ok := d.GetOk(DescriptionKey)
if ok {
descStr := descVal.(string)
opts = append(opts, accounts.WithDescription(descStr))
}
aClient := accounts.NewClient(md.client)
@@ -143,9 +147,7 @@ func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, meta int
return diag.Errorf("nil account after create")
}
if err := setFromAccountResponseMap(d, acr.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
setFromAccountResponseMap(d, acr.GetResponseMap())
return nil
}
@@ -156,7 +158,7 @@ func resourceAccountRead(ctx context.Context, d *schema.ResourceData, meta inter
arr, err := aClient.Read(ctx, d.Id())
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Status == int32(http.StatusNotFound) {
d.SetId("")
return nil
}
@@ -166,9 +168,7 @@ func resourceAccountRead(ctx context.Context, d *schema.ResourceData, meta inter
return diag.Errorf("account nil after read")
}
if err := setFromAccountResponseMap(d, arr.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
setFromAccountResponseMap(d, arr.GetResponseMap())
return nil
}
@@ -179,38 +179,79 @@ func resourceAccountUpdate(ctx context.Context, d *schema.ResourceData, meta int
opts := []accounts.Option{}
var name *string
if d.HasChange(NameKey) {
opts = append(opts, accounts.DefaultName())
nameVal, ok := d.GetOk(NameKey)
if ok {
opts = append(opts, accounts.WithName(nameVal.(string)))
nameStr := nameVal.(string)
name = &nameStr
opts = append(opts, accounts.WithName(nameStr))
}
}
var desc *string
if d.HasChange(DescriptionKey) {
opts = append(opts, accounts.DefaultDescription())
descVal, ok := d.GetOk(DescriptionKey)
if ok {
opts = append(opts, accounts.WithDescription(descVal.(string)))
descStr := descVal.(string)
desc = &descStr
opts = append(opts, accounts.WithDescription(descStr))
}
}
// TODO(malnick) - remove after deprecation cycle
var loginName *string
if d.HasChange(accountLoginNameKey) {
opts = append(opts, accounts.DefaultPasswordAccountLoginName())
if keyVal, ok := d.GetOk(accountLoginNameKey); ok {
opts = append(opts, accounts.WithPasswordAccountLoginName(keyVal.(string)))
switch d.Get(TypeKey).(string) {
case accountTypePassword:
opts = append(opts, accounts.DefaultPasswordAccountLoginName())
keyVal, ok := d.GetOk(accountLoginNameKey)
if ok {
keyStr := keyVal.(string)
loginName = &keyStr
opts = append(opts, accounts.WithPasswordAccountLoginName(keyStr))
}
default:
return diag.Errorf(`"login_name" cannot be used with this type of account`)
}
}
var password *string
if d.HasChange(accountPasswordKey) {
switch d.Get(TypeKey).(string) {
case accountTypePassword:
opts = append(opts, accounts.DefaultPasswordAccountPassword())
keyVal, ok := d.GetOk(accountPasswordKey)
if ok {
keyStr := keyVal.(string)
password = &keyStr
opts = append(opts, accounts.WithPasswordAccountPassword(keyStr))
}
default:
return diag.Errorf(`"password" cannot be used with this type of account`)
}
}
if len(opts) > 0 {
opts = append(opts, accounts.WithAutomaticVersioning(true))
aur, err := aClient.Update(ctx, d.Id(), 0, opts...)
_, err := aClient.Update(ctx, d.Id(), 0, opts...)
if err != nil {
return diag.Errorf("error updating account: %v", err)
}
}
setFromAccountResponseMap(d, aur.GetResponse().Map)
if d.HasChange(NameKey) {
d.Set(NameKey, name)
}
if d.HasChange(DescriptionKey) {
d.Set(DescriptionKey, desc)
}
if d.HasChange(accountLoginNameKey) {
d.Set(accountLoginNameKey, loginName)
}
if d.HasChange(accountPasswordKey) {
d.Set(accountPasswordKey, password)
}
return nil
-199
View File
@@ -1,199 +0,0 @@
package provider
import (
"context"
"net/http"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/accounts"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
const (
accountTypeOidc = "password"
accountOidcIssuerKey = "issuer"
accountOidcSubjectKey = "subject"
)
func resourceAccountOidc() *schema.Resource {
return &schema.Resource{
Description: "The account resource allows you to configure a Boundary account.",
CreateContext: resourceAccountOidcCreate,
ReadContext: resourceAccountOidcRead,
UpdateContext: resourceAccountOidcUpdate,
DeleteContext: resourceAccountOidcDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the account.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The account name. Defaults to the resource name.",
Type: schema.TypeString,
Optional: true,
},
DescriptionKey: {
Description: "The account description.",
Type: schema.TypeString,
Optional: true,
},
AuthMethodIdKey: {
Description: "The resource ID for the auth method.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
accountOidcIssuerKey: {
Description: "The OIDC issuer.",
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
accountOidcSubjectKey: {
Description: "The OIDC subject.",
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
},
}
}
func setFromAccountOidcResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
d.Set(NameKey, raw["name"])
d.Set(DescriptionKey, raw["description"])
d.Set(AuthMethodIdKey, raw["auth_method_id"])
d.Set(accountOidcIssuerKey, raw["issuer"])
d.Set(accountOidcSubjectKey, raw["subject"])
d.SetId(raw["id"].(string))
}
func resourceAccountOidcCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
var authMethodId string
if authMethodIdVal, ok := d.GetOk(AuthMethodIdKey); ok {
authMethodId = authMethodIdVal.(string)
} else {
return diag.Errorf("no auth method ID provided")
}
opts := []accounts.Option{}
if i, ok := d.GetOk(accountOidcIssuerKey); ok {
opts = append(opts, accounts.WithOidcAccountIssuer(i.(string)))
}
if s, ok := d.GetOk(accountOidcSubjectKey); ok {
opts = append(opts, accounts.WithOidcAccountSubject(s.(string)))
}
if n, ok := d.GetOk(NameKey); ok {
opts = append(opts, accounts.WithName(n.(string)))
}
if d, ok := d.GetOk(DescriptionKey); ok {
opts = append(opts, accounts.WithDescription(d.(string)))
}
aClient := accounts.NewClient(md.client)
acr, err := aClient.Create(ctx, authMethodId, opts...)
if err != nil {
return diag.Errorf("error creating account: %v", err)
}
if acr == nil {
return diag.Errorf("nil account after create")
}
setFromAccountOidcResponseMap(d, acr.GetResponse().Map)
return nil
}
func resourceAccountOidcRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
aClient := accounts.NewClient(md.client)
arr, err := aClient.Read(ctx, d.Id())
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
d.SetId("")
return nil
}
return diag.Errorf("error reading account: %v", err)
}
if arr == nil {
return diag.Errorf("account nil after read")
}
setFromAccountOidcResponseMap(d, arr.GetResponse().Map)
return nil
}
func resourceAccountOidcUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
aClient := accounts.NewClient(md.client)
opts := []accounts.Option{}
if d.HasChange(NameKey) {
opts = append(opts, accounts.DefaultName())
if n, ok := d.GetOk(NameKey); ok {
opts = append(opts, accounts.WithName(n.(string)))
}
}
if d.HasChange(DescriptionKey) {
opts = append(opts, accounts.DefaultDescription())
if d, ok := d.GetOk(DescriptionKey); ok {
opts = append(opts, accounts.WithDescription(d.(string)))
}
}
if d.HasChange(accountOidcIssuerKey) {
opts = append(opts, accounts.DefaultOidcAccountIssuer())
if i, ok := d.GetOk(accountOidcIssuerKey); ok {
opts = append(opts, accounts.WithOidcAccountIssuer(i.(string)))
}
}
if d.HasChange(accountOidcSubjectKey) {
opts = append(opts, accounts.DefaultOidcAccountSubject())
if i, ok := d.GetOk(accountOidcSubjectKey); ok {
opts = append(opts, accounts.WithOidcAccountSubject(i.(string)))
}
}
if len(opts) > 0 {
opts = append(opts, accounts.WithAutomaticVersioning(true))
aur, err := aClient.Update(ctx, d.Id(), 0, opts...)
if err != nil {
return diag.Errorf("error updating account: %v", err)
}
setFromAccountOidcResponseMap(d, aur.GetResponse().Map)
}
return nil
}
func resourceAccountOidcDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
aClient := accounts.NewClient(md.client)
_, err := aClient.Delete(ctx, d.Id())
if err != nil {
return diag.Errorf("error deleting account: %v", err)
}
return nil
}
@@ -1,266 +0,0 @@
package provider
import (
"context"
"net/http"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/accounts"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
const (
accountTypePassword = "password"
accountLoginNameKey = "login_name"
accountPasswordKey = "password"
)
func resourceAccountPassword() *schema.Resource {
return &schema.Resource{
Description: "The account resource allows you to configure a Boundary account.",
CreateContext: resourceAccountPasswordCreate,
ReadContext: resourceAccountPasswordRead,
UpdateContext: resourceAccountPasswordUpdate,
DeleteContext: resourceAccountPasswordDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the account.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The account name. Defaults to the resource name.",
Type: schema.TypeString,
Optional: true,
},
DescriptionKey: {
Description: "The account description.",
Type: schema.TypeString,
Optional: true,
},
AuthMethodIdKey: {
Description: "The resource ID for the auth method.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
TypeKey: {
Description: "The resource type.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
accountLoginNameKey: {
Description: "The login name for this account.",
Type: schema.TypeString,
Optional: true,
Deprecated: "Will be removed in favor of using attributes parameter",
},
accountPasswordKey: {
Description: "The account password. Only set on create, changes will not be reflected when updating account.",
Type: schema.TypeString,
Optional: true,
Deprecated: "Will be removed in favor of using attributes parameter",
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
if d.Id() == "" {
// This is a new resource do not suppress password diff
return false
}
return true
},
},
},
}
}
func setFromAccountPasswordResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
d.Set(NameKey, raw["name"])
d.Set(DescriptionKey, raw["description"])
d.Set(AuthMethodIdKey, raw["auth_method_id"])
d.Set(TypeKey, raw["type"])
switch raw["type"].(string) {
case accountTypePassword:
if attrsVal, ok := raw["attributes"]; ok {
attrs := attrsVal.(map[string]interface{})
d.Set(accountLoginNameKey, attrs["login_name"])
}
}
d.SetId(raw["id"].(string))
}
func resourceAccountPasswordCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
var authMethodId string
if authMethodIdVal, ok := d.GetOk(AuthMethodIdKey); ok {
authMethodId = authMethodIdVal.(string)
} else {
return diag.Errorf("no auth method ID provided")
}
var loginName *string
if keyVal, ok := d.GetOk(accountLoginNameKey); ok {
key := keyVal.(string)
loginName = &key
}
var password *string
if keyVal, ok := d.GetOk(accountPasswordKey); ok {
key := keyVal.(string)
password = &key
}
opts := []accounts.Option{}
var typeStr string
if typeVal, ok := d.GetOk(TypeKey); ok {
typeStr = typeVal.(string)
} else {
return diag.Errorf("no type provided")
}
switch typeStr {
case accountTypePassword:
if loginName != nil {
opts = append(opts, accounts.WithPasswordAccountLoginName(*loginName))
}
if password != nil {
opts = append(opts, accounts.WithPasswordAccountPassword(*password))
d.Set(accountPasswordKey, *password)
}
default:
return diag.Errorf("invalid type provided")
}
nameVal, ok := d.GetOk(NameKey)
if ok {
nameStr := nameVal.(string)
opts = append(opts, accounts.WithName(nameStr))
}
descVal, ok := d.GetOk(DescriptionKey)
if ok {
descStr := descVal.(string)
opts = append(opts, accounts.WithDescription(descStr))
}
aClient := accounts.NewClient(md.client)
acr, err := aClient.Create(ctx, authMethodId, opts...)
if err != nil {
return diag.Errorf("error creating account: %v", err)
}
if acr == nil {
return diag.Errorf("nil account after create")
}
setFromAccountPasswordResponseMap(d, acr.GetResponse().Map)
return nil
}
func resourceAccountPasswordRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
aClient := accounts.NewClient(md.client)
arr, err := aClient.Read(ctx, d.Id())
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
d.SetId("")
return nil
}
return diag.Errorf("error reading account: %v", err)
}
if arr == nil {
return diag.Errorf("account nil after read")
}
setFromAccountPasswordResponseMap(d, arr.GetResponse().Map)
return nil
}
func resourceAccountPasswordUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
aClient := accounts.NewClient(md.client)
opts := []accounts.Option{}
var name *string
if d.HasChange(NameKey) {
opts = append(opts, accounts.DefaultName())
nameVal, ok := d.GetOk(NameKey)
if ok {
nameStr := nameVal.(string)
name = &nameStr
opts = append(opts, accounts.WithName(nameStr))
}
}
var desc *string
if d.HasChange(DescriptionKey) {
opts = append(opts, accounts.DefaultDescription())
descVal, ok := d.GetOk(DescriptionKey)
if ok {
descStr := descVal.(string)
desc = &descStr
opts = append(opts, accounts.WithDescription(descStr))
}
}
var loginName *string
if d.HasChange(accountLoginNameKey) {
switch d.Get(TypeKey).(string) {
case accountTypePassword:
opts = append(opts, accounts.DefaultPasswordAccountLoginName())
keyVal, ok := d.GetOk(accountLoginNameKey)
if ok {
keyStr := keyVal.(string)
loginName = &keyStr
opts = append(opts, accounts.WithPasswordAccountLoginName(keyStr))
}
default:
return diag.Errorf(`"login_name" cannot be used with this type of account`)
}
}
if len(opts) > 0 {
opts = append(opts, accounts.WithAutomaticVersioning(true))
_, err := aClient.Update(ctx, d.Id(), 0, opts...)
if err != nil {
return diag.Errorf("error updating account: %v", err)
}
}
if d.HasChange(NameKey) {
d.Set(NameKey, name)
}
if d.HasChange(DescriptionKey) {
d.Set(DescriptionKey, desc)
}
if d.HasChange(accountLoginNameKey) {
d.Set(accountLoginNameKey, loginName)
}
return nil
}
func resourceAccountPasswordDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
aClient := accounts.NewClient(md.client)
_, err := aClient.Delete(ctx, d.Id())
if err != nil {
return diag.Errorf("error deleting account: %v", err)
}
return nil
}
@@ -1,150 +0,0 @@
package provider
import (
"context"
"fmt"
"net/http"
"testing"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/accounts"
"github.com/hashicorp/boundary/testing/controller"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)
const (
fooAccountPasswordDesc = "test account"
fooAccountPasswordDescUpdate = "test account update"
)
var (
fooAccountPassword = fmt.Sprintf(`
resource "boundary_auth_method" "foo" {
name = "test"
description = "test account"
type = "password"
scope_id = boundary_scope.org1.id
depends_on = [boundary_role.org1_admin]
}
resource "boundary_account_password" "foo" {
name = "test"
description = "%s"
type = "password"
login_name = "foo"
password = "foofoofoo"
auth_method_id = boundary_auth_method.foo.id
}`, fooAccountPasswordDesc)
fooAccountPasswordUpdate = fmt.Sprintf(`
resource "boundary_auth_method" "foo" {
name = "test"
description = "test account"
type = "password"
scope_id = boundary_scope.org1.id
depends_on = [boundary_role.org1_admin]
}
resource "boundary_account_password" "foo" {
name = "test"
description = "%s"
type = "password"
login_name = "foo"
password = "foofoofoo"
auth_method_id = boundary_auth_method.foo.id
}`, fooAccountPasswordDescUpdate)
)
func TestAccAccount(t *testing.T) {
tc := controller.NewTestController(t, tcConfig...)
defer tc.Shutdown()
url := tc.ApiAddrs()[0]
var provider *schema.Provider
resource.Test(t, resource.TestCase{
ProviderFactories: providerFactories(&provider),
CheckDestroy: testAccCheckAccountPasswordResourceDestroy(t, provider),
Steps: []resource.TestStep{
{
// create
Config: testConfig(url, fooOrg, fooAccountPassword),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("boundary_account_password.foo", "description", fooAccountPasswordDesc),
resource.TestCheckResourceAttr("boundary_account_password.foo", "name", "test"),
resource.TestCheckResourceAttr("boundary_account_password.foo", "type", "password"),
resource.TestCheckResourceAttr("boundary_account_password.foo", "login_name", "foo"),
resource.TestCheckResourceAttr("boundary_account_password.foo", "password", "foofoofoo"),
testAccCheckAccountPasswordResourceExists(provider, "boundary_account_password.foo"),
),
},
importStep("boundary_account_password.foo", "password"),
{
// update
Config: testConfig(url, fooOrg, fooAccountPasswordUpdate),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("boundary_account_password.foo", "description", fooAccountPasswordDescUpdate),
resource.TestCheckResourceAttr("boundary_account_password.foo", "name", "test"),
resource.TestCheckResourceAttr("boundary_account_password.foo", "type", "password"),
resource.TestCheckResourceAttr("boundary_account_password.foo", "login_name", "foo"),
resource.TestCheckResourceAttr("boundary_account_password.foo", "password", "foofoofoo"),
testAccCheckAccountPasswordResourceExists(provider, "boundary_account_password.foo"),
),
},
importStep("boundary_account_password.foo", "password"),
},
})
}
func testAccCheckAccountPasswordResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Not found: %s", name)
}
id := rs.Primary.ID
if id == "" {
return fmt.Errorf("No ID is set")
}
md := testProvider.Meta().(*metaData)
amClient := accounts.NewClient(md.client)
if _, err := amClient.Read(context.Background(), id); err != nil {
return fmt.Errorf("Got an error when reading account %q: %v", id, err)
}
return nil
}
}
func testAccCheckAccountPasswordResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
return func(s *terraform.State) error {
if testProvider.Meta() == nil {
t.Fatal("got nil provider metadata")
}
md := testProvider.Meta().(*metaData)
for _, rs := range s.RootModule().Resources {
switch rs.Type {
case "boundary_account_password":
id := rs.Primary.ID
amClient := accounts.NewClient(md.client)
_, err := amClient.Read(context.Background(), id)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
return fmt.Errorf("didn't get a 404 when reading destroyed account %q: %v", id, err)
}
default:
continue
}
}
return nil
}
}
+4 -4
View File
@@ -57,7 +57,7 @@ resource "boundary_account" "foo" {
}`, fooAccountDescUpdate)
)
func TestAccAccountBase(t *testing.T) {
func TestAccAccount(t *testing.T) {
tc := controller.NewTestController(t, tcConfig...)
defer tc.Shutdown()
url := tc.ApiAddrs()[0]
@@ -69,7 +69,7 @@ func TestAccAccountBase(t *testing.T) {
CheckDestroy: testAccCheckAccountResourceDestroy(t, provider),
Steps: []resource.TestStep{
{
// create
//create
Config: testConfig(url, fooOrg, fooAccount),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("boundary_account.foo", "description", fooAccountDesc),
@@ -137,8 +137,8 @@ func testAccCheckAccountResourceDestroy(t *testing.T, testProvider *schema.Provi
amClient := accounts.NewClient(md.client)
_, err := amClient.Read(context.Background(), id)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
return fmt.Errorf("didn't get a 404 when reading destroyed account %q: %v", id, err)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Status != http.StatusNotFound {
return fmt.Errorf("Didn't get a 404 when reading destroyed account %q: %v", id, err)
}
default:
+107 -69
View File
@@ -11,6 +11,12 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
const (
authmethodTypePassword = "password"
authmethodMinLoginNameLengthKey = "min_login_name_length"
authmethodMinPasswordLengthKey = "min_password_length"
)
func resourceAuthMethod() *schema.Resource {
return &schema.Resource{
Description: "The auth method resource allows you to configure a Boundary auth_method.",
@@ -24,11 +30,6 @@ func resourceAuthMethod() *schema.Resource {
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the account.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The auth method name. Defaults to the resource name.",
Type: schema.TypeString,
@@ -56,32 +57,22 @@ func resourceAuthMethod() *schema.Resource {
Type: schema.TypeInt,
Optional: true,
Computed: true,
Deprecated: "Will be removed in favor of using attributes parameter",
},
authmethodMinPasswordLengthKey: {
Description: "The minimum password length.",
Type: schema.TypeInt,
Optional: true,
Computed: true,
Deprecated: "Will be removed in favor of using attributes parameter",
},
},
}
}
func setFromAuthMethodResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
if err := d.Set(NameKey, raw["name"]); err != nil {
return err
}
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
return err
}
if err := d.Set(ScopeIdKey, raw["scope_id"]); err != nil {
return err
}
if err := d.Set(TypeKey, raw["type"]); err != nil {
return err
}
func setFromAuthMethodResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
d.Set(NameKey, raw["name"])
d.Set(DescriptionKey, raw["description"])
d.Set(ScopeIdKey, raw["scope_id"])
d.Set(TypeKey, raw["type"])
switch raw["type"].(string) {
case authmethodTypePassword:
@@ -90,19 +81,15 @@ func setFromAuthMethodResponseMap(d *schema.ResourceData, raw map[string]interfa
minLoginNameLength := attrs["min_login_name_length"].(json.Number)
minLoginNameLengthInt, _ := minLoginNameLength.Int64()
if err := d.Set(authmethodMinLoginNameLengthKey, int(minLoginNameLengthInt)); err != nil {
return err
}
d.Set(authmethodMinLoginNameLengthKey, int(minLoginNameLengthInt))
minPasswordLength := attrs["min_password_length"].(json.Number)
minPasswordLengthInt, _ := minPasswordLength.Int64()
if err := d.Set(authmethodMinPasswordLengthKey, int(minPasswordLengthInt)); err != nil {
return err
}
d.Set(authmethodMinPasswordLengthKey, int(minPasswordLengthInt))
}
}
d.SetId(raw["id"].(string))
return nil
}
func resourceAuthMethodCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
@@ -115,31 +102,48 @@ func resourceAuthMethodCreate(ctx context.Context, d *schema.ResourceData, meta
return diag.Errorf("no scope ID provided")
}
var minLoginNameLength *int
if minLengthVal, ok := d.GetOk(authmethodMinLoginNameLengthKey); ok {
minLength := minLengthVal.(int)
minLoginNameLength = &minLength
}
var minPasswordLength *int
if minLengthVal, ok := d.GetOk(authmethodMinPasswordLengthKey); ok {
minLength := minLengthVal.(int)
minPasswordLength = &minLength
}
opts := []authmethods.Option{}
var typeStr string
if typeVal, ok := d.GetOk(TypeKey); ok {
typeStr = typeVal.(string)
} else {
return diag.Errorf("no type provided")
}
opts := []authmethods.Option{}
if nameVal, ok := d.GetOk(NameKey); ok {
opts = append(opts, authmethods.WithName(nameVal.(string)))
switch typeStr {
case authmethodTypePassword:
if minLoginNameLength != nil {
opts = append(opts, authmethods.WithPasswordAuthMethodMinLoginNameLength(uint32(*minLoginNameLength)))
}
if minPasswordLength != nil {
opts = append(opts, authmethods.WithPasswordAuthMethodMinPasswordLength(uint32(*minPasswordLength)))
}
default:
return diag.Errorf("invalid type provided")
}
if descVal, ok := d.GetOk(DescriptionKey); ok {
opts = append(opts, authmethods.WithDescription(descVal.(string)))
nameVal, ok := d.GetOk(NameKey)
if ok {
nameStr := nameVal.(string)
opts = append(opts, authmethods.WithName(nameStr))
}
// TODO(malnick) - deprecate
if minLengthVal, ok := d.GetOk(authmethodMinLoginNameLengthKey); ok {
opts = append(opts, authmethods.WithPasswordAuthMethodMinLoginNameLength(uint32(minLengthVal.(int))))
}
// TODO(malnick) - deprecate
if minLengthVal, ok := d.GetOk(authmethodMinPasswordLengthKey); ok {
opts = append(opts, authmethods.WithPasswordAuthMethodMinPasswordLength(uint32(minLengthVal.(int))))
descVal, ok := d.GetOk(DescriptionKey)
if ok {
descStr := descVal.(string)
opts = append(opts, authmethods.WithDescription(descStr))
}
amClient := authmethods.NewClient(md.client)
@@ -152,9 +156,7 @@ func resourceAuthMethodCreate(ctx context.Context, d *schema.ResourceData, meta
return diag.Errorf("nil auth method after create")
}
if err := setFromAuthMethodResponseMap(d, amcr.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
setFromAuthMethodResponseMap(d, amcr.GetResponseMap())
return nil
}
@@ -165,7 +167,7 @@ func resourceAuthMethodRead(ctx context.Context, d *schema.ResourceData, meta in
amrr, err := amClient.Read(ctx, d.Id())
if err != nil {
if apiErr := api.AsServerError(err); apiErr.Response().StatusCode() == http.StatusNotFound {
if apiErr := api.AsServerError(err); apiErr.Status == int32(http.StatusNotFound) {
d.SetId("")
return nil
}
@@ -175,9 +177,7 @@ func resourceAuthMethodRead(ctx context.Context, d *schema.ResourceData, meta in
return diag.Errorf("auth method nil after read")
}
if err := setFromAuthMethodResponseMap(d, amrr.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
setFromAuthMethodResponseMap(d, amrr.GetResponseMap())
return nil
}
@@ -188,42 +188,80 @@ func resourceAuthMethodUpdate(ctx context.Context, d *schema.ResourceData, meta
opts := []authmethods.Option{}
var name *string
if d.HasChange(NameKey) {
opts = append(opts, authmethods.DefaultName())
if nameVal, ok := d.GetOk(NameKey); ok {
opts = append(opts, authmethods.WithName(nameVal.(string)))
nameVal, ok := d.GetOk(NameKey)
if ok {
nameStr := nameVal.(string)
name = &nameStr
opts = append(opts, authmethods.WithName(nameStr))
}
}
var desc *string
if d.HasChange(DescriptionKey) {
opts = append(opts, authmethods.DefaultDescription())
if descVal, ok := d.GetOk(DescriptionKey); ok {
opts = append(opts, authmethods.WithDescription(descVal.(string)))
descVal, ok := d.GetOk(DescriptionKey)
if ok {
descStr := descVal.(string)
desc = &descStr
opts = append(opts, authmethods.WithDescription(descStr))
}
}
// TODO(malnick) - deprecate
if d.HasChange(authmethodMinPasswordLengthKey) {
opts = append(opts, authmethods.DefaultPasswordAuthMethodMinPasswordLength())
if minLengthVal, ok := d.GetOk(authmethodMinPasswordLengthKey); ok {
opts = append(opts, authmethods.WithPasswordAuthMethodMinPasswordLength(uint32(minLengthVal.(int))))
}
}
// TODO(malnick) - deprecate
var minLoginNameLength *int
if d.HasChange(authmethodMinLoginNameLengthKey) {
opts = append(opts, authmethods.DefaultPasswordAuthMethodMinLoginNameLength())
if minLengthVal, ok := d.GetOk(authmethodMinLoginNameLengthKey); ok {
opts = append(opts, authmethods.WithPasswordAuthMethodMinLoginNameLength(uint32(minLengthVal.(int))))
switch d.Get(TypeKey).(string) {
case authmethodTypePassword:
opts = append(opts, authmethods.DefaultPasswordAuthMethodMinLoginNameLength())
minLengthVal, ok := d.GetOk(authmethodMinLoginNameLengthKey)
if ok {
minLengthInt := minLengthVal.(int)
minLoginNameLength = &minLengthInt
opts = append(opts, authmethods.WithPasswordAuthMethodMinLoginNameLength(uint32(minLengthInt)))
}
default:
return diag.Errorf(`"min_login_name_length" cannot be used with this type of auth method`)
}
}
opts = append(opts, authmethods.WithAutomaticVersioning(true))
amu, err := amClient.Update(ctx, d.Id(), 0, opts...)
if err != nil {
return diag.Errorf("error updating auth method: %v", err)
var minPasswordLength *int
if d.HasChange(authmethodMinPasswordLengthKey) {
switch d.Get(TypeKey).(string) {
case authmethodTypePassword:
opts = append(opts, authmethods.DefaultPasswordAuthMethodMinPasswordLength())
minLengthVal, ok := d.GetOk(authmethodMinPasswordLengthKey)
if ok {
minLengthInt := minLengthVal.(int)
minPasswordLength = &minLengthInt
opts = append(opts, authmethods.WithPasswordAuthMethodMinPasswordLength(uint32(minLengthInt)))
}
default:
return diag.Errorf(`"min_password_length" cannot be used with this type of auth method`)
}
}
setFromAuthMethodResponseMap(d, amu.GetResponse().Map)
if len(opts) > 0 {
opts = append(opts, authmethods.WithAutomaticVersioning(true))
_, err := amClient.Update(ctx, d.Id(), 0, opts...)
if err != nil {
return diag.Errorf("error updating auth method: %v", err)
}
}
if d.HasChange(NameKey) {
d.Set(NameKey, name)
}
if d.HasChange(DescriptionKey) {
d.Set(DescriptionKey, desc)
}
if d.HasChange(authmethodMinLoginNameLengthKey) {
d.Set(authmethodMinLoginNameLengthKey, minLoginNameLength)
}
if d.HasChange(authmethodMinPasswordLengthKey) {
d.Set(authmethodMinPasswordLengthKey, minPasswordLength)
}
return nil
}
@@ -1,564 +0,0 @@
package provider
import (
"context"
"encoding/json"
"net/http"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/authmethods"
"github.com/hashicorp/boundary/api/scopes"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
const (
authmethodTypeOidc = "oidc"
authmethodOidcIssuerKey = "issuer"
authmethodOidcClientIdKey = "client_id"
authmethodOidcClientSecretKey = "client_secret"
authmethodOidcMaxAgeKey = "max_age"
authmethodOidcApiUrlPrefixKey = "api_url_prefix"
authmethodOidcIdpCaCertsKey = "idp_ca_certs"
authmethodOidcAllowedAudiencesKey = "allowed_audiences"
authmethodOidcDisableDiscoveredConfigValidationKey = "disable_discovered_config_validation"
authmethodOidcSigningAlgorithmsKey = "signing_algorithms"
authmethodOidcIsPrimaryAuthMethodForScope = "is_primary_for_scope"
authmethodOidcAccountClaimMapsKey = "account_claim_maps"
authmethodOidcClaimsScopesKey = "claims_scopes"
// computed-only parameters
authmethodOidcCallbackUrlKey = "callback_url"
authmethodOidcClientSecretHmacKey = "client_secret_hmac"
authmethodOidcStateKey = "state"
)
func resourceAuthMethodOidc() *schema.Resource {
return &schema.Resource{
Description: "The OIDC auth method resource allows you to configure a Boundary auth_method_oidc.",
CreateContext: resourceAuthMethodOidcCreate,
ReadContext: resourceAuthMethodOidcRead,
UpdateContext: resourceAuthMethodOidcUpdate,
DeleteContext: resourceAuthMethodOidcDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the auth method.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The auth method name. Defaults to the resource name.",
Type: schema.TypeString,
Optional: true,
},
DescriptionKey: {
Description: "The auth method description.",
Type: schema.TypeString,
Optional: true,
},
ScopeIdKey: {
Description: "The scope ID.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
// OIDC specific configurable parameters
authmethodOidcAllowedAudiencesKey: {
Description: "Audiences for which the provider responses will be allowed",
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Optional: true,
},
authmethodOidcApiUrlPrefixKey: {
Description: "The API prefix to use when generating callback URLs for the provider. Should be set to an address at which the provider can reach back to the controller.",
Type: schema.TypeString,
Optional: true,
},
authmethodOidcIdpCaCertsKey: {
Description: "A list of CA certificates to trust when validating the IdP's token signatures.",
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Optional: true,
},
authmethodOidcClientIdKey: {
Description: "The client ID assigned to this auth method from the provider.",
Type: schema.TypeString,
Optional: true,
},
authmethodOidcClientSecretKey: {
Description: "The secret key assigned to this auth method from the provider. Once set, only the hash will be kept and the original value can be removed from configuration.",
Type: schema.TypeString,
Optional: true,
},
authmethodOidcIssuerKey: {
Description: "The issuer corresponding to the provider, which must match the issuer field in generated tokens.",
Type: schema.TypeString,
Optional: true,
},
authmethodOidcDisableDiscoveredConfigValidationKey: {
Description: "Disables validation logic ensuring that the OIDC provider's information from its discovery endpoint matches the information here. The validation is only performed at create or update time.",
Type: schema.TypeBool,
Optional: true,
},
authmethodOidcMaxAgeKey: {
Description: "The max age to provide to the provider, indicating how much time is allowed to have passed since the last authentication before the user is challenged again.",
Type: schema.TypeInt,
Optional: true,
},
authmethodOidcSigningAlgorithmsKey: {
Description: "Allowed signing algorithms for the provider's issued tokens.",
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Optional: true,
},
authmethodOidcAccountClaimMapsKey: {
Description: "Account claim maps for the to_claim of sub.",
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Optional: true,
// per comment in https://github.com/hashicorp/boundary/pull/1186
ForceNew: true,
},
authmethodOidcClaimsScopesKey: {
Description: "Claims scopes.",
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Optional: true,
},
// OIDC specific immutable and computed parameters
authmethodOidcClientSecretHmacKey: {
Description: "The HMAC of the client secret returned by the Boundary controller, which is used for comparison after initial setting of the value.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
authmethodOidcStateKey: {
Description: "Can be one of 'inactive', 'active-private', or 'active-public'. Currently automatically set to active-public.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
authmethodOidcCallbackUrlKey: {
Description: "The URL that should be provided to the IdP for callbacks.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
authmethodOidcIsPrimaryAuthMethodForScope: {
Description: "When true, makes this auth method the primary auth method for the scope in which it resides. The primary auth method for a scope means the the user will be automatically created when they login using an OIDC account.",
Type: schema.TypeBool,
Optional: true,
},
TypeKey: {
Description: "The type of auth method; hardcoded.",
Type: schema.TypeString,
Default: authmethodTypeOidc,
Optional: true,
},
},
}
}
func setFromOidcAuthMethodResponseMap(d *schema.ResourceData, raw map[string]interface{}) diag.Diagnostics {
d.Set(NameKey, raw[NameKey])
d.Set(DescriptionKey, raw[DescriptionKey])
d.Set(ScopeIdKey, raw[ScopeIdKey])
d.Set(TypeKey, raw[TypeKey])
if attrsVal, ok := raw["attributes"]; ok {
attrs := attrsVal.(map[string]interface{})
// these are always set
d.Set(authmethodOidcStateKey, attrs[authmethodOidcStateKey].(string))
d.Set(authmethodOidcIssuerKey, attrs[authmethodOidcIssuerKey].(string))
d.Set(authmethodOidcClientIdKey, attrs[authmethodOidcClientIdKey].(string))
d.Set(authmethodOidcClientSecretHmacKey, attrs[authmethodOidcClientSecretHmacKey].(string))
if certs, ok := attrs[authmethodOidcIdpCaCertsKey]; ok {
d.Set(authmethodOidcIdpCaCertsKey, certs.([]interface{}))
}
if aud, ok := attrs[authmethodOidcAllowedAudiencesKey]; ok {
d.Set(authmethodOidcAllowedAudiencesKey, aud.([]interface{}))
}
if m, ok := attrs[authmethodOidcMaxAgeKey]; ok {
maxAge := m.(json.Number)
maxAgeInt, _ := maxAge.Int64()
d.Set(authmethodOidcMaxAgeKey, maxAgeInt)
}
if val, ok := attrs[authmethodOidcApiUrlPrefixKey]; ok {
d.Set(authmethodOidcApiUrlPrefixKey, val.(string))
}
if val, ok := attrs[authmethodOidcCallbackUrlKey]; ok {
d.Set(authmethodOidcCallbackUrlKey, val.(string))
}
if val, ok := attrs[authmethodOidcSigningAlgorithmsKey]; ok {
d.Set(authmethodOidcSigningAlgorithmsKey, val.([]interface{}))
}
if p, ok := attrs[authmethodOidcIsPrimaryAuthMethodForScope]; ok {
d.Set(authmethodOidcIsPrimaryAuthMethodForScope, p.(bool))
}
if p, ok := attrs[authmethodOidcDisableDiscoveredConfigValidationKey]; ok {
d.Set(authmethodOidcDisableDiscoveredConfigValidationKey, p.(bool))
}
if p, ok := attrs[authmethodOidcAccountClaimMapsKey]; ok {
d.Set(authmethodOidcAccountClaimMapsKey, p.([]interface{}))
}
if p, ok := attrs[authmethodOidcClaimsScopesKey]; ok {
d.Set(authmethodOidcClaimsScopesKey, p.([]interface{}))
}
}
d.SetId(raw["id"].(string))
return nil
}
func resourceAuthMethodOidcCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
var typeStr string
if typeVal, ok := d.GetOk(TypeKey); ok {
typeStr = typeVal.(string)
} else {
return diag.Errorf("no type provided")
}
opts := []authmethods.Option{}
opts = append(opts, authmethods.WithAutomaticVersioning(true))
if issuer, ok := d.GetOk(authmethodOidcIssuerKey); ok {
opts = append(opts, authmethods.WithOidcAuthMethodIssuer(issuer.(string)))
}
if clientId, ok := d.GetOk(authmethodOidcClientIdKey); ok {
opts = append(opts, authmethods.WithOidcAuthMethodClientId(clientId.(string)))
}
if clientSecret, ok := d.GetOk(authmethodOidcClientSecretKey); ok {
opts = append(opts, authmethods.WithOidcAuthMethodClientSecret(clientSecret.(string)))
}
if maxAge, ok := d.GetOk(authmethodOidcMaxAgeKey); ok {
opts = append(opts, authmethods.WithOidcAuthMethodMaxAge(uint32(maxAge.(int))))
}
if prefix, ok := d.GetOk(authmethodOidcApiUrlPrefixKey); ok {
opts = append(opts, authmethods.WithOidcAuthMethodApiUrlPrefix(prefix.(string)))
}
if certs, ok := d.GetOk(authmethodOidcIdpCaCertsKey); ok {
certList := []string{}
for _, c := range certs.([]interface{}) {
certList = append(certList, c.(string))
}
opts = append(opts, authmethods.WithOidcAuthMethodIdpCaCerts(certList))
}
if aud, ok := d.GetOk(authmethodOidcAllowedAudiencesKey); ok {
audList := []string{}
for _, c := range aud.([]interface{}) {
audList = append(audList, c.(string))
}
opts = append(opts, authmethods.WithOidcAuthMethodAllowedAudiences(audList))
}
if dis, ok := d.GetOk(authmethodOidcDisableDiscoveredConfigValidationKey); ok {
opts = append(opts, authmethods.WithOidcAuthMethodDisableDiscoveredConfigValidation(dis.(bool)))
}
if algos, ok := d.GetOk(authmethodOidcSigningAlgorithmsKey); ok {
algoList := []string{}
for _, c := range algos.([]interface{}) {
algoList = append(algoList, c.(string))
}
opts = append(opts, authmethods.WithOidcAuthMethodSigningAlgorithms(algoList))
}
if claims, ok := d.GetOk(authmethodOidcAccountClaimMapsKey); ok {
cList := []string{}
for _, c := range claims.([]interface{}) {
cList = append(cList, c.(string))
}
opts = append(opts, authmethods.WithOidcAuthMethodAccountClaimMaps(cList))
}
if claimsScopes, ok := d.GetOk(authmethodOidcClaimsScopesKey); ok {
cList := []string{}
for _, c := range claimsScopes.([]interface{}) {
cList = append(cList, c.(string))
}
opts = append(opts, authmethods.WithOidcAuthMethodClaimsScopes(cList))
}
nameVal, ok := d.GetOk(NameKey)
if ok {
nameStr := nameVal.(string)
opts = append(opts, authmethods.WithName(nameStr))
}
descVal, ok := d.GetOk(DescriptionKey)
if ok {
descStr := descVal.(string)
opts = append(opts, authmethods.WithDescription(descStr))
}
var scopeId string
if scopeIdVal, ok := d.GetOk(ScopeIdKey); ok {
scopeId = scopeIdVal.(string)
} else {
return diag.Errorf("no scope ID provided")
}
amClient := authmethods.NewClient(md.client)
amcr, err := amClient.Create(ctx, typeStr, scopeId, opts...)
if err != nil {
return diag.Errorf("error creating auth method: %v", err)
}
if amcr == nil {
return diag.Errorf("nil auth method after create")
}
// need to update state is create was successful to avoid loosing state if the change state or update
// scope step that follows errors out
if err := setFromOidcAuthMethodResponseMap(d, amcr.GetResponse().Map); err != nil {
return diag.Errorf("%v", err)
}
amid := amcr.GetResponse().Map["id"].(string)
// auto set to active-public state
_, err = amClient.ChangeState(ctx, amid, 0, "active-public", authmethods.WithAutomaticVersioning(true))
if err != nil {
return diag.Errorf("%v", err)
}
// update scope when set to primary
if p, ok := d.GetOk(authmethodOidcIsPrimaryAuthMethodForScope); ok {
if p.(bool) {
if err := updateScopeWithPrimaryAuthMethodId(ctx, scopeId, amid, meta); err != nil {
return diag.Errorf("%v", err)
}
amcr.GetResponse().Map[authmethodOidcIsPrimaryAuthMethodForScope] = true
}
}
return setFromOidcAuthMethodResponseMap(d, amcr.GetResponse().Map)
}
func updateScopeWithPrimaryAuthMethodId(ctx context.Context, scopeId, authmethodId string, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
scp := scopes.NewClient(md.client)
opts := []scopes.Option{}
opts = append(opts, scopes.WithAutomaticVersioning(true))
opts = append(opts, scopes.WithPrimaryAuthMethodId(authmethodId))
_, err := scp.Update(ctx, scopeId, 0, opts...)
if err != nil {
return diag.Errorf("error updating scope: %v", err)
}
return nil
}
func readScopeIsPrimaryAuthMethodId(ctx context.Context, scopeId, authmethodId string, meta interface{}) (diag.Diagnostics, bool) {
md := meta.(*metaData)
scp := scopes.NewClient(md.client)
srr, err := scp.Read(ctx, scopeId)
if err != nil {
return diag.Errorf("%s", err), false
}
if p, ok := srr.GetResponse().Map["primary_auth_method_id"]; ok {
if p == authmethodId {
return nil, true
}
}
return nil, false
}
func resourceAuthMethodOidcRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
amClient := authmethods.NewClient(md.client)
amrr, err := amClient.Read(ctx, d.Id())
if err != nil {
if apiErr := api.AsServerError(err); apiErr.Response().StatusCode() == http.StatusNotFound {
d.SetId("")
return nil
}
return diag.Errorf("error reading auth method: %v", err)
}
if amrr == nil {
return diag.Errorf("auth method nil after read")
}
serr, isPrimary := readScopeIsPrimaryAuthMethodId(ctx, amrr.GetResponse().Map["scope_id"].(string), amrr.GetResponse().Map["id"].(string), meta)
if err != nil {
return diag.Errorf("%v", serr)
}
if isPrimary {
amrr.GetResponse().Map[authmethodOidcIsPrimaryAuthMethodForScope] = true
}
return setFromOidcAuthMethodResponseMap(d, amrr.GetResponse().Map)
}
func resourceAuthMethodOidcUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
amClient := authmethods.NewClient(md.client)
opts := []authmethods.Option{}
if d.HasChange(NameKey) {
opts = append(opts, authmethods.DefaultName())
nameVal, ok := d.GetOk(NameKey)
if ok {
opts = append(opts, authmethods.WithName(nameVal.(string)))
}
}
if d.HasChange(DescriptionKey) {
opts = append(opts, authmethods.DefaultDescription())
descVal, ok := d.GetOk(DescriptionKey)
if ok {
opts = append(opts, authmethods.WithDescription(descVal.(string)))
}
}
if d.HasChange(authmethodOidcIssuerKey) {
if issuer, ok := d.GetOk(authmethodOidcIssuerKey); ok {
opts = append(opts, authmethods.WithOidcAuthMethodIssuer(issuer.(string)))
}
}
if d.HasChange(authmethodOidcClientIdKey) {
if clientId, ok := d.GetOk(authmethodOidcClientIdKey); ok {
opts = append(opts, authmethods.WithOidcAuthMethodClientId(clientId.(string)))
}
}
if d.HasChange(authmethodOidcClientSecretKey) {
if clientSecret, ok := d.GetOk(authmethodOidcClientSecretKey); ok {
opts = append(opts, authmethods.WithOidcAuthMethodClientSecret(clientSecret.(string)))
}
}
if d.HasChange(authmethodOidcMaxAgeKey) {
if maxAge, ok := d.GetOk(authmethodOidcMaxAgeKey); ok {
opts = append(opts, authmethods.WithOidcAuthMethodMaxAge(uint32(maxAge.(int))))
}
}
if d.HasChange(authmethodOidcSigningAlgorithmsKey) {
if algos, ok := d.GetOk(authmethodOidcSigningAlgorithmsKey); ok {
var signingAlgs []string
for _, alg := range algos.([]interface{}) {
signingAlgs = append(signingAlgs, alg.(string))
}
opts = append(opts, authmethods.WithOidcAuthMethodSigningAlgorithms(signingAlgs))
}
}
if d.HasChange(authmethodOidcApiUrlPrefixKey) {
if prefix, ok := d.GetOk(authmethodOidcApiUrlPrefixKey); ok {
opts = append(opts, authmethods.WithOidcAuthMethodApiUrlPrefix(prefix.(string)))
}
}
if d.HasChange(authmethodOidcClientSecretHmacKey) {
if sec, ok := d.GetOk(authmethodOidcClientSecretHmacKey); ok {
opts = append(opts, authmethods.WithOidcAuthMethodClientSecret(sec.(string)))
}
}
if d.HasChange(authmethodOidcAllowedAudiencesKey) {
if val, ok := d.GetOk(authmethodOidcAllowedAudiencesKey); ok {
var audiences []string
for _, aud := range val.([]interface{}) {
audiences = append(audiences, aud.(string))
}
opts = append(opts, authmethods.WithOidcAuthMethodAllowedAudiences(audiences))
}
}
if d.HasChange(authmethodOidcIdpCaCertsKey) {
if val, ok := d.GetOk(authmethodOidcIdpCaCertsKey); ok {
certs := []string{}
for _, cert := range val.([]interface{}) {
certs = append(certs, cert.(string))
}
opts = append(opts, authmethods.WithOidcAuthMethodIdpCaCerts(certs))
}
}
if d.HasChange(authmethodOidcDisableDiscoveredConfigValidationKey) {
if val, ok := d.GetOk(authmethodOidcDisableDiscoveredConfigValidationKey); ok {
opts = append(opts, authmethods.WithOidcAuthMethodDisableDiscoveredConfigValidation(val.(bool)))
}
}
if d.HasChange(authmethodOidcClaimsScopesKey) {
if val, ok := d.GetOk(authmethodOidcClaimsScopesKey); ok {
claimsScopes := []string{}
for _, c := range val.([]interface{}) {
claimsScopes = append(claimsScopes, c.(string))
}
opts = append(opts, authmethods.WithOidcAuthMethodClaimsScopes(claimsScopes))
}
}
if len(opts) > 0 {
opts = append(opts, authmethods.WithAutomaticVersioning(true))
amur, err := amClient.Update(ctx, d.Id(), 0, opts...)
if err != nil {
return diag.Errorf("error updating auth method: %v", err)
}
if d.HasChange(authmethodOidcIsPrimaryAuthMethodForScope) {
if p, ok := d.GetOk(authmethodOidcIsPrimaryAuthMethodForScope); ok {
if p.(bool) {
if err := updateScopeWithPrimaryAuthMethodId(
ctx,
amur.GetResponse().Map["scope_id"].(string),
amur.GetResponse().Map["id"].(string),
meta); err != nil {
return diag.Errorf("%v", err)
}
amur.GetResponse().Map[authmethodOidcIsPrimaryAuthMethodForScope] = true
}
}
}
return setFromOidcAuthMethodResponseMap(d, amur.GetResponse().Map)
}
return nil
}
func resourceAuthMethodOidcDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
amClient := authmethods.NewClient(md.client)
_, err := amClient.Delete(ctx, d.Id())
if err != nil {
return diag.Errorf("error deleting auth method: %v", err)
}
return nil
}
@@ -1,286 +0,0 @@
package provider
import (
"context"
"fmt"
"net/http"
"strings"
"testing"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/authmethods"
"github.com/hashicorp/boundary/api/scopes"
"github.com/hashicorp/boundary/testing/controller"
"github.com/hashicorp/cap/oidc"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)
const (
fooAuthMethodOidcDesc = "test auth method oidc"
fooAuthMethodOidcDescUpdate = "test auth method oidc update"
fooAuthMethodOidcCaCerts = `-----BEGIN CERTIFICATE-----
MIIDsjCCApoCCQCslgm7fAu/VzANBgkqhkiG9w0BAQsFADCBmjELMAkGA1UEBhMC
VVMxCzAJBgNVBAgMAldBMRMwEQYDVQQHDApCZWxsaW5naGFtMRIwEAYDVQQKDAlI
YXNoaUNvcnAxETAPBgNVBAsMCEJvdW5kYXJ5MRswGQYDVQQDDBJib3VuZGFyeXBy
b2plY3QuaW8xJTAjBgkqhkiG9w0BCQEWFmptYWxuaWNrQGhhc2hpY29ycC5jb20w
HhcNMjEwNDA2MjMzNTIxWhcNMjYwNDA1MjMzNTIxWjCBmjELMAkGA1UEBhMCVVMx
CzAJBgNVBAgMAldBMRMwEQYDVQQHDApCZWxsaW5naGFtMRIwEAYDVQQKDAlIYXNo
aUNvcnAxETAPBgNVBAsMCEJvdW5kYXJ5MRswGQYDVQQDDBJib3VuZGFyeXByb2pl
Y3QuaW8xJTAjBgkqhkiG9w0BCQEWFmptYWxuaWNrQGhhc2hpY29ycC5jb20wggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUa/ZLwYhTQ2hGzlwy/sB9xY9h
dM4qzY8DUF3Sin/+J2j59q3/vXZA7PS+o1GhoG1nW3J51zgyNFOY/EKHdtxVBken
YTTG+JswzrcTxsMV7/sYDgCLq6W8dPMV72gPH/3dRi3/0KUHtA6rBOf0Shf0f6Sz
7VmgTWcNmLvXpHKOs4YkjOL/tyflTgNm5j1dOa53TtwtMyvCcpGrB7PGL8m5+E2U
qxOzQ9kWfA6zr4Gl5rIm+Us8Ez3n1yGwjwFBteexk1Fot8zWKhoy7pZ3ZjWRpjwL
hfGs5eJs4kERQVAGONt39ZIR6OzOFxAsvI9WrMvxAsdCK63RtF2k4r0X21yDAgMB
AAEwDQYJKoZIhvcNAQELBQADggEBAJZcl7Zxjya23IcOV8jZDdCHtqnbcg9TcQb+
kpX1uEKJMFJoNmNK1q//nJxG1YBn3G8t9XtO6Kc6egdGHXWnOsM37N9hbYPJ2kW1
WWAwqWkQbV3wb0cc6MuU1S9xivOqwM046ZIcjrWR4T4tEUSUfYc3I+Yd8APdapn8
vePgWnmi/aSsx9RxVOUrzmVhzgN7rQJZGwnYYnxl4cwy2jxpysmXzg/grfXCZs/V
Kkc7Y5Ph6vRQ+vPCeB7QUxHxjlr8aq+rYDIaSiZ+/4+qyme0ergfvZmMSU8A3NNS
tYIMds5s2lIqVwOoyzpBEOjWBhUThH+aZu1A5c7Cb7s1eLSRX70=
-----END CERTIFICATE-----`
)
var (
fooAuthMethodOidc = `
resource "boundary_auth_method_oidc" "foo" {
name = "test"
description = "%s"
scope_id = boundary_scope.org1.id
depends_on = [boundary_role.org1_admin]
issuer = "%s"
client_id = "foo_id"
client_secret = "foo_secret"
max_age = 10
api_url_prefix = "http://localhost:9200"
idp_ca_certs = [
<<EOT
%s
EOT
]
allowed_audiences = ["foo_aud"]
signing_algorithms = ["ES256"]
account_claim_maps = ["oid=sub"]
claims_scopes = ["profile"]
}`
fooAuthMethodOidcUpdate = `
resource "boundary_auth_method_oidc" "foo" {
name = "test"
description = "%s"
scope_id = boundary_scope.org1.id
is_primary_for_scope = true
depends_on = [boundary_role.org1_admin]
issuer = "https://test-update.com"
client_id = "foo_id_update"
client_secret = "foo_secret_update"
max_age = 1
api_url_prefix = "http://localhost:9200"
idp_ca_certs = [
<<EOT
%s
EOT
]
allowed_audiences = ["foo_aud_update"]
signing_algorithms = ["ES256"]
account_claim_maps = ["oid=sub"]
claims_scopes = ["profile"]
// we need to disable this validatin, since the updated issuer isn't discoverable
disable_discovered_config_validation = true
}`
)
func TestAccAuthMethodOidc(t *testing.T) {
tp := oidc.StartTestProvider(t)
tc := controller.NewTestController(t, tcConfig...)
defer tc.Shutdown()
url := tc.ApiAddrs()[0]
tpCert := strings.TrimSpace(tp.CACert())
createConfig := fmt.Sprintf(fooAuthMethodOidc, fooAuthMethodOidcDesc, tp.Addr(), tpCert)
updateConfig := fmt.Sprintf(fooAuthMethodOidcUpdate, fooAuthMethodOidcDescUpdate, fooAuthMethodOidcCaCerts)
var provider *schema.Provider
resource.Test(t, resource.TestCase{
ProviderFactories: providerFactories(&provider),
CheckDestroy: testAccCheckAuthMethodOidcResourceDestroy(t, provider),
Steps: []resource.TestStep{
{
// create
Config: testConfig(url, fooOrg, createConfig),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", "description", fooAuthMethodOidcDesc),
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", "name", "test"),
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", authmethodOidcIssuerKey, tp.Addr()),
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", authmethodOidcClientIdKey, "foo_id"),
testAccCheckAuthMethodOidcAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcIdpCaCertsKey, []string{tpCert}),
testAccCheckAuthMethodOidcAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcAllowedAudiencesKey, []string{"foo_aud"}),
testAccCheckAuthMethodOidcAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcSigningAlgorithmsKey, []string{"ES256"}),
testAccCheckAuthMethodOidcAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcAccountClaimMapsKey, []string{"oid=sub"}),
testAccCheckAuthMethodOidcAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcClaimsScopesKey, []string{"profile"}),
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", authmethodOidcMaxAgeKey, "10"),
testAccCheckAuthMethodOidcResourceExists(provider, "boundary_auth_method_oidc.foo"),
testAccIsPrimaryForScope(provider, "boundary_auth_method_oidc.foo", false),
),
},
importStep("boundary_auth_method_oidc.foo", "client_secret", "is_primary_for_scope"),
{
// update
Config: testConfig(url, fooOrg, updateConfig),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", "description", fooAuthMethodOidcDescUpdate),
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", "name", "test"),
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", authmethodOidcIssuerKey, "https://test-update.com"),
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", authmethodOidcClientIdKey, "foo_id_update"),
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", authmethodOidcMaxAgeKey, "1"),
testAccCheckAuthMethodOidcAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcIdpCaCertsKey, []string{fooAuthMethodOidcCaCerts}),
testAccCheckAuthMethodOidcAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcAllowedAudiencesKey, []string{"foo_aud_update"}),
testAccCheckAuthMethodOidcResourceExists(provider, "boundary_auth_method_oidc.foo"),
testAccIsPrimaryForScope(provider, "boundary_auth_method_oidc.foo", true),
testAccCheckAuthMethodOidcResourceExists(provider, "boundary_auth_method_oidc.foo"),
),
},
importStep("boundary_auth_method_oidc.foo", "client_secret", "is_primary_for_scope"),
},
})
}
func testAccCheckAuthMethodOidcResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Not found: %s", name)
}
id := rs.Primary.ID
if id == "" {
return fmt.Errorf("No ID is set")
}
md := testProvider.Meta().(*metaData)
amClient := authmethods.NewClient(md.client)
if _, err := amClient.Read(context.Background(), id); err != nil {
return fmt.Errorf("Got an error when reading auth method %q: %w", id, err)
}
return nil
}
}
func testAccCheckAuthMethodOidcResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
return func(s *terraform.State) error {
if testProvider.Meta() == nil {
t.Fatal("got nil provider metadata")
}
md := testProvider.Meta().(*metaData)
for _, rs := range s.RootModule().Resources {
switch rs.Type {
case "boundary_auth_method_oidc":
id := rs.Primary.ID
amClient := authmethods.NewClient(md.client)
_, err := amClient.Read(context.Background(), id)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
return fmt.Errorf("didn't get a 404 when reading destroyed auth method %q: %v", id, err)
}
default:
continue
}
}
return nil
}
}
func testAccCheckAuthMethodOidcAttrAryValueSet(testProvider *schema.Provider, name string, key string, strAry []string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("auth method resource not found: %s", name)
}
id := rs.Primary.ID
if id == "" {
return fmt.Errorf("auth method resource ID is not set")
}
md := testProvider.Meta().(*metaData)
amClient := authmethods.NewClient(md.client)
amr, err := amClient.Read(context.Background(), id)
if err != nil {
return fmt.Errorf("Got an error when reading auth method %q: %v", id, err)
}
for _, got := range amr.Item.Attributes[key].([]interface{}) {
ok := false
for _, expected := range strAry {
if strings.TrimSpace(got.(string)) == strings.TrimSpace(expected) {
ok = true
}
}
if !ok {
return fmt.Errorf("value not found in boundary\n %s: %s\n", key, got.(string))
}
}
return nil
}
}
func testAccIsPrimaryForScope(tp *schema.Provider, name string, shouldBePrimary bool) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("auth method resource not found: %s", name)
}
amId := rs.Primary.ID
if amId == "" {
return fmt.Errorf("auth method resource ID is not set")
}
md := tp.Meta().(*metaData)
amClient := authmethods.NewClient(md.client)
amr, err := amClient.Read(context.Background(), amId)
if err != nil {
return fmt.Errorf("Got an error when reading auth method %q: %v", amId, err)
}
amScopeId, ok := amr.GetResponse().Map["scope_id"]
if !ok {
return fmt.Errorf("scope_id unset on auth method resource")
}
scp := scopes.NewClient(md.client)
srr, err := scp.Read(context.Background(), amScopeId.(string))
if err != nil {
return err
}
primaryScopeAuthMethodId, ok := srr.GetResponse().Map["primary_auth_method_id"]
if !ok && shouldBePrimary {
return fmt.Errorf("primary_auth_method_id is not set on scope resource response")
}
if shouldBePrimary {
if primaryScopeAuthMethodId != amId {
return fmt.Errorf("auth method ('%s') should be primary for scope but scope returned '%s' for primary_auth_method_id", amId, primaryScopeAuthMethodId)
}
}
return nil
}
}
@@ -1,240 +0,0 @@
package provider
import (
"context"
"encoding/json"
"net/http"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/authmethods"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
const (
authmethodTypePassword = "password"
authmethodMinLoginNameLengthKey = "min_login_name_length"
authmethodMinPasswordLengthKey = "min_password_length"
)
func resourceAuthMethodPassword() *schema.Resource {
return &schema.Resource{
Description: "The auth method resource allows you to configure a Boundary auth_method_password.",
CreateContext: resourceAuthMethodPasswordCreate,
ReadContext: resourceAuthMethodPasswordRead,
UpdateContext: resourceAuthMethodPasswordUpdate,
DeleteContext: resourceAuthMethodPasswordDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the account.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The auth method name. Defaults to the resource name.",
Type: schema.TypeString,
Optional: true,
},
DescriptionKey: {
Description: "The auth method description.",
Type: schema.TypeString,
Optional: true,
},
ScopeIdKey: {
Description: "The scope ID.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
TypeKey: {
Description: "The resource type, hardcoded per resource",
Type: schema.TypeString,
Default: authmethodTypePassword,
Optional: true,
},
authmethodMinLoginNameLengthKey: {
Description: "The minimum login name length.",
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
authmethodMinPasswordLengthKey: {
Description: "The minimum password length.",
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
},
}
}
func setFromPasswordAuthMethodResponseMap(d *schema.ResourceData, raw map[string]interface{}) diag.Diagnostics {
d.Set(NameKey, raw[NameKey])
d.Set(DescriptionKey, raw[DescriptionKey])
d.Set(ScopeIdKey, raw[ScopeIdKey])
d.Set(TypeKey, raw[TypeKey])
if attrsVal, ok := raw["attributes"]; ok {
attrs := attrsVal.(map[string]interface{})
minLoginNameLength := attrs[authmethodMinLoginNameLengthKey].(json.Number)
minLoginNameLengthInt, _ := minLoginNameLength.Int64()
d.Set(authmethodMinLoginNameLengthKey, int(minLoginNameLengthInt))
minPasswordLength := attrs[authmethodMinPasswordLengthKey].(json.Number)
minPasswordLengthInt, _ := minPasswordLength.Int64()
d.Set(authmethodMinPasswordLengthKey, int(minPasswordLengthInt))
}
d.SetId(raw["id"].(string))
return nil
}
func resourceAuthMethodPasswordCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
var typeStr string
if typeVal, ok := d.GetOk(TypeKey); ok {
typeStr = typeVal.(string)
} else {
return diag.Errorf("no type provided")
}
opts := []authmethods.Option{}
var minLoginNameLength *int
if minLengthVal, ok := d.GetOk(authmethodMinLoginNameLengthKey); ok {
minLength := minLengthVal.(int)
minLoginNameLength = &minLength
}
if minLoginNameLength != nil {
opts = append(opts, authmethods.WithPasswordAuthMethodMinLoginNameLength(uint32(*minLoginNameLength)))
}
var minPasswordLength *int
if minLengthVal, ok := d.GetOk(authmethodMinPasswordLengthKey); ok {
minLength := minLengthVal.(int)
minPasswordLength = &minLength
}
if minPasswordLength != nil {
opts = append(opts, authmethods.WithPasswordAuthMethodMinPasswordLength(uint32(*minPasswordLength)))
}
nameVal, ok := d.GetOk(NameKey)
if ok {
nameStr := nameVal.(string)
opts = append(opts, authmethods.WithName(nameStr))
}
descVal, ok := d.GetOk(DescriptionKey)
if ok {
descStr := descVal.(string)
opts = append(opts, authmethods.WithDescription(descStr))
}
var scopeId string
if scopeIdVal, ok := d.GetOk(ScopeIdKey); ok {
scopeId = scopeIdVal.(string)
} else {
return diag.Errorf("no scope ID provided")
}
amClient := authmethods.NewClient(md.client)
amcr, err := amClient.Create(ctx, typeStr, scopeId, opts...)
if err != nil {
return diag.Errorf("error creating auth method: %v", err)
}
if amcr == nil {
return diag.Errorf("nil auth method after create")
}
return setFromPasswordAuthMethodResponseMap(d, amcr.GetResponse().Map)
}
func resourceAuthMethodPasswordRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
amClient := authmethods.NewClient(md.client)
amrr, err := amClient.Read(ctx, d.Id())
if err != nil {
if apiErr := api.AsServerError(err); apiErr.Response().StatusCode() == http.StatusNotFound {
d.SetId("")
return nil
}
return diag.Errorf("error reading auth method: %v", err)
}
if amrr == nil {
return diag.Errorf("auth method nil after read")
}
return setFromPasswordAuthMethodResponseMap(d, amrr.GetResponse().Map)
}
func resourceAuthMethodPasswordUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
amClient := authmethods.NewClient(md.client)
opts := []authmethods.Option{}
if d.HasChange(NameKey) {
opts = append(opts, authmethods.DefaultName())
nameVal, ok := d.GetOk(NameKey)
if ok {
opts = append(opts, authmethods.WithName(nameVal.(string)))
}
}
if d.HasChange(DescriptionKey) {
opts = append(opts, authmethods.DefaultDescription())
descVal, ok := d.GetOk(DescriptionKey)
if ok {
opts = append(opts, authmethods.WithDescription(descVal.(string)))
}
}
if d.HasChange(authmethodMinLoginNameLengthKey) {
opts = append(opts, authmethods.DefaultPasswordAuthMethodMinLoginNameLength())
minLengthVal, ok := d.GetOk(authmethodMinLoginNameLengthKey)
if ok {
opts = append(opts, authmethods.WithPasswordAuthMethodMinLoginNameLength(uint32(minLengthVal.(int))))
}
}
if d.HasChange(authmethodMinPasswordLengthKey) {
opts = append(opts, authmethods.DefaultPasswordAuthMethodMinPasswordLength())
minLengthVal, ok := d.GetOk(authmethodMinPasswordLengthKey)
if ok {
opts = append(opts, authmethods.WithPasswordAuthMethodMinPasswordLength(uint32(minLengthVal.(int))))
}
}
if len(opts) > 0 {
opts = append(opts, authmethods.WithAutomaticVersioning(true))
amur, err := amClient.Update(ctx, d.Id(), 0, opts...)
if err != nil {
return diag.Errorf("error updating auth method: %v", err)
}
return setFromPasswordAuthMethodResponseMap(d, amur.GetResponse().Map)
}
return nil
}
func resourceAuthMethodPasswordDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
amClient := authmethods.NewClient(md.client)
_, err := amClient.Delete(ctx, d.Id())
if err != nil {
return diag.Errorf("error deleting auth method: %v", err)
}
return nil
}
@@ -1,127 +0,0 @@
package provider
import (
"context"
"fmt"
"net/http"
"testing"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/authmethods"
"github.com/hashicorp/boundary/testing/controller"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)
const (
fooAuthMethodDesc = "test auth method"
fooAuthMethodDescUpdate = "test auth method update"
)
var (
fooAuthMethod = fmt.Sprintf(`
resource "boundary_auth_method_password" "foo" {
name = "test"
description = "%s"
type = "password"
scope_id = boundary_scope.org1.id
depends_on = [boundary_role.org1_admin]
}`, fooAuthMethodDesc)
fooAuthMethodUpdate = fmt.Sprintf(`
resource "boundary_auth_method_password" "foo" {
name = "test"
description = "%s"
type = "password"
scope_id = boundary_scope.org1.id
depends_on = [boundary_role.org1_admin]
}`, fooAuthMethodDescUpdate)
)
func TestAccAuthMethodPassword(t *testing.T) {
tc := controller.NewTestController(t, tcConfig...)
defer tc.Shutdown()
url := tc.ApiAddrs()[0]
var provider *schema.Provider
resource.Test(t, resource.TestCase{
ProviderFactories: providerFactories(&provider),
CheckDestroy: testAccCheckAuthMethodResourceDestroy(t, provider),
Steps: []resource.TestStep{
{
// create
Config: testConfig(url, fooOrg, fooAuthMethod),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("boundary_auth_method_password.foo", "description", fooAuthMethodDesc),
resource.TestCheckResourceAttr("boundary_auth_method_password.foo", "name", "test"),
resource.TestCheckResourceAttr("boundary_auth_method_password.foo", "type", "password"),
testAccCheckAuthMethodResourceExists(provider, "boundary_auth_method_password.foo"),
),
},
importStep("boundary_auth_method_password.foo"),
{
// update
Config: testConfig(url, fooOrg, fooAuthMethodUpdate),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("boundary_auth_method_password.foo", "description", fooAuthMethodDescUpdate),
resource.TestCheckResourceAttr("boundary_auth_method_password.foo", "name", "test"),
resource.TestCheckResourceAttr("boundary_auth_method_password.foo", "type", "password"),
testAccCheckAuthMethodResourceExists(provider, "boundary_auth_method_password.foo"),
),
},
importStep("boundary_auth_method_password.foo"),
},
})
}
func testAccCheckAuthMethodResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Not found: %s", name)
}
id := rs.Primary.ID
if id == "" {
return fmt.Errorf("No ID is set")
}
md := testProvider.Meta().(*metaData)
amClient := authmethods.NewClient(md.client)
if _, err := amClient.Read(context.Background(), id); err != nil {
return fmt.Errorf("Got an error when reading auth method %q: %w", id, err)
}
return nil
}
}
func testAccCheckAuthMethodResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
return func(s *terraform.State) error {
if testProvider.Meta() == nil {
t.Fatal("got nil provider metadata")
}
md := testProvider.Meta().(*metaData)
for _, rs := range s.RootModule().Resources {
switch rs.Type {
case "boundary_auth_method_password":
id := rs.Primary.ID
amClient := authmethods.NewClient(md.client)
_, err := amClient.Read(context.Background(), id)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
return fmt.Errorf("didn't get a 404 when reading destroyed auth method %q: %v", id, err)
}
default:
continue
}
}
return nil
}
}
+20 -21
View File
@@ -15,33 +15,31 @@ import (
)
const (
fooBaseAuthMethodDesc = "test auth method"
fooBaseAuthMethodDescUpdate = "test auth method update"
fooAuthMethodDesc = "test auth method"
fooAuthMethodDescUpdate = "test auth method update"
)
// TODO(malnick) - remove these tests after deprecating use of password params in
// favor of attributes map
var (
fooBaseAuthMethod = fmt.Sprintf(`
fooAuthMethod = fmt.Sprintf(`
resource "boundary_auth_method" "foo" {
name = "test"
description = "%s"
type = "password"
scope_id = boundary_scope.org1.id
depends_on = [boundary_role.org1_admin]
}`, fooBaseAuthMethodDesc)
}`, fooAuthMethodDesc)
fooBaseAuthMethodUpdate = fmt.Sprintf(`
fooAuthMethodUpdate = fmt.Sprintf(`
resource "boundary_auth_method" "foo" {
name = "test"
description = "%s"
type = "password"
scope_id = boundary_scope.org1.id
depends_on = [boundary_role.org1_admin]
}`, fooBaseAuthMethodDescUpdate)
}`, fooAuthMethodDescUpdate)
)
func TestAccBaseAuthMethodPassword(t *testing.T) {
func TestAccAuthMethod(t *testing.T) {
tc := controller.NewTestController(t, tcConfig...)
defer tc.Shutdown()
url := tc.ApiAddrs()[0]
@@ -52,25 +50,24 @@ func TestAccBaseAuthMethodPassword(t *testing.T) {
CheckDestroy: testAccCheckAuthMethodResourceDestroy(t, provider),
Steps: []resource.TestStep{
{
// create
Config: testConfig(url, fooOrg, fooBaseAuthMethod),
//create
Config: testConfig(url, fooOrg, fooAuthMethod),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("boundary_auth_method.foo", "description", fooBaseAuthMethodDesc),
resource.TestCheckResourceAttr("boundary_auth_method.foo", "description", fooAuthMethodDesc),
resource.TestCheckResourceAttr("boundary_auth_method.foo", "name", "test"),
resource.TestCheckResourceAttr("boundary_auth_method.foo", "type", "password"),
testAccCheckBaseAuthMethodResourceExists(provider, "boundary_auth_method.foo"),
testAccCheckAuthMethodResourceExists(provider, "boundary_auth_method.foo"),
),
},
importStep("boundary_auth_method.foo"),
{
// update
Config: testConfig(url, fooOrg, fooBaseAuthMethodUpdate),
Config: testConfig(url, fooOrg, fooAuthMethodUpdate),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("boundary_auth_method.foo", "description", fooBaseAuthMethodDescUpdate),
resource.TestCheckResourceAttr("boundary_auth_method.foo", "description", fooAuthMethodDescUpdate),
resource.TestCheckResourceAttr("boundary_auth_method.foo", "name", "test"),
resource.TestCheckResourceAttr("boundary_auth_method.foo", "type", "password"),
testAccCheckBaseAuthMethodResourceExists(provider, "boundary_auth_method.foo"),
testAccCheckAuthMethodResourceExists(provider, "boundary_auth_method.foo"),
),
},
importStep("boundary_auth_method.foo"),
@@ -78,7 +75,7 @@ func TestAccBaseAuthMethodPassword(t *testing.T) {
})
}
func testAccCheckBaseAuthMethodResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
func testAccCheckAuthMethodResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
@@ -102,7 +99,7 @@ func testAccCheckBaseAuthMethodResourceExists(testProvider *schema.Provider, nam
}
}
func testAccCheckBaseAuthMethodResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
func testAccCheckAuthMethodResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
return func(s *terraform.State) error {
if testProvider.Meta() == nil {
t.Fatal("got nil provider metadata")
@@ -111,14 +108,16 @@ func testAccCheckBaseAuthMethodResourceDestroy(t *testing.T, testProvider *schem
for _, rs := range s.RootModule().Resources {
switch rs.Type {
case "boundary_scope":
continue
case "boundary_auth_method":
id := rs.Primary.ID
amClient := authmethods.NewClient(md.client)
_, err := amClient.Read(context.Background(), id)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
return fmt.Errorf("didn't get a 404 when reading destroyed auth method %q: %v", id, err)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Status != http.StatusNotFound {
return fmt.Errorf("Didn't get a 404 when reading destroyed auth method %q: %v", id, err)
}
default:
@@ -1,237 +0,0 @@
package provider
import (
"context"
"net/http"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/credentiallibraries"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
const (
credentialStoreIdKey = "credential_store_id"
credentialLibraryVaultHttpMethodKey = "http_method"
credentialLibraryVaultHttpRequestBodyKey = "http_request_body"
credentialLibraryVaultPathKey = "path"
)
var libraryVaultAttrs = []string{
credentialLibraryVaultHttpMethodKey,
credentialLibraryVaultHttpRequestBodyKey,
credentialLibraryVaultPathKey,
}
func resourceCredentialLibraryVault() *schema.Resource {
return &schema.Resource{
Description: "The credential library for Vault resource allows you to configure a Boundary credential library for Vault.",
CreateContext: resourceCredentialLibraryCreateVault,
ReadContext: resourceCredentialLibraryReadVault,
UpdateContext: resourceCredentialLibraryUpdateVault,
DeleteContext: resourceCredentialLibraryDeleteVault,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the Vault credential library.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The Vault credential library name. Defaults to the resource name.",
Type: schema.TypeString,
Optional: true,
},
DescriptionKey: {
Description: "The Vault credential library description.",
Type: schema.TypeString,
Optional: true,
},
credentialStoreIdKey: {
Description: "The ID of the credential store that this library belongs to.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
credentialLibraryVaultHttpMethodKey: {
Description: "The HTTP method the library uses when requesting credentials from Vault. Defaults to 'GET'",
Type: schema.TypeString,
Optional: true,
},
credentialLibraryVaultHttpRequestBodyKey: {
Description: "The body of the HTTP request the library sends to Vault when requesting credentials. Only valid if `http_method` is set to `POST`.",
Type: schema.TypeString,
Optional: true,
},
credentialLibraryVaultPathKey: {
Description: "The path in Vault to request credentials from.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func setFromVaultCredentialLibraryResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
if err := d.Set(NameKey, raw[NameKey]); err != nil {
return err
}
if err := d.Set(DescriptionKey, raw[DescriptionKey]); err != nil {
return err
}
if err := d.Set(credentialStoreIdKey, raw[credentialStoreIdKey]); err != nil {
return err
}
if attrsVal, ok := raw["attributes"]; ok {
attrs := attrsVal.(map[string]interface{})
for _, v := range libraryVaultAttrs {
if err := d.Set(v, attrs[v]); err != nil {
return err
}
}
}
d.SetId(raw["id"].(string))
return nil
}
func resourceCredentialLibraryCreateVault(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
var opts []credentiallibraries.Option
if v, ok := d.GetOk(NameKey); ok {
opts = append(opts, credentiallibraries.WithName(v.(string)))
}
if v, ok := d.GetOk(DescriptionKey); ok {
opts = append(opts, credentiallibraries.WithDescription(v.(string)))
}
if v, ok := d.GetOk(credentialLibraryVaultHttpMethodKey); ok {
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryHttpMethod(v.(string)))
}
if v, ok := d.GetOk(credentialLibraryVaultHttpRequestBodyKey); ok {
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryHttpRequestBody(v.(string)))
}
if v, ok := d.GetOk(credentialLibraryVaultPathKey); ok {
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryPath(v.(string)))
}
var credentialStoreId string
cid, ok := d.GetOk(credentialStoreIdKey)
if ok {
credentialStoreId = cid.(string)
} else {
return diag.Errorf("no credential store ID is set")
}
client := credentiallibraries.NewClient(md.client)
cr, err := client.Create(ctx, credentialStoreId, opts...)
if err != nil {
return diag.Errorf("error creating credential library: %v", err)
}
if cr == nil {
return diag.Errorf("nil credential library after create")
}
if err := setFromVaultCredentialLibraryResponseMap(d, cr.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
return nil
}
func resourceCredentialLibraryReadVault(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
client := credentiallibraries.NewClient(md.client)
cr, err := client.Read(ctx, d.Id())
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
d.SetId("")
return nil
}
return diag.Errorf("error reading credential library: %v", err)
}
if cr == nil {
return diag.Errorf("credential library nil after read")
}
if err := setFromVaultCredentialLibraryResponseMap(d, cr.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
return nil
}
func resourceCredentialLibraryUpdateVault(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
client := credentiallibraries.NewClient(md.client)
var opts []credentiallibraries.Option
if d.HasChange(NameKey) {
opts = append(opts, credentiallibraries.DefaultName())
nameVal, ok := d.GetOk(NameKey)
if ok {
opts = append(opts, credentiallibraries.WithName(nameVal.(string)))
}
}
if d.HasChange(DescriptionKey) {
opts = append(opts, credentiallibraries.DefaultDescription())
descVal, ok := d.GetOk(DescriptionKey)
if ok {
opts = append(opts, credentiallibraries.WithDescription(descVal.(string)))
}
}
if d.HasChange(credentialLibraryVaultHttpMethodKey) {
opts = append(opts, credentiallibraries.DefaultVaultCredentialLibraryHttpMethod())
v, ok := d.GetOk(credentialLibraryVaultHttpMethodKey)
if ok {
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryHttpMethod(v.(string)))
}
}
if d.HasChange(credentialLibraryVaultHttpRequestBodyKey) {
opts = append(opts, credentiallibraries.DefaultVaultCredentialLibraryHttpRequestBody())
v, ok := d.GetOk(credentialLibraryVaultHttpRequestBodyKey)
if ok {
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryHttpRequestBody(v.(string)))
}
}
if d.HasChange(credentialLibraryVaultPathKey) {
v, ok := d.GetOk(credentialLibraryVaultPathKey)
if ok {
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryPath(v.(string)))
}
}
if len(opts) > 0 {
opts = append(opts, credentiallibraries.WithAutomaticVersioning(true))
aur, err := client.Update(ctx, d.Id(), 0, opts...)
if err != nil {
return diag.Errorf("error updating credential library: %v", err)
}
if err := setFromVaultCredentialLibraryResponseMap(d, aur.GetResponse().Map); err != nil {
return diag.Errorf("error setting credential library from response: %v", err)
}
}
return nil
}
func resourceCredentialLibraryDeleteVault(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
client := credentiallibraries.NewClient(md.client)
_, err := client.Delete(ctx, d.Id())
if err != nil {
return diag.Errorf("error deleting credential library: %v", err)
}
return nil
}
@@ -1,154 +0,0 @@
package provider
import (
"context"
"fmt"
"net/http"
"testing"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/credentiallibraries"
"github.com/hashicorp/boundary/testing/controller"
"github.com/hashicorp/boundary/testing/vault"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)
const (
vaultCredResc = "boundary_credential_library_vault.example"
vaultCredLibName = "foo"
vaultCredLibDesc = "the foo"
vaultCredLibPath = "/foo/bar"
vaultCredLibMethodGet = "GET"
vaultCredLibMethodPost = "POST"
vaultCredLibRequestBody = "foobar"
vaultCredLibStringUpdate = "_random"
)
var vaultCredLibResource = fmt.Sprintf(`
resource "boundary_credential_library_vault" "example" {
name = "%s"
description = "%s"
credential_store_id = boundary_credential_store_vault.example.id
path = "%s"
http_method = "%s"
}`, vaultCredLibName,
vaultCredLibDesc,
vaultCredLibPath,
vaultCredLibMethodGet)
var vaultCredLibResourceUpdate = fmt.Sprintf(`
resource "boundary_credential_library_vault" "example" {
name = "%s"
description = "%s"
credential_store_id = boundary_credential_store_vault.example.id
path = "%s"
http_method = "%s"
http_request_body = "%s"
}`, vaultCredLibName+vaultCredLibStringUpdate,
vaultCredLibDesc+vaultCredLibStringUpdate,
vaultCredLibPath+vaultCredLibStringUpdate,
vaultCredLibMethodPost,
vaultCredLibRequestBody)
func TestAccCredentialLibraryVault(t *testing.T) {
tc := controller.NewTestController(t, tcConfig...)
defer tc.Shutdown()
url := tc.ApiAddrs()[0]
vc := vault.NewTestVaultServer(t)
_, token := vc.CreateToken(t)
credStoreRes := vaultCredStoreResource(vc,
vaultCredStoreName,
vaultCredStoreDesc,
vaultCredStoreNamespace,
"www.original.com",
token,
true)
var provider *schema.Provider
resource.Test(t, resource.TestCase{
ProviderFactories: providerFactories(&provider),
CheckDestroy: testAccCheckAuthMethodResourceDestroy(t, provider),
Steps: []resource.TestStep{
{
// create
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, vaultCredLibResource),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(vaultCredResc, NameKey, vaultCredLibName),
resource.TestCheckResourceAttr(vaultCredResc, DescriptionKey, vaultCredLibDesc),
resource.TestCheckResourceAttr(vaultCredResc, credentialLibraryVaultPathKey, vaultCredLibPath),
resource.TestCheckResourceAttr(vaultCredResc, credentialLibraryVaultHttpMethodKey, vaultCredLibMethodGet),
resource.TestCheckResourceAttr(vaultCredResc, credentialLibraryVaultHttpRequestBodyKey, ""),
testAccCheckCredentialLibraryVaultResourceExists(provider, vaultCredResc),
),
},
importStep(vaultCredResc),
{
// update
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, vaultCredLibResourceUpdate),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(vaultCredResc, NameKey, vaultCredLibName+vaultCredLibStringUpdate),
resource.TestCheckResourceAttr(vaultCredResc, DescriptionKey, vaultCredLibDesc+vaultCredLibStringUpdate),
resource.TestCheckResourceAttr(vaultCredResc, credentialLibraryVaultPathKey, vaultCredLibPath+vaultCredLibStringUpdate),
resource.TestCheckResourceAttr(vaultCredResc, credentialLibraryVaultHttpMethodKey, vaultCredLibMethodPost),
resource.TestCheckResourceAttr(vaultCredResc, credentialLibraryVaultHttpRequestBodyKey, vaultCredLibRequestBody),
testAccCheckCredentialLibraryVaultResourceExists(provider, vaultCredResc),
),
},
importStep(vaultCredResc),
},
})
}
func testAccCheckCredentialLibraryVaultResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("not found: %s", name)
}
id := rs.Primary.ID
if id == "" {
return fmt.Errorf("no ID is set")
}
md := testProvider.Meta().(*metaData)
c := credentiallibraries.NewClient(md.client)
if _, err := c.Read(context.Background(), id); err != nil {
return fmt.Errorf("got an error reading %q: %w", id, err)
}
return nil
}
}
func testAccCheckCredentialLibraryVaultResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
return func(s *terraform.State) error {
if testProvider.Meta() == nil {
t.Fatal("got nil provider metadata")
}
md := testProvider.Meta().(*metaData)
for _, rs := range s.RootModule().Resources {
switch rs.Type {
case "boundary_credential_library_vault":
id := rs.Primary.ID
c := credentiallibraries.NewClient(md.client)
_, err := c.Read(context.Background(), id)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
return fmt.Errorf("didn't get a 404 when reading destroyed vault credential library %q: %v", id, err)
}
default:
continue
}
}
return nil
}
}
@@ -1,373 +0,0 @@
package provider
import (
"context"
"net/http"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/credentialstores"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
const (
credentialStoreVaultAddressKey = "address"
credentialStoreVaultNamespaceKey = "namespace"
credentialStoreVaultCaCertKey = "ca_cert"
credentialStoreVaultTlsServerNameKey = "tls_server_name"
credentialStoreVaultTlsSkipVerifyKey = "tls_skip_verify"
credentialStoreVaultTokenKey = "token"
credentialStoreVaultTokenHmacKey = "token_hmac"
credentialStoreVaultClientCertificateKey = "client_certificate"
credentialStoreVaultClientCertificateKeyKey = "client_certificate_key"
credentialStoreVaultClientCertificateKeyHmacKey = "client_certificate_key_hmac"
credentialStoreType = "vault"
)
var storeVaultAttrs = []string{
credentialStoreVaultAddressKey,
credentialStoreVaultNamespaceKey,
credentialStoreVaultCaCertKey,
credentialStoreVaultTlsServerNameKey,
credentialStoreVaultTlsSkipVerifyKey,
credentialStoreVaultClientCertificateKey,
}
func resourceCredentialStoreVault() *schema.Resource {
return &schema.Resource{
Description: "The credential store for Vault resource allows you to configure a Boundary credential store for Vault.",
CreateContext: resourceCredentialStoreVaultCreate,
ReadContext: resourceCredentialStoreVaultRead,
UpdateContext: resourceCredentialStoreVaultUpdate,
DeleteContext: resourceCredentialStoreVaultDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the Vault credential store.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The Vault credential store name. Defaults to the resource name.",
Type: schema.TypeString,
Optional: true,
},
DescriptionKey: {
Description: "The Vault credential store description.",
Type: schema.TypeString,
Optional: true,
},
ScopeIdKey: {
Description: "The scope for this credential store.",
Type: schema.TypeString,
ForceNew: true,
Required: true,
},
credentialStoreVaultAddressKey: {
Description: "The address to Vault server. This should be a complete URL such as 'https://127.0.0.1:8200'",
Type: schema.TypeString,
Required: true,
},
credentialStoreVaultNamespaceKey: {
Description: "The namespace within Vault to use.",
Type: schema.TypeString,
Optional: true,
},
credentialStoreVaultCaCertKey: {
Description: "A PEM-encoded CA certificate to verify the Vault server's TLS certificate.",
Type: schema.TypeString,
Optional: true,
},
credentialStoreVaultTlsServerNameKey: {
Description: "Name to use as the SNI host when connecting to Vault via TLS.",
Type: schema.TypeString,
Optional: true,
},
credentialStoreVaultTlsSkipVerifyKey: {
Description: "Whether or not to skip TLS verification.",
Type: schema.TypeBool,
Optional: true,
},
credentialStoreVaultTokenKey: {
Description: "A token used for accessing Vault.",
Type: schema.TypeString,
Required: true,
Sensitive: true,
},
credentialStoreVaultTokenHmacKey: {
Description: "The Vault token hmac.",
Type: schema.TypeString,
Computed: true,
},
credentialStoreVaultClientCertificateKey: {
Description: "A PEM-encoded client certificate to use for TLS authentication to the Vault server.",
Type: schema.TypeString,
Optional: true,
},
credentialStoreVaultClientCertificateKeyKey: {
Description: "A PEM-encoded private key matching the client certificate from 'client_certificate'.",
Type: schema.TypeString,
Optional: true,
Sensitive: true,
},
credentialStoreVaultClientCertificateKeyHmacKey: {
Description: "The Vault client certificate key hmac.",
Type: schema.TypeString,
Computed: true,
},
},
}
}
func setFromVaultCredentialStoreResponseMap(d *schema.ResourceData, raw map[string]interface{}, fromRead bool) error {
if err := d.Set(NameKey, raw[NameKey]); err != nil {
return err
}
if err := d.Set(DescriptionKey, raw[DescriptionKey]); err != nil {
return err
}
if err := d.Set(ScopeIdKey, raw[ScopeIdKey]); err != nil {
return err
}
if attrsVal, ok := raw["attributes"]; ok {
attrs := attrsVal.(map[string]interface{})
for _, v := range storeVaultAttrs {
if err := d.Set(v, attrs[v]); err != nil {
return err
}
}
stateTokenHmac := d.Get(credentialStoreVaultTokenHmacKey)
boundaryTokenHmac := attrs[credentialStoreVaultTokenHmacKey].(string)
if stateTokenHmac.(string) != boundaryTokenHmac && fromRead {
// TokenHmac has changed in Boundary, therefore the token has changed.
// Update token value to force tf to attempt update.
if err := d.Set(credentialStoreVaultTokenKey, "(changed in Boundary)"); err != nil {
return err
}
}
if err := d.Set(credentialStoreVaultTokenHmacKey, boundaryTokenHmac); err != nil {
return err
}
stateClientKeyHmac := d.Get(credentialStoreVaultClientCertificateKeyHmacKey)
var boundaryClientKeyHmac string
if v, ok := attrs[credentialStoreVaultClientCertificateKeyHmacKey]; ok {
boundaryClientKeyHmac = v.(string)
}
if stateClientKeyHmac.(string) != boundaryClientKeyHmac && fromRead {
// ClientKeyHmac has changed in Boundary, therefore the ClientKey has changed.
// Update ClientKey value to force tf to attempt update.
if err := d.Set(credentialStoreVaultClientCertificateKeyKey, "(changed in Boundary)"); err != nil {
return err
}
}
if err := d.Set(credentialStoreVaultClientCertificateKeyHmacKey, boundaryClientKeyHmac); err != nil {
return err
}
}
d.SetId(raw["id"].(string))
return nil
}
func resourceCredentialStoreVaultCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
var opts []credentialstores.Option
if v, ok := d.GetOk(NameKey); ok {
opts = append(opts, credentialstores.WithName(v.(string)))
}
if v, ok := d.GetOk(DescriptionKey); ok {
opts = append(opts, credentialstores.WithDescription(v.(string)))
}
if v, ok := d.GetOk(credentialStoreVaultAddressKey); ok {
opts = append(opts, credentialstores.WithVaultCredentialStoreAddress(v.(string)))
}
if v, ok := d.GetOk(credentialStoreVaultNamespaceKey); ok {
opts = append(opts, credentialstores.WithVaultCredentialStoreNamespace(v.(string)))
}
if v, ok := d.GetOk(credentialStoreVaultCaCertKey); ok {
opts = append(opts, credentialstores.WithVaultCredentialStoreCaCert(v.(string)))
}
if v, ok := d.GetOk(credentialStoreVaultTlsServerNameKey); ok {
opts = append(opts, credentialstores.WithVaultCredentialStoreTlsServerName(v.(string)))
}
if v, ok := d.GetOk(credentialStoreVaultTlsSkipVerifyKey); ok {
opts = append(opts, credentialstores.WithVaultCredentialStoreTlsSkipVerify(v.(bool)))
}
if v, ok := d.GetOk(credentialStoreVaultClientCertificateKey); ok {
opts = append(opts, credentialstores.WithVaultCredentialStoreClientCertificate(v.(string)))
}
if v, ok := d.GetOk(credentialStoreVaultClientCertificateKeyKey); ok {
opts = append(opts, credentialstores.WithVaultCredentialStoreClientCertificateKey(v.(string)))
}
if v, ok := d.GetOk(credentialStoreVaultTokenKey); ok {
opts = append(opts, credentialstores.WithVaultCredentialStoreToken(v.(string)))
}
var scope string
gotScope, ok := d.GetOk(ScopeIdKey)
if ok {
scope = gotScope.(string)
} else {
return diag.Errorf("no scope is set")
}
client := credentialstores.NewClient(md.client)
cr, err := client.Create(ctx, credentialStoreType, scope, opts...)
if err != nil {
return diag.Errorf("error creating credential store: %v", err)
}
if cr == nil {
return diag.Errorf("nil credential store after create")
}
if err := setFromVaultCredentialStoreResponseMap(d, cr.GetResponse().Map, false); err != nil {
return diag.Errorf("error generating credential store from response map: %v", err)
}
return nil
}
func resourceCredentialStoreVaultRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
client := credentialstores.NewClient(md.client)
cr, err := client.Read(ctx, d.Id())
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
d.SetId("")
return nil
}
return diag.Errorf("error reading credential store: %v", err)
}
if cr == nil {
return diag.Errorf("credential store nil after read")
}
if err := setFromVaultCredentialStoreResponseMap(d, cr.GetResponse().Map, true); err != nil {
return diag.Errorf("error generating credential store from response map: %v", err)
}
return nil
}
func resourceCredentialStoreVaultUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
client := credentialstores.NewClient(md.client)
var opts []credentialstores.Option
if d.HasChange(NameKey) {
opts = append(opts, credentialstores.DefaultName())
nameVal, ok := d.GetOk(NameKey)
if ok {
opts = append(opts, credentialstores.WithName(nameVal.(string)))
}
}
if d.HasChange(DescriptionKey) {
opts = append(opts, credentialstores.DefaultDescription())
descVal, ok := d.GetOk(DescriptionKey)
if ok {
opts = append(opts, credentialstores.WithDescription(descVal.(string)))
}
}
if d.HasChange(credentialStoreVaultAddressKey) {
v, ok := d.GetOk(credentialStoreVaultAddressKey)
if ok {
opts = append(opts, credentialstores.WithVaultCredentialStoreAddress(v.(string)))
}
}
if d.HasChange(credentialStoreVaultNamespaceKey) {
opts = append(opts, credentialstores.DefaultVaultCredentialStoreNamespace())
v, ok := d.GetOk(credentialStoreVaultNamespaceKey)
if ok {
opts = append(opts, credentialstores.WithVaultCredentialStoreNamespace(v.(string)))
}
}
if d.HasChange(credentialStoreVaultCaCertKey) {
opts = append(opts, credentialstores.DefaultVaultCredentialStoreCaCert())
v, ok := d.GetOk(credentialStoreVaultCaCertKey)
if ok {
opts = append(opts, credentialstores.WithVaultCredentialStoreCaCert(v.(string)))
}
}
if d.HasChange(credentialStoreVaultTlsServerNameKey) {
opts = append(opts, credentialstores.DefaultVaultCredentialStoreTlsServerName())
v, ok := d.GetOk(credentialStoreVaultTlsServerNameKey)
if ok {
opts = append(opts, credentialstores.WithVaultCredentialStoreTlsServerName(v.(string)))
}
}
if d.HasChange(credentialStoreVaultTlsSkipVerifyKey) {
opts = append(opts, credentialstores.DefaultVaultCredentialStoreTlsSkipVerify())
v, ok := d.GetOk(credentialStoreVaultTlsSkipVerifyKey)
if ok {
opts = append(opts, credentialstores.WithVaultCredentialStoreTlsSkipVerify(v.(bool)))
}
}
if d.HasChange(credentialStoreVaultTokenKey) {
v, ok := d.GetOk(credentialStoreVaultTokenKey)
if ok {
opts = append(opts, credentialstores.WithVaultCredentialStoreToken(v.(string)))
}
}
if d.HasChange(credentialStoreVaultClientCertificateKey) {
opts = append(opts, credentialstores.DefaultVaultCredentialStoreClientCertificate())
v, ok := d.GetOk(credentialStoreVaultClientCertificateKey)
if ok {
opts = append(opts, credentialstores.WithVaultCredentialStoreClientCertificate(v.(string)))
}
}
if d.HasChange(credentialStoreVaultClientCertificateKeyKey) {
opts = append(opts, credentialstores.DefaultVaultCredentialStoreClientCertificateKey())
v, ok := d.GetOk(credentialStoreVaultClientCertificateKeyKey)
if ok {
opts = append(opts, credentialstores.WithVaultCredentialStoreClientCertificateKey(v.(string)))
}
}
if len(opts) > 0 {
opts = append(opts, credentialstores.WithAutomaticVersioning(true))
crUpdate, err := client.Update(ctx, d.Id(), 0, opts...)
if err != nil {
return diag.Errorf("error updating credential store: %v", err)
}
if crUpdate == nil {
return diag.Errorf("credential store nil after update")
}
if err = setFromVaultCredentialStoreResponseMap(d, crUpdate.GetResponse().Map, false); err != nil {
return diag.Errorf("error generating credential store from response map: %v", err)
}
}
return nil
}
func resourceCredentialStoreVaultDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
client := credentialstores.NewClient(md.client)
_, err := client.Delete(ctx, d.Id())
if err != nil {
return diag.Errorf("error deleting credential store: %v", err)
}
return nil
}
@@ -1,240 +0,0 @@
package provider
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"net/http"
"strings"
"testing"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/credentialstores"
"github.com/hashicorp/boundary/testing/controller"
"github.com/hashicorp/boundary/testing/vault"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"golang.org/x/crypto/blake2b"
)
const (
vaultCredStoreResc = "boundary_credential_store_vault.example"
vaultCredStoreName = "foo"
vaultCredStoreDesc = "the foo"
vaultCredStoreNamespace = "default"
vaultCredStoreUpdate = "_random"
)
func vaultCredStoreResource(vc *vault.TestVaultServer, name, description, namespace, tlsServer, token string, skipVerify bool) string {
caCert := fmt.Sprintf("\"%s\"", strings.Replace(string(vc.CaCert), "\n", `\n`, -1))
clientCert := fmt.Sprintf("\"%s\"", strings.Replace(string(vc.ClientCert), "\n", `\n`, -1))
clientKey := fmt.Sprintf("\"%s\"", strings.Replace(string(vc.ClientKey), "\n", `\n`, -1))
return fmt.Sprintf(`
resource "boundary_credential_store_vault" "example" {
name = "%s"
description = "%s"
scope_id = boundary_scope.proj1.id
address = "%s"
namespace = "%s"
ca_cert = %s
tls_server_name = "%s"
tls_skip_verify = "%v"
token = "%s"
client_certificate = %s
client_certificate_key = %s
depends_on = [boundary_role.proj1_admin]
}`, name,
description,
vc.Addr,
namespace,
caCert,
tlsServer,
skipVerify,
token,
clientCert,
clientKey)
}
func tokenHmac(token, accessor string) string {
key := blake2b.Sum256([]byte(accessor))
mac := hmac.New(sha256.New, key[:])
_, _ = mac.Write([]byte(token))
hmac := mac.Sum(nil)
return base64.RawURLEncoding.EncodeToString(hmac)
}
func TestAccCredentialStoreVault(t *testing.T) {
tc := controller.NewTestController(t, tcConfig...)
defer tc.Shutdown()
url := tc.ApiAddrs()[0]
vc := vault.NewTestVaultServer(t)
secret, token := vc.CreateToken(t)
tHmac := tokenHmac(token, secret.Auth.Accessor)
res := vaultCredStoreResource(vc,
vaultCredStoreName,
vaultCredStoreDesc,
vaultCredStoreNamespace,
"www.original.com",
token,
true)
vcUpdate := vault.NewTestVaultServer(t, vault.WithTestVaultTLS(vault.TestClientTLS))
secret, tokenUpdate := vcUpdate.CreateToken(t)
tHmacUpdate := tokenHmac(tokenUpdate, secret.Auth.Accessor)
resUpdate := vaultCredStoreResource(vcUpdate,
vaultCredStoreName+vaultCredStoreUpdate,
vaultCredStoreDesc+vaultCredStoreUpdate,
vaultCredStoreNamespace+vaultCredStoreUpdate,
"www.updated.com",
tokenUpdate,
false)
var provider *schema.Provider
resource.Test(t, resource.TestCase{
ProviderFactories: providerFactories(&provider),
CheckDestroy: testAccCheckCredentialStoreVaultResourceDestroy(t, provider),
Steps: []resource.TestStep{
{
// create
Config: testConfig(url, fooOrg, firstProjectFoo, res),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(vaultCredStoreResc, NameKey, vaultCredStoreName),
resource.TestCheckResourceAttr(vaultCredStoreResc, DescriptionKey, vaultCredStoreDesc),
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultAddressKey, vc.Addr),
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultNamespaceKey, vaultCredStoreNamespace),
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultCaCertKey, ""),
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTlsServerNameKey, "www.original.com"),
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTlsSkipVerifyKey, "true"),
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTokenKey, token),
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTokenHmacKey, tHmac),
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultClientCertificateKey, ""),
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultClientCertificateKeyKey, ""),
testAccCheckCredentialStoreVaultResourceExists(provider, vaultCredStoreResc),
),
},
importStep(vaultCredStoreResc, credentialStoreVaultTokenKey, credentialStoreVaultClientCertificateKeyKey),
{
// update
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(vaultCredStoreResc, NameKey, vaultCredStoreName+vaultCredStoreUpdate),
resource.TestCheckResourceAttr(vaultCredStoreResc, DescriptionKey, vaultCredStoreDesc+vaultCredStoreUpdate),
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultAddressKey, vcUpdate.Addr),
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultNamespaceKey, vaultCredStoreNamespace+vaultCredStoreUpdate),
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultCaCertKey, string(vcUpdate.CaCert)),
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTlsServerNameKey, "www.updated.com"),
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTlsSkipVerifyKey, "false"),
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTokenKey, tokenUpdate),
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTokenHmacKey, tHmacUpdate),
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultClientCertificateKey, string(vcUpdate.ClientCert)),
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultClientCertificateKeyKey, string(vcUpdate.ClientKey)),
testAccCheckCredentialStoreVaultResourceExists(provider, vaultCredStoreResc),
),
},
importStep(vaultCredStoreResc, credentialStoreVaultTokenKey, credentialStoreVaultClientCertificateKeyKey),
{
// Run a plan only update and verify no changes
PlanOnly: true,
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
},
importStep(vaultCredStoreResc, credentialStoreVaultTokenKey, credentialStoreVaultClientCertificateKeyKey),
{
// update again but apply a preConfig to externally update resource
// TODO: Boundary currently causes an error on moving back to a previously
// used token, for now verify that a plan only step had changes
PreConfig: func() { externalUpdate(t, provider) },
PlanOnly: true,
ExpectNonEmptyPlan: true,
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
},
importStep(vaultCredStoreResc, credentialStoreVaultTokenKey, credentialStoreVaultClientCertificateKeyKey),
},
})
}
var storeId string
// externalUpdate uses the global storeId, therefore this function cannot be called until
// a previous test step that calls testAccCheckCredentialStoreVaultResourceExists has completed.
func externalUpdate(t *testing.T, testProvider *schema.Provider) {
if storeId == "" {
t.Fatal("storeId must be set before testing an external update")
}
vs := vault.NewTestVaultServer(t, vault.WithTestVaultTLS(vault.TestClientTLS))
_, token := vs.CreateToken(t)
md := testProvider.Meta().(*metaData)
c := credentialstores.NewClient(md.client)
cr, err := c.Read(context.Background(), storeId)
if err != nil {
t.Fatal(fmt.Errorf("got an error reading %q: %w", storeId, err))
}
// update Vault server to existing store
var opts []credentialstores.Option
opts = append(opts, credentialstores.WithVaultCredentialStoreToken(token))
opts = append(opts, credentialstores.WithVaultCredentialStoreAddress(vs.Addr))
opts = append(opts, credentialstores.WithVaultCredentialStoreCaCert(string(vs.CaCert)))
opts = append(opts, credentialstores.WithVaultCredentialStoreClientCertificate(string(vs.ClientCert)))
opts = append(opts, credentialstores.WithVaultCredentialStoreClientCertificateKey(string(vs.ClientKey)))
_, err = c.Update(context.Background(), cr.Item.Id, cr.Item.Version, opts...)
if err != nil {
t.Fatal(fmt.Errorf("got an error updating %q: %w", cr.Item.Id, err))
}
}
func testAccCheckCredentialStoreVaultResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("not found: %s", name)
}
id := rs.Primary.ID
if id == "" {
return fmt.Errorf("no ID is set")
}
storeId = id
md := testProvider.Meta().(*metaData)
c := credentialstores.NewClient(md.client)
if _, err := c.Read(context.Background(), id); err != nil {
return fmt.Errorf("got an error reading %q: %w", id, err)
}
return nil
}
}
func testAccCheckCredentialStoreVaultResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
return func(s *terraform.State) error {
if testProvider.Meta() == nil {
t.Fatal("got nil provider metadata")
}
md := testProvider.Meta().(*metaData)
for _, rs := range s.RootModule().Resources {
switch rs.Type {
case "boundary_credential_store_vault":
id := rs.Primary.ID
c := credentialstores.NewClient(md.client)
_, err := c.Read(context.Background(), id)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
return fmt.Errorf("didn't get a 404 when reading destroyed Vault credential store %q: %v", id, err)
}
default:
continue
}
}
return nil
}
}
+13 -38
View File
@@ -27,11 +27,6 @@ func resourceGroup() *schema.Resource {
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the group.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The group name. Defaults to the resource name.",
Type: schema.TypeString,
@@ -58,21 +53,12 @@ func resourceGroup() *schema.Resource {
}
}
func setFromGroupResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
if err := d.Set(NameKey, raw["name"]); err != nil {
return err
}
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
return err
}
if err := d.Set(ScopeIdKey, raw["scope_id"]); err != nil {
return err
}
if err := d.Set(groupMemberIdsKey, raw["member_ids"]); err != nil {
return err
}
func setFromGroupResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
d.Set(NameKey, raw["name"])
d.Set(DescriptionKey, raw["description"])
d.Set(ScopeIdKey, raw["scope_id"])
d.Set(groupMemberIdsKey, raw["member_ids"])
d.SetId(raw["id"].(string))
return nil
}
func resourceGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
@@ -108,7 +94,7 @@ func resourceGroupCreate(ctx context.Context, d *schema.ResourceData, meta inter
if gcr == nil {
return diag.Errorf("group nil after create")
}
raw := gcr.GetResponse().Map
raw := gcr.GetResponseMap()
if val, ok := d.GetOk(groupMemberIdsKey); ok {
list := val.(*schema.Set).List()
@@ -123,12 +109,10 @@ func resourceGroupCreate(ctx context.Context, d *schema.ResourceData, meta inter
if gcsmr == nil {
return diag.Errorf("group nil after setting members")
}
raw = gcsmr.GetResponse().Map
raw = gcsmr.GetResponseMap()
}
if err := setFromGroupResponseMap(d, raw); err != nil {
return diag.FromErr(err)
}
setFromGroupResponseMap(d, raw)
return nil
}
@@ -139,7 +123,7 @@ func resourceGroupRead(ctx context.Context, d *schema.ResourceData, meta interfa
g, err := grps.Read(ctx, d.Id())
if err != nil {
if apiErr := api.AsServerError(err); apiErr.Response().StatusCode() == http.StatusNotFound {
if apiErr := api.AsServerError(err); apiErr.Status == int32(http.StatusNotFound) {
d.SetId("")
return nil
}
@@ -149,9 +133,7 @@ func resourceGroupRead(ctx context.Context, d *schema.ResourceData, meta interfa
return diag.Errorf("group nil after read")
}
if err := setFromGroupResponseMap(d, g.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
setFromGroupResponseMap(d, g.GetResponseMap())
return nil
}
@@ -193,14 +175,10 @@ func resourceGroupUpdate(ctx context.Context, d *schema.ResourceData, meta inter
}
if d.HasChange(NameKey) {
if err := d.Set(NameKey, name); err != nil {
return diag.FromErr(err)
}
d.Set(NameKey, name)
}
if d.HasChange(DescriptionKey) {
if err := d.Set(DescriptionKey, desc); err != nil {
return diag.FromErr(err)
}
d.Set(DescriptionKey, desc)
}
// The above call may not actually happen, so we use d.Id() and automatic
@@ -218,10 +196,7 @@ func resourceGroupUpdate(ctx context.Context, d *schema.ResourceData, meta inter
if err != nil {
return diag.Errorf("error updating members in group: %v", err)
}
if err := d.Set(groupMemberIdsKey, memberIds); err != nil {
return diag.FromErr(err)
}
d.Set(groupMemberIdsKey, memberIds)
}
return nil
+7 -3
View File
@@ -359,18 +359,22 @@ func testAccCheckGroupResourceDestroy(t *testing.T, testProvider *schema.Provide
for _, rs := range s.RootModule().Resources {
switch rs.Type {
case "boundary_scope":
continue
case "boundary_user":
continue
case "boundary_group":
grps := groups.NewClient(md.client)
id := rs.Primary.ID
_, err := grps.Read(context.Background(), id)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
return fmt.Errorf("didn't get a 404 when reading destroyed resource %q: %v", id, apiErr)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Status != http.StatusNotFound {
return fmt.Errorf("Didn't get a 404 when reading destroyed resource %q: %v", id, apiErr)
}
default:
continue
t.Logf("Got unknown resource type %q", rs.Type)
}
}
return nil
@@ -17,72 +17,19 @@ const (
func resourceHost() *schema.Resource {
return &schema.Resource{
DeprecationMessage: "Deprecated: use `resource_host_static` instead.",
Description: "Deprecated: use `resource_host_static` instead.",
CreateContext: resourceHostStaticCreate,
ReadContext: resourceHostStaticRead,
UpdateContext: resourceHostStaticUpdate,
DeleteContext: resourceHostStaticDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the host.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The host name. Defaults to the resource name.",
Type: schema.TypeString,
Optional: true,
},
DescriptionKey: {
Description: "The host description.",
Type: schema.TypeString,
Optional: true,
},
TypeKey: {
Description: "The type of host",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
HostCatalogIdKey: {
Type: schema.TypeString,
Required: true,
},
hostAddressKey: {
Description: "The static address of the host resource as `<IP>` (note: port assignment occurs in the target resource definition, do not add :port here) or a domain name.",
Type: schema.TypeString,
Optional: true,
},
},
}
}
func resourceHostStatic() *schema.Resource {
return &schema.Resource{
Description: "The static host resource allows you to configure a Boundary static host. Hosts are " +
Description: "The host resource allows you to configure a Boundary static host. Hosts are " +
"always part of a project, so a project resource should be used inline or you should have " +
"the project ID in hand to successfully configure a host.",
CreateContext: resourceHostStaticCreate,
ReadContext: resourceHostStaticRead,
UpdateContext: resourceHostStaticUpdate,
DeleteContext: resourceHostStaticDelete,
CreateContext: resourceHostCreate,
ReadContext: resourceHostRead,
UpdateContext: resourceHostUpdate,
DeleteContext: resourceHostDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the host.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The host name. Defaults to the resource name.",
Type: schema.TypeString,
@@ -94,11 +41,9 @@ func resourceHostStatic() *schema.Resource {
Optional: true,
},
TypeKey: {
Description: "The type of host",
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Default: "static",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
HostCatalogIdKey: {
Type: schema.TypeString,
@@ -113,35 +58,24 @@ func resourceHostStatic() *schema.Resource {
}
}
func setFromHostResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
if err := d.Set(NameKey, raw["name"]); err != nil {
return err
}
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
return err
}
if err := d.Set(HostCatalogIdKey, raw["host_catalog_id"]); err != nil {
return err
}
if err := d.Set(TypeKey, raw["type"]); err != nil {
return err
}
func setFromHostResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
d.Set(NameKey, raw["name"])
d.Set(DescriptionKey, raw["description"])
d.Set(HostCatalogIdKey, raw["host_catalog_id"])
d.Set(TypeKey, raw["type"])
switch raw["type"].(string) {
case hostTypeStatic:
if attrsVal, ok := raw["attributes"]; ok {
attrs := attrsVal.(map[string]interface{})
if err := d.Set(hostAddressKey, attrs["address"]); err != nil {
return err
}
d.Set(hostAddressKey, attrs["address"])
}
}
d.SetId(raw["id"].(string))
return nil
}
func resourceHostStaticCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
func resourceHostCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
var hostCatalogId string
@@ -201,20 +135,18 @@ func resourceHostStaticCreate(ctx context.Context, d *schema.ResourceData, meta
return diag.Errorf("host nil after create")
}
if err := setFromHostResponseMap(d, hcr.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
setFromHostResponseMap(d, hcr.GetResponseMap())
return nil
}
func resourceHostStaticRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
func resourceHostRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
hClient := hosts.NewClient(md.client)
hrr, err := hClient.Read(ctx, d.Id())
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Status == int32(http.StatusNotFound) {
d.SetId("")
return nil
}
@@ -224,14 +156,12 @@ func resourceHostStaticRead(ctx context.Context, d *schema.ResourceData, meta in
return diag.Errorf("host nil after read")
}
if err := setFromHostResponseMap(d, hrr.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
setFromHostResponseMap(d, hrr.GetResponseMap())
return nil
}
func resourceHostStaticUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
func resourceHostUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
hClient := hosts.NewClient(md.client)
@@ -284,25 +214,19 @@ func resourceHostStaticUpdate(ctx context.Context, d *schema.ResourceData, meta
}
if d.HasChange(NameKey) {
if err := d.Set(NameKey, name); err != nil {
return diag.FromErr(err)
}
d.Set(NameKey, name)
}
if d.HasChange(DescriptionKey) {
if err := d.Set(DescriptionKey, desc); err != nil {
return diag.FromErr(err)
}
d.Set(DescriptionKey, desc)
}
if d.HasChange(hostAddressKey) {
if err := d.Set(hostAddressKey, *address); err != nil {
return diag.FromErr(err)
}
d.Set(hostAddressKey, *address)
}
return nil
}
func resourceHostStaticDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
func resourceHostDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
hClient := hosts.NewClient(md.client)
+194
View File
@@ -0,0 +1,194 @@
package provider
import (
"context"
"net/http"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/hostcatalogs"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
const (
hostCatalogTypeStatic = "static"
)
func resourceHostCatalog() *schema.Resource {
return &schema.Resource{
Description: "The host catalog resource allows you to configure a Boundary host catalog. Host " +
"catalogs are always part of a project, so a project resource should be used inline or you " +
"should have the project ID in hand to successfully configure a host catalog.",
CreateContext: resourceHostCatalogCreate,
ReadContext: resourceHostCatalogRead,
UpdateContext: resourceHostCatalogUpdate,
DeleteContext: resourceHostCatalogDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
NameKey: {
Description: "The host catalog name. Defaults to the resource name.",
Type: schema.TypeString,
Optional: true,
},
DescriptionKey: {
Description: "The host catalog description.",
Type: schema.TypeString,
Optional: true,
},
ScopeIdKey: {
Description: "The scope ID in which the resource is created.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
TypeKey: {
Description: "The host catalog type. Only `Static` (yes, title case) is supported.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}
func setFromHostCatalogResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
d.Set(NameKey, raw["name"])
d.Set(DescriptionKey, raw["description"])
d.Set(ScopeIdKey, raw["scope_id"])
d.Set(TypeKey, raw["type"])
d.SetId(raw["id"].(string))
}
func resourceHostCatalogCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
var scopeId string
if scopeIdVal, ok := d.GetOk(ScopeIdKey); ok {
scopeId = scopeIdVal.(string)
} else {
return diag.Errorf("no scope ID provided")
}
var typeStr string
if typeVal, ok := d.GetOk(TypeKey); ok {
typeStr = typeVal.(string)
} else {
return diag.Errorf("no type provided")
}
switch typeStr {
case hostCatalogTypeStatic:
default:
return diag.Errorf("invalid type provided")
}
opts := []hostcatalogs.Option{}
nameVal, ok := d.GetOk(NameKey)
if ok {
nameStr := nameVal.(string)
opts = append(opts, hostcatalogs.WithName(nameStr))
}
descVal, ok := d.GetOk(DescriptionKey)
if ok {
descStr := descVal.(string)
opts = append(opts, hostcatalogs.WithDescription(descStr))
}
hcClient := hostcatalogs.NewClient(md.client)
hccr, err := hcClient.Create(ctx, typeStr, scopeId, opts...)
if err != nil {
return diag.Errorf("error creating host catalog: %v", err)
}
if hccr == nil {
return diag.Errorf("nil host catalog after create")
}
setFromHostCatalogResponseMap(d, hccr.GetResponseMap())
return nil
}
func resourceHostCatalogRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
hcClient := hostcatalogs.NewClient(md.client)
hcrr, err := hcClient.Read(ctx, d.Id())
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Status == int32(http.StatusNotFound) {
d.SetId("")
return nil
}
return diag.Errorf("error reading host catalog: %v", err)
}
if hcrr == nil {
return diag.Errorf("host catalog nil after read")
}
setFromHostCatalogResponseMap(d, hcrr.GetResponseMap())
return nil
}
func resourceHostCatalogUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
hcClient := hostcatalogs.NewClient(md.client)
opts := []hostcatalogs.Option{}
var name *string
if d.HasChange(NameKey) {
opts = append(opts, hostcatalogs.DefaultName())
nameVal, ok := d.GetOk(NameKey)
if ok {
nameStr := nameVal.(string)
name = &nameStr
opts = append(opts, hostcatalogs.WithName(nameStr))
}
}
var desc *string
if d.HasChange(DescriptionKey) {
opts = append(opts, hostcatalogs.DefaultDescription())
descVal, ok := d.GetOk(DescriptionKey)
if ok {
descStr := descVal.(string)
desc = &descStr
opts = append(opts, hostcatalogs.WithDescription(descStr))
}
}
if len(opts) > 0 {
opts = append(opts, hostcatalogs.WithAutomaticVersioning(true))
_, err := hcClient.Update(ctx, d.Id(), 0, opts...)
if err != nil {
return diag.Errorf("error updating host catalog: %v", err)
}
}
if d.HasChange(NameKey) {
d.Set(NameKey, name)
}
if d.HasChange(DescriptionKey) {
d.Set(DescriptionKey, desc)
}
return nil
}
func resourceHostCatalogDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
hcClient := hostcatalogs.NewClient(md.client)
_, err := hcClient.Delete(ctx, d.Id())
if err != nil {
return diag.Errorf("error deleting host catalog: %v", err)
}
return nil
}
@@ -1,648 +0,0 @@
package provider
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"math/rand"
"net/http"
"strconv"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/hostcatalogs"
"github.com/hashicorp/go-secure-stdlib/parseutil"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"golang.org/x/crypto/blake2b"
)
const (
hostCatalogTypePlugin = "plugin"
)
func resourceHostCatalogPlugin() *schema.Resource {
return &schema.Resource{
Description: "The host catalog resource allows you to configure a Boundary plugin-type host catalog. Host " +
"catalogs are always part of a project, so a project resource should be used inline or you " +
"should have the project ID in hand to successfully configure a host catalog.",
CreateContext: resourceHostCatalogPluginCreate,
ReadContext: resourceHostCatalogPluginRead,
UpdateContext: resourceHostCatalogPluginUpdate,
DeleteContext: resourceHostCatalogPluginDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the host catalog.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The host catalog name. Defaults to the resource name.",
Type: schema.TypeString,
Optional: true,
},
DescriptionKey: {
Description: "The host catalog description.",
Type: schema.TypeString,
Optional: true,
},
ScopeIdKey: {
Description: "The scope ID in which the resource is created.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
PluginIdKey: {
Description: "The ID of the plugin that should back the resource. This or " + PluginNameKey + " must be defined.",
Type: schema.TypeString,
ConflictsWith: []string{PluginNameKey},
Optional: true,
ForceNew: true,
Computed: true, // If name is provided this will be computed
},
PluginNameKey: {
Description: "The name of the plugin that should back the resource. This or " + PluginIdKey + " must be defined.",
Type: schema.TypeString,
ConflictsWith: []string{PluginIdKey},
Optional: true,
ForceNew: true,
Computed: true,
},
AttributesJsonKey: {
Description: `The attributes for the host catalog. Either values encoded with the "jsonencode" function, pre-escaped JSON string, ` +
`or a file:// or env:// path. Set to a string "null" or remove the block to clear all attributes in the host catalog.`,
Type: schema.TypeString,
Optional: true,
// If set to null in config and nothing comes from API, consider
// it the same. Same if config changes from empty to null.
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
sanitizedNew, err := sanitizeJson(new)
if err != nil {
return false
}
new = string(sanitizedNew)
switch {
case old == new:
return true
case old == "null" && new == "":
return true
case old == "" && new == "null":
return true
default:
return false
}
},
},
SecretsJsonKey: {
Description: `The secrets for the host catalog. Either values encoded with the "jsonencode" function, pre-escaped JSON string, ` +
`or a file:// or env:// path. Set to a string "null" to clear any existing values. NOTE: Unlike "attributes_json", removing ` +
`this block will NOT clear secrets from the host catalog; this allows injecting secrets for one call, then removing them for storage.`,
Type: schema.TypeString,
Optional: true,
Sensitive: true,
},
SecretsHmacKey: {
Description: "The HMAC'd secrets value returned from the server.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
internalSecretsConfigHmacKey: {
Description: "Internal only. HMAC of (serverSecretsHmac + config secrets). Used for proper secrets handling.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
internalHmacUsedForSecretsConfigHmacKey: {
Description: "Internal only. The Boundary-provided HMAC used to calculate the current value of the HMAC'd config. Used for drift detection.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
internalForceUpdateKey: {
Description: "Internal only. Used to force update so that we can always check the value of secrets.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
// We want to always force an update (which itself may not actually do
// anything) so that we can properly check secrets state.
CustomizeDiff: func(_ context.Context, d *schema.ResourceDiff, _ interface{}) error {
return d.SetNewComputed(internalForceUpdateKey)
},
}
}
func sanitizeJson(in string) ([]byte, error) {
var v interface{}
if err := json.Unmarshal([]byte(in), &v); err != nil {
return nil, err
}
// Remarshal so we sanitize the order
out, err := json.Marshal(v)
if err != nil {
return nil, err
}
return out, nil
}
// calculateCurrentConfigHmac generates an HMAC'd config value using the
// server's calculated HMAC as an HMAC key. Prior to calculating this we parse
// and then re-marshal the JSON to take advantage of Go alphabetizing JSON
// output so that we can ensure we'll treat the same objects as equivalent
// regardless of initial input order.
func calculateCurrentConfigHmac(serverHmac, secretsStr string) (string, error) {
jsonBytes, err := sanitizeJson(secretsStr)
if err != nil {
return "", err
}
key := blake2b.Sum256([]byte(serverHmac))
mac := hmac.New(sha256.New, key[:])
_, err = mac.Write(jsonBytes)
if err != nil {
return "", err
}
hmac := mac.Sum(nil)
return base64.StdEncoding.EncodeToString(hmac), nil
}
// calculateConfigHmacPlan, given the schema and the current server HMAC,
// returns what to set on the server (if anything) and any diagnostics. If
// clearState is set we should nil out existing values in state. If
// sendToBoundary is set then the read secrets_json should be sent in an API
// call.
func calculateConfigHmacPlan(serverHmac, secretsJson string, d *schema.ResourceData) (clearState, sendToBoundary bool, diagWarn *diag.Diagnostic, retErr error) {
stateConfigHmac := d.Get(internalSecretsConfigHmacKey).(string)
// Iterate through possible states and handle appropriately
switch {
case serverHmac == "" && secretsJson == "":
// State 1: No configured secrets in either Boundary or TF. Clear HMAC'd
// config if present.
return true, false, nil, nil
case serverHmac == "" && secretsJson != "":
// State 2: Boundary has no secrets but TF does. Put secrets into
// Boundary. HMAC'd config will be updated after. However, if the value
// is "null" it's a no-op so just make sure HMAC'd config is cleared if
// present.
switch secretsJson {
case "null":
return true, false, nil, nil
default:
return false, true, nil, nil
}
case serverHmac != "" && secretsJson == "":
// State 3: Boundary has configured secrets but nothing in TF config. In
// this case we make a break from "normal" TF behavior; rather than
// interpret this as needing to clear secrets out of Boundary, we do
// nothing. That way the user is free to configure secrets directly in
// the API (thus never having it transport through TF or any of its
// parts or configs) or remove them from the TF config file once they're
// set. Note that we wipe knowledge of the HMAC'd config in this case,
// so if they re-add values (even the same ones) we'll hit state 4. This
// is the escape hatch for state 6a below.
return true, false, nil, nil
case serverHmac != "" && secretsJson != "" && stateConfigHmac == "":
// State 4: In this case Boundary has secrets and TF config has secrets,
// but we have no HMAC value at all in state. This almost certainly
// means that Boundary was configured before the Terraform configuration
// file was written, or at least prior to the value being added to TF's
// config, since running TF with config would generate an HMAC'd config
// value. We can assume that the TF config is new and meant as an update.
return false, true, nil, nil
case serverHmac != "" && secretsJson != "":
// At this point we need to calculated the current HMAC'd config value
// so we can see if it matches.
currentConfigHmac, err := calculateCurrentConfigHmac(serverHmac, secretsJson)
if err != nil {
return false, false, nil, err
}
if stateConfigHmac == currentConfigHmac {
// State 5: Both Boundary and TF have secrets configured and they
// are known to match, so do nothing.
return false, false, nil, nil
}
// The next bit is a little complicated. We have been storing not just
// the HMAC'd config but also the boundary HMAC value that was used to
// generate it. If that value has not changed, but the HMAC'd config
// has, it means that the Terraform configuration has changed (but
// Boundary has not been given new values since the last time we
// calculated from the TF config) and we can assume that it is new. If
// that value has changed, it is unclear whether or not Boundary's new
// values are correct or the existing config values are correct. Picking
// wrong could lead to a denial of service (e.g. if creds provided
// through TF were rotated and no longer valid) so instead we raise this
// as a warning to the user.
//
// The next question, however, is how does the user escape from this
// scenario? Simply changing TF config more won't work as we'll be right
// back here. The answer is state 3. If they remove the secrets we will
// wipe knowlege of the HMAC'd config from TF; when they add them back
// they'll hit state 4 and we'll put the new values in.
switch {
case serverHmac != d.Get(internalHmacUsedForSecretsConfigHmacKey).(string):
// State 6a: mismatch. Warn the user.
return false, false, &diag.Diagnostic{
Severity: diag.Warning,
Summary: "Mismatch in secrets state between Boundary and Terraform.",
Detail: `Boundary's secret state is out of sync with Terraform. Usually this is the result of secrets being provided ` +
`directly via Boundary's API. To suppress this warning, either remove the secrets via Boundary's API and allow Terraform to ` +
`repopulate them, or remove the secrets_json block from Terraform's configuration file (the next time you add secrets_json ` +
`back to the file, those values will be used to overwrite the current Boundary values.)`,
}, nil
default:
// State 6b: Boundary's HMAC hasn't changed from the one we used to
// generate the HMAC'd config, so we know the config has changed.
return false, true, nil, nil
}
default:
return false, false, nil, fmt.Errorf(
"unhandled secrets state; server hmac is found: %t; secrets detected in config: %t; existing hmac'd config: %t",
serverHmac != "", secretsJson != "", stateConfigHmac != "")
}
}
func setFromHostCatalogPluginResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
if err := d.Set(NameKey, raw[NameKey]); err != nil {
return err
}
if err := d.Set(DescriptionKey, raw[DescriptionKey]); err != nil {
return err
}
if err := d.Set(ScopeIdKey, raw[ScopeIdKey]); err != nil {
return err
}
// Plugin stuff
{
if err := d.Set(PluginIdKey, raw[PluginIdKey]); err != nil {
return err
}
pluginRaw, ok := raw["plugin"]
if !ok {
return fmt.Errorf("plugin field not found in response")
}
pluginInfo, ok := pluginRaw.(map[string]interface{})
if !ok {
return fmt.Errorf("plugin field in response has wrong type")
}
pluginNameRaw, ok := pluginInfo["name"]
if !ok {
return fmt.Errorf("plugin name field not found in response")
}
pluginName, ok := pluginNameRaw.(string)
if !ok {
return fmt.Errorf("plugin name field in response has wrong type")
}
if err := d.Set(PluginNameKey, pluginName); err != nil {
return err
}
}
// Attributes stuff
{
attrRaw, ok := raw["attributes"]
switch ok {
case true:
encodedAttributes, err := json.Marshal(attrRaw)
if err != nil {
return err
}
if err := d.Set(AttributesJsonKey, string(encodedAttributes)); err != nil {
return err
}
default:
d.Set(AttributesJsonKey, nil)
}
}
// Secrets stuff
{
// We do not save secrets into the state file, and they're not returned in
// the response
secretsHmacRaw, ok := raw[SecretsHmacKey]
switch ok {
case true:
if err := d.Set(SecretsHmacKey, secretsHmacRaw); err != nil {
return err
}
default:
d.Set(SecretsHmacKey, nil)
}
}
d.SetId(raw[IDKey].(string))
if err := d.Set(internalForceUpdateKey, strconv.FormatInt(rand.Int63(), 10)); err != nil {
return err
}
return nil
}
func resourceHostCatalogPluginCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
var scopeId string
if scopeIdVal, ok := d.GetOk(ScopeIdKey); ok {
scopeId = scopeIdVal.(string)
} else {
return diag.Errorf("no scope ID provided")
}
opts := []hostcatalogs.Option{}
var foundPluginId bool
var foundPluginName bool
if pluginIdVal, ok := d.GetOk(PluginIdKey); ok {
pluginId := pluginIdVal.(string)
opts = append(opts, hostcatalogs.WithPluginId(pluginId))
foundPluginId = true
}
if pluginNameVal, ok := d.GetOk(PluginNameKey); ok {
pluginName := pluginNameVal.(string)
opts = append(opts, hostcatalogs.WithPluginName(pluginName))
foundPluginName = true
}
if !foundPluginId && !foundPluginName {
return diag.Errorf("neither plugin ID nor plugin name provided")
}
nameVal, ok := d.GetOk(NameKey)
if ok {
nameStr := nameVal.(string)
opts = append(opts, hostcatalogs.WithName(nameStr))
}
descVal, ok := d.GetOk(DescriptionKey)
if ok {
descStr := descVal.(string)
opts = append(opts, hostcatalogs.WithDescription(descStr))
}
attrsVal, ok := d.GetOk(AttributesJsonKey)
if ok {
attrsStr, err := parseutil.ParsePath(attrsVal.(string))
if err != nil && !errors.Is(err, parseutil.ErrNotAUrl) {
return diag.Errorf("error parsing path with attributes: %v", err)
}
switch attrsStr {
case "null":
opts = append(opts, hostcatalogs.DefaultAttributes())
default:
// What comes in is json-encoded but we want to set a
// map[string]interface{} so we unmarshal it and set that
var m map[string]interface{}
if err := json.Unmarshal([]byte(attrsStr), &m); err != nil {
return diag.Errorf("error unmarshaling attributes: %v", err)
}
opts = append(opts, hostcatalogs.WithAttributes(m))
}
}
secretsVal, ok := d.GetOk(SecretsJsonKey)
var secretsJson string
if ok {
var err error
secretsJson, err = parseutil.ParsePath(secretsVal.(string))
if err != nil && !errors.Is(err, parseutil.ErrNotAUrl) {
return diag.Errorf("error parsing path with secrets: %v", err)
}
switch secretsJson {
case "null":
opts = append(opts, hostcatalogs.DefaultSecrets())
default:
// What comes in is json-encoded but we want to set a
// map[string]interface{} so we unmarshal it and set that
var m map[string]interface{}
if err := json.Unmarshal([]byte(secretsJson), &m); err != nil {
return diag.Errorf("error unmarshaling secrets: %v", err)
}
opts = append(opts, hostcatalogs.WithSecrets(m))
}
}
hcClient := hostcatalogs.NewClient(md.client)
hccr, err := hcClient.Create(ctx, hostCatalogTypePlugin, scopeId, opts...)
if err != nil {
return diag.Errorf("error creating host catalog: %v", err)
}
if hccr == nil {
return diag.Errorf("host catalog nil after create")
}
if err := setFromHostCatalogPluginResponseMap(d, hccr.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
if serverHmac := d.Get(SecretsHmacKey).(string); serverHmac != "" {
configHmac, err := calculateCurrentConfigHmac(serverHmac, secretsJson)
if err != nil {
return diag.FromErr(err)
}
if err := d.Set(internalSecretsConfigHmacKey, configHmac); err != nil {
return diag.FromErr(err)
}
if err := d.Set(internalHmacUsedForSecretsConfigHmacKey, serverHmac); err != nil {
return diag.FromErr(err)
}
}
return nil
}
func resourceHostCatalogPluginRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
hcClient := hostcatalogs.NewClient(md.client)
hcrr, err := hcClient.Read(ctx, d.Id())
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
d.SetId("")
return nil
}
return diag.Errorf("error reading host catalog: %v", err)
}
if hcrr == nil {
return diag.Errorf("host catalog nil after read")
}
if err := setFromHostCatalogPluginResponseMap(d, hcrr.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
return nil
}
func resourceHostCatalogPluginUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
hcClient := hostcatalogs.NewClient(md.client)
// We need to refresh the current server hmac value to figure out what to do
// next around secrets handling
var clearStateSecrets, sendSecretsToBoundary bool
var secretsJson string
var currentDiagnostics diag.Diagnostics
{
hcrr, err := hcClient.Read(ctx, d.Id())
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
d.SetId("")
return nil
}
return diag.Errorf("error reading host catalog in update: %v", err)
}
if hcrr == nil {
return diag.Errorf("host catalog nil after read in update")
}
var serverSecretsHmac string
if secretsHmacRaw, ok := hcrr.GetResponse().Map[SecretsHmacKey]; ok {
serverSecretsHmac = secretsHmacRaw.(string)
}
// Get current secrets_json value
secretsJson, err = parseutil.ParsePath(d.Get(SecretsJsonKey).(string))
if err != nil && !errors.Is(err, parseutil.ErrNotAUrl) {
return diag.Errorf("error parsing path with secrets: %v", err)
}
// Now that we have the value from the server, see if anything needs to be
// done
var diagWarning *diag.Diagnostic
clearStateSecrets, sendSecretsToBoundary, diagWarning, err = calculateConfigHmacPlan(serverSecretsHmac, secretsJson, d)
if err != nil {
return diag.FromErr(err)
}
if diagWarning != nil {
currentDiagnostics = append(currentDiagnostics, *diagWarning)
}
}
opts := []hostcatalogs.Option{}
if d.HasChange(NameKey) {
opts = append(opts, hostcatalogs.DefaultName())
nameVal, ok := d.GetOk(NameKey)
if ok {
nameStr := nameVal.(string)
opts = append(opts, hostcatalogs.WithName(nameStr))
}
}
if d.HasChange(DescriptionKey) {
opts = append(opts, hostcatalogs.DefaultDescription())
descVal, ok := d.GetOk(DescriptionKey)
if ok {
descStr := descVal.(string)
opts = append(opts, hostcatalogs.WithDescription(descStr))
}
}
if d.HasChange(AttributesJsonKey) {
attrsVal, ok := d.GetOk(AttributesJsonKey)
if ok {
attrsStr, err := parseutil.ParsePath(attrsVal.(string))
if err != nil && !errors.Is(err, parseutil.ErrNotAUrl) {
return append(currentDiagnostics, diag.Errorf("error parsing path with attributes: %v", err)...)
}
switch attrsStr {
case "null", "":
opts = append(opts, hostcatalogs.DefaultAttributes())
default:
// What comes in is json-encoded but we want to set a
// map[string]interface{} so we unmarshal it and set that
var m map[string]interface{}
if err := json.Unmarshal([]byte(attrsStr), &m); err != nil {
return append(currentDiagnostics, diag.Errorf("error unmarshaling attributes: %v", err)...)
}
opts = append(opts, hostcatalogs.WithAttributes(m))
}
} else {
opts = append(opts, hostcatalogs.DefaultAttributes())
}
}
if sendSecretsToBoundary {
switch secretsJson {
case "null":
opts = append(opts, hostcatalogs.DefaultSecrets())
default:
// What comes in is json-encoded but we want to set a
// map[string]interface{} so we unmarshal it and set that
var m map[string]interface{}
if err := json.Unmarshal([]byte(secretsJson), &m); err != nil {
return append(currentDiagnostics, diag.Errorf("error unmarshaling secrets: %v", err)...)
}
opts = append(opts, hostcatalogs.WithSecrets(m))
}
}
if len(opts) > 0 {
opts = append(opts, hostcatalogs.WithAutomaticVersioning(true))
hcur, err := hcClient.Update(ctx, d.Id(), 0, opts...)
if err != nil {
return append(currentDiagnostics, diag.Errorf("error updating host catalog: %v", err)...)
}
if hcur == nil {
return append(currentDiagnostics, diag.Errorf("host catalog nil after update")...)
}
if err := setFromHostCatalogPluginResponseMap(d, hcur.GetResponse().Map); err != nil {
return append(currentDiagnostics, diag.FromErr(err)...)
}
}
// Save any updated secrets information if needed
switch {
case clearStateSecrets:
if err := d.Set(internalSecretsConfigHmacKey, nil); err != nil {
return append(currentDiagnostics, diag.FromErr(err)...)
}
if err := d.Set(internalHmacUsedForSecretsConfigHmacKey, nil); err != nil {
return append(currentDiagnostics, diag.FromErr(err)...)
}
case sendSecretsToBoundary:
if serverHmac := d.Get(SecretsHmacKey).(string); serverHmac != "" {
configHmac, err := calculateCurrentConfigHmac(serverHmac, secretsJson)
if err != nil {
return append(currentDiagnostics, diag.FromErr(err)...)
}
if err := d.Set(internalSecretsConfigHmacKey, configHmac); err != nil {
return append(currentDiagnostics, diag.FromErr(err)...)
}
if err := d.Set(internalHmacUsedForSecretsConfigHmacKey, serverHmac); err != nil {
return append(currentDiagnostics, diag.FromErr(err)...)
}
}
}
return nil
}
func resourceHostCatalogPluginDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
hcClient := hostcatalogs.NewClient(md.client)
_, err := hcClient.Delete(ctx, d.Id())
if err != nil {
return diag.Errorf("error deleting host catalog: %v", err)
}
return nil
}
@@ -1,391 +0,0 @@
package provider
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"testing"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/hostcatalogs"
"github.com/hashicorp/boundary/testing/controller"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
_ "github.com/kr/pretty" // So I don't have to keep adding to/removing from go.mod :-)
)
const (
testPluginHostCatalogDescription = "bar"
testPluginHostCatalogDescriptionUpdate = "bar foo"
testPluginHostCatalogDescriptionUpdate2 = "bar foo foo"
)
// expectedAttributesState is used here and in plugin host sets to control how
// we expect attributes to behave compared to the previous step.
type expectedAttributesState uint
const (
expectedAttributesStatePreviouslyEmptyNowSet expectedAttributesState = iota
expectedAttributesStatePreviouslySetNoChange
expectedAttributesStatePreviouslySetButChanged
expectedAttributesStatePreviouslySetNowEmpty
)
var projPluginHostCatalogBase = `
resource "boundary_host_catalog_plugin" "foo" {
name = "foo"
scope_id = boundary_scope.proj1.id
plugin_name = "loopback"
%s
depends_on = [boundary_role.proj1_admin]
}`
var (
currentPluginHostCatalogSecretsHmacValue string
currentPluginHostCatalogAttributesValue string
testStep = 1
)
// NOTE: In the test below, secrets and attributes change in the same manner at
// the same time; the eventual result is the same even if the JSON looks
// different. Thus expectedAttributesState also controls expectations for
// secrets.
func TestAccPluginHostCatalog(t *testing.T) {
tc := controller.NewTestController(t, tcConfig...)
defer tc.Shutdown()
url := tc.ApiAddrs()[0]
resName := "boundary_host_catalog_plugin.foo"
initialValuesStr := fmt.Sprintf(`
description = "%s"
attributes_json = jsonencode({
foo = "bar"
zip = "zap"
})
secrets_json = jsonencode({
hush = "puppies"
})
`,
testPluginHostCatalogDescription,
)
// Changed description and secrets
valuesStrUpdate1 := fmt.Sprintf(`
description = "%s"
attributes_json = jsonencode({
foo = "bar"
zip = "zoop"
})
secrets_json = jsonencode({
flush = "fluppies"
})
`,
testPluginHostCatalogDescriptionUpdate,
)
// Changed description, no secrets update
valuesStrUpdate2 := fmt.Sprintf(`
description = "%s"
attributes_json = jsonencode({
foo = "bar"
zip = "zoop"
})
secrets_json = jsonencode({
flush = "fluppies"
})
`,
testPluginHostCatalogDescriptionUpdate2,
)
// Same description, now empty secrets
valuesStrUpdate3 := fmt.Sprintf(`
description = "%s"
attributes_json = jsonencode({
foo = "bar"
zip = "zoop"
})
`,
testPluginHostCatalogDescriptionUpdate2,
)
// Same description, now explicitly unset secrets and blankify attrs
valuesStrUpdate4 := fmt.Sprintf(`
description = "%s"
secrets_json = "null"
`,
testPluginHostCatalogDescriptionUpdate2,
)
// Set values again
valuesStrUpdate5 := fmt.Sprintf(`
description = "%s"
attributes_json = jsonencode({
foo = "bar"
zip = "zoop"
})
secrets_json = jsonencode({
flush = "fluppies"
})
`,
testPluginHostCatalogDescriptionUpdate2,
)
// Explicitly set both secrets and attributes to null
valuesStrUpdate6 := fmt.Sprintf(`
description = "%s"
attributes_json = "null"
secrets_json = "null"
`,
testPluginHostCatalogDescriptionUpdate2,
)
initialHcl := fmt.Sprintf(projPluginHostCatalogBase, initialValuesStr)
update1Hcl := fmt.Sprintf(projPluginHostCatalogBase, valuesStrUpdate1)
update2Hcl := fmt.Sprintf(projPluginHostCatalogBase, valuesStrUpdate2)
update3Hcl := fmt.Sprintf(projPluginHostCatalogBase, valuesStrUpdate3)
update4Hcl := fmt.Sprintf(projPluginHostCatalogBase, valuesStrUpdate4)
update5Hcl := fmt.Sprintf(projPluginHostCatalogBase, valuesStrUpdate5)
update6Hcl := fmt.Sprintf(projPluginHostCatalogBase, valuesStrUpdate6)
var provider *schema.Provider
resource.Test(t, resource.TestCase{
ProviderFactories: providerFactories(&provider),
CheckDestroy: testAccCheckPluginHostCatalogResourceDestroy(t, provider),
Steps: []resource.TestStep{
{
// test create
Config: testConfig(url, fooOrg, firstProjectFoo, initialHcl),
Check: resource.ComposeTestCheckFunc(
testAccCheckScopeResourceExists(provider, "boundary_scope.org1"),
testAccCheckScopeResourceExists(provider, "boundary_scope.proj1"),
testAccCheckPluginHostCatalogResourceExists(provider, resName, expectedAttributesStatePreviouslyEmptyNowSet),
resource.TestCheckResourceAttr(resName, DescriptionKey, testPluginHostCatalogDescription),
),
ExpectNonEmptyPlan: true,
},
importStep(resName, SecretsJsonKey, internalHmacUsedForSecretsConfigHmacKey, internalForceUpdateKey, internalSecretsConfigHmacKey),
{
// test update
Config: testConfig(url, fooOrg, firstProjectFoo, update1Hcl),
Check: resource.ComposeTestCheckFunc(
testAccCheckPluginHostCatalogResourceExists(provider, resName, expectedAttributesStatePreviouslySetButChanged),
resource.TestCheckResourceAttr(resName, DescriptionKey, testPluginHostCatalogDescriptionUpdate),
),
ExpectNonEmptyPlan: true,
},
importStep(resName, SecretsJsonKey, internalHmacUsedForSecretsConfigHmacKey, internalForceUpdateKey, internalSecretsConfigHmacKey),
{
// test update
Config: testConfig(url, fooOrg, firstProjectFoo, update2Hcl),
Check: resource.ComposeTestCheckFunc(
testAccCheckPluginHostCatalogResourceExists(provider, resName, expectedAttributesStatePreviouslySetNoChange),
resource.TestCheckResourceAttr(resName, DescriptionKey, testPluginHostCatalogDescriptionUpdate2),
),
ExpectNonEmptyPlan: true,
},
importStep(resName, SecretsJsonKey, internalHmacUsedForSecretsConfigHmacKey, internalForceUpdateKey, internalSecretsConfigHmacKey),
{
// this runs the same HCL; mostly used in some manual checking
// to ensure update is still called even when nothing has
// changed (as we need for secrets)
Config: testConfig(url, fooOrg, firstProjectFoo, update2Hcl),
Check: resource.ComposeTestCheckFunc(
testAccCheckPluginHostCatalogResourceExists(provider, resName, expectedAttributesStatePreviouslySetNoChange),
resource.TestCheckResourceAttr(resName, DescriptionKey, testPluginHostCatalogDescriptionUpdate2),
),
ExpectNonEmptyPlan: true,
},
importStep(resName, SecretsJsonKey, internalHmacUsedForSecretsConfigHmacKey, internalForceUpdateKey, internalSecretsConfigHmacKey),
{
// test update
Config: testConfig(url, fooOrg, firstProjectFoo, update3Hcl),
Check: resource.ComposeTestCheckFunc(
testAccCheckPluginHostCatalogResourceExists(provider, resName, expectedAttributesStatePreviouslySetNoChange),
resource.TestCheckResourceAttr(resName, DescriptionKey, testPluginHostCatalogDescriptionUpdate2),
),
ExpectNonEmptyPlan: true,
},
importStep(resName, SecretsJsonKey, internalHmacUsedForSecretsConfigHmacKey, internalForceUpdateKey, internalSecretsConfigHmacKey),
{
// test update
Config: testConfig(url, fooOrg, firstProjectFoo, update4Hcl),
Check: resource.ComposeTestCheckFunc(
testAccCheckPluginHostCatalogResourceExists(provider, resName, expectedAttributesStatePreviouslySetNowEmpty),
resource.TestCheckResourceAttr(resName, DescriptionKey, testPluginHostCatalogDescriptionUpdate2),
),
ExpectNonEmptyPlan: true,
},
importStep(resName, SecretsJsonKey, internalHmacUsedForSecretsConfigHmacKey, internalForceUpdateKey, internalSecretsConfigHmacKey),
{
// test update
Config: testConfig(url, fooOrg, firstProjectFoo, update5Hcl),
Check: resource.ComposeTestCheckFunc(
testAccCheckPluginHostCatalogResourceExists(provider, resName, expectedAttributesStatePreviouslyEmptyNowSet),
resource.TestCheckResourceAttr(resName, DescriptionKey, testPluginHostCatalogDescriptionUpdate2),
),
ExpectNonEmptyPlan: true,
},
importStep(resName, SecretsJsonKey, internalHmacUsedForSecretsConfigHmacKey, internalForceUpdateKey, internalSecretsConfigHmacKey),
{
// test update
Config: testConfig(url, fooOrg, firstProjectFoo, update6Hcl),
Check: resource.ComposeTestCheckFunc(
testAccCheckPluginHostCatalogResourceExists(provider, resName, expectedAttributesStatePreviouslySetNowEmpty),
resource.TestCheckResourceAttr(resName, DescriptionKey, testPluginHostCatalogDescriptionUpdate2),
),
ExpectNonEmptyPlan: true,
},
importStep(resName, SecretsJsonKey, internalHmacUsedForSecretsConfigHmacKey, internalForceUpdateKey, internalSecretsConfigHmacKey),
},
})
}
func testAccCheckPluginHostCatalogResourceExists(testProvider *schema.Provider, name string, expAttrState expectedAttributesState) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Not found: %s", name)
}
id := rs.Primary.ID
if id == "" {
return fmt.Errorf("No ID is set")
}
md := testProvider.Meta().(*metaData)
hcClient := hostcatalogs.NewClient(md.client)
val, err := hcClient.Read(context.Background(), id)
if err != nil {
return fmt.Errorf("Got an error when reading host catalog %q: %v", id, err)
}
if val == nil {
return errors.New("empty val returned")
}
secretsHmac := val.GetResponse().Map[SecretsHmacKey]
attrs := val.GetResponse().Map["attributes"]
switch expAttrState {
case expectedAttributesStatePreviouslyEmptyNowSet:
if currentPluginHostCatalogSecretsHmacValue != "" {
return errors.New("expected no previous secrets hmac value")
}
val := secretsHmac.(string)
if val == "" {
return errors.New("expected non-empty new secrets hmac value")
}
currentPluginHostCatalogSecretsHmacValue = val
if currentPluginHostCatalogAttributesValue != "" {
return fmt.Errorf("expected no previous attributes value, got %s", currentPluginHostCatalogAttributesValue)
}
if attrs == nil {
return errors.New("expected non-empty new attributes value")
}
attrsVal, err := json.Marshal(attrs)
if err != nil {
return fmt.Errorf("error marshaling attrs: %w", err)
}
currentPluginHostCatalogAttributesValue = string(attrsVal)
case expectedAttributesStatePreviouslySetButChanged:
if currentPluginHostCatalogSecretsHmacValue == "" {
return errors.New("expected previous secrets hmac value")
}
val := secretsHmac.(string)
if val == "" {
return errors.New("expected non-empty new secrets hmac value")
}
if val == currentPluginHostCatalogSecretsHmacValue {
return errors.New("expected changed secrets hmac value")
}
currentPluginHostCatalogSecretsHmacValue = val
if currentPluginHostCatalogAttributesValue == "" {
return errors.New("expected previous attributes value")
}
if attrs == nil {
return errors.New("expected non-empty new attributes value")
}
attrsVal, err := json.Marshal(attrs)
if err != nil {
return fmt.Errorf("error marshaling attrs: %w", err)
}
if string(attrsVal) == currentPluginHostCatalogAttributesValue {
return errors.New("expected changed attrs value")
}
currentPluginHostCatalogAttributesValue = string(attrsVal)
case expectedAttributesStatePreviouslySetNoChange:
if currentPluginHostCatalogSecretsHmacValue == "" {
return errors.New("expected previous secrets hmac value")
}
val := secretsHmac.(string)
if val == "" {
return errors.New("expected non-empty new secrets hmac value")
}
if val != currentPluginHostCatalogSecretsHmacValue {
return errors.New("expected same secrets hmac value")
}
if currentPluginHostCatalogAttributesValue == "" {
return errors.New("expected previous attributes value")
}
if attrs == nil {
return errors.New("expected non-empty new attributes value")
}
attrsVal, err := json.Marshal(attrs)
if err != nil {
return fmt.Errorf("error marshaling attrs: %w", err)
}
if string(attrsVal) == "" {
return errors.New("expected non-empty new attributes value")
}
if string(attrsVal) != currentPluginHostCatalogAttributesValue {
return errors.New("expected same attributes value")
}
case expectedAttributesStatePreviouslySetNowEmpty:
if currentPluginHostCatalogSecretsHmacValue == "" {
return errors.New("expected previous secrets hmac value")
}
if secretsHmac != nil {
return fmt.Errorf("expected empty new secrets hmac value, got %s", secretsHmac.(string))
}
currentPluginHostCatalogSecretsHmacValue = ""
if currentPluginHostCatalogAttributesValue == "" {
return errors.New("expected previous attributes value")
}
if attrs != nil {
return fmt.Errorf("expected empty new attributes value, got %s", attrs)
}
currentPluginHostCatalogAttributesValue = ""
}
testStep++
return nil
}
}
func testAccCheckPluginHostCatalogResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
return func(s *terraform.State) error {
md := testProvider.Meta().(*metaData)
for _, rs := range s.RootModule().Resources {
switch rs.Type {
case "boundary_host_catalog":
id := rs.Primary.ID
hcClient := hostcatalogs.NewClient(md.client)
_, err := hcClient.Read(context.Background(), id)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
return fmt.Errorf("didn't get a 404 when reading destroyed host catalog %q: %v", id, err)
}
default:
continue
}
}
return nil
}
}
@@ -1,257 +0,0 @@
package provider
import (
"context"
"net/http"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/hostcatalogs"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
const (
hostCatalogTypeStatic = "static"
)
func resourceHostCatalog() *schema.Resource {
return &schema.Resource{
DeprecationMessage: "Deprecated: use `resource_host_catalog_static` instead.",
Description: "Deprecated: use `resource_host_catalog_static` instead.",
CreateContext: resourceHostCatalogStaticCreate(true),
ReadContext: resourceHostCatalogStaticRead(true),
UpdateContext: resourceHostCatalogStaticUpdate(true),
DeleteContext: resourceHostCatalogStaticDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the host catalog.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The host catalog name. Defaults to the resource name.",
Type: schema.TypeString,
Optional: true,
},
DescriptionKey: {
Description: "The host catalog description.",
Type: schema.TypeString,
Optional: true,
},
ScopeIdKey: {
Description: "The scope ID in which the resource is created.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
TypeKey: {
Description: "The host catalog type. Only `static` is supported.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}
func resourceHostCatalogStatic() *schema.Resource {
return &schema.Resource{
Description: "The static host catalog resource allows you to configure a Boundary static-type host catalog. Host " +
"catalogs are always part of a project, so a project resource should be used inline or you " +
"should have the project ID in hand to successfully configure a host catalog.",
CreateContext: resourceHostCatalogStaticCreate(false),
ReadContext: resourceHostCatalogStaticRead(false),
UpdateContext: resourceHostCatalogStaticUpdate(false),
DeleteContext: resourceHostCatalogStaticDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the host catalog.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The host catalog name. Defaults to the resource name.",
Type: schema.TypeString,
Optional: true,
},
DescriptionKey: {
Description: "The host catalog description.",
Type: schema.TypeString,
Optional: true,
},
ScopeIdKey: {
Description: "The scope ID in which the resource is created.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}
func setFromHostCatalogStaticResponseMap(d *schema.ResourceData, raw map[string]interface{}, hasTypeKey bool) error {
if err := d.Set(NameKey, raw["name"]); err != nil {
return err
}
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
return err
}
if err := d.Set(ScopeIdKey, raw["scope_id"]); err != nil {
return err
}
if hasTypeKey {
if err := d.Set(TypeKey, raw["type"]); err != nil {
return err
}
}
d.SetId(raw["id"].(string))
return nil
}
func resourceHostCatalogStaticCreate(hasTypeKey bool) schema.CreateContextFunc {
return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
var scopeId string
if scopeIdVal, ok := d.GetOk(ScopeIdKey); ok {
scopeId = scopeIdVal.(string)
} else {
return diag.Errorf("no scope ID provided")
}
typeStr := hostCatalogTypeStatic
if hasTypeKey {
// Perform backwards-compat validation
if typeVal, ok := d.GetOk(TypeKey); ok {
typeStr = typeVal.(string)
} else {
return diag.Errorf("no type provided")
}
switch typeStr {
case hostCatalogTypeStatic:
default:
return diag.Errorf("invalid type provided")
}
}
opts := []hostcatalogs.Option{}
nameVal, ok := d.GetOk(NameKey)
if ok {
nameStr := nameVal.(string)
opts = append(opts, hostcatalogs.WithName(nameStr))
}
descVal, ok := d.GetOk(DescriptionKey)
if ok {
descStr := descVal.(string)
opts = append(opts, hostcatalogs.WithDescription(descStr))
}
hcClient := hostcatalogs.NewClient(md.client)
hccr, err := hcClient.Create(ctx, typeStr, scopeId, opts...)
if err != nil {
return diag.Errorf("error creating host catalog: %v", err)
}
if hccr == nil {
return diag.Errorf("nil host catalog after create")
}
if err := setFromHostCatalogStaticResponseMap(d, hccr.GetResponse().Map, hasTypeKey); err != nil {
return diag.FromErr(err)
}
return nil
}
}
func resourceHostCatalogStaticRead(hasTypeKey bool) schema.ReadContextFunc {
return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
hcClient := hostcatalogs.NewClient(md.client)
hcrr, err := hcClient.Read(ctx, d.Id())
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
d.SetId("")
return nil
}
return diag.Errorf("error reading host catalog: %v", err)
}
if hcrr == nil {
return diag.Errorf("host catalog nil after read")
}
if err := setFromHostCatalogStaticResponseMap(d, hcrr.GetResponse().Map, hasTypeKey); err != nil {
return diag.FromErr(err)
}
return nil
}
}
func resourceHostCatalogStaticUpdate(hasTypeKey bool) schema.UpdateContextFunc {
return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
hcClient := hostcatalogs.NewClient(md.client)
opts := []hostcatalogs.Option{}
if d.HasChange(NameKey) {
opts = append(opts, hostcatalogs.DefaultName())
nameVal, ok := d.GetOk(NameKey)
if ok {
nameStr := nameVal.(string)
opts = append(opts, hostcatalogs.WithName(nameStr))
}
}
if d.HasChange(DescriptionKey) {
opts = append(opts, hostcatalogs.DefaultDescription())
descVal, ok := d.GetOk(DescriptionKey)
if ok {
descStr := descVal.(string)
opts = append(opts, hostcatalogs.WithDescription(descStr))
}
}
if len(opts) > 0 {
opts = append(opts, hostcatalogs.WithAutomaticVersioning(true))
hcrr, err := hcClient.Update(ctx, d.Id(), 0, opts...)
if err != nil {
return diag.Errorf("error updating host catalog: %v", err)
}
if hcrr == nil {
return diag.Errorf("host catalog nil after update")
}
if err := setFromHostCatalogStaticResponseMap(d, hcrr.GetResponse().Map, hasTypeKey); err != nil {
return diag.FromErr(err)
}
}
return nil
}
}
func resourceHostCatalogStaticDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
hcClient := hostcatalogs.NewClient(md.client)
_, err := hcClient.Delete(ctx, d.Id())
if err != nil {
return diag.Errorf("error deleting host catalog: %v", err)
}
return nil
}
@@ -20,50 +20,30 @@ const (
)
var (
projHostCatalog = `
resource "%s" "foo" {
projHostCatalog = fmt.Sprintf(`
resource "boundary_host_catalog" "foo" {
name = "foo"
description = "bar"
description = "%s"
scope_id = boundary_scope.proj1.id
%s
type = "static"
depends_on = [boundary_role.proj1_admin]
}`
}`, fooHostCatalogDescription)
projHostCatalogUpdate = `
resource "%s" "foo" {
projHostCatalogUpdate = fmt.Sprintf(`
resource "boundary_host_catalog" "foo" {
name = "foo"
description = "foo bar"
description = "%s"
scope_id = boundary_scope.proj1.id
%s
type = "static"
depends_on = [boundary_role.proj1_admin]
}`
}`, fooHostCatalogDescriptionUpdate)
)
func TestAccHostCatalogCreate(t *testing.T) {
t.Run("non-static", func(t *testing.T) {
t.Parallel()
testAccHostCatalog(t, false)
})
t.Run("static", func(t *testing.T) {
t.Parallel()
testAccHostCatalog(t, true)
})
}
func testAccHostCatalog(t *testing.T, static bool) {
tc := controller.NewTestController(t, tcConfig...)
defer tc.Shutdown()
url := tc.ApiAddrs()[0]
resName := "boundary_host_catalog"
typeStr := `type = "static"`
if static {
resName = "boundary_host_catalog_static"
typeStr = ""
}
hc, hcu := fmt.Sprintf(projHostCatalog, resName, typeStr), fmt.Sprintf(projHostCatalogUpdate, resName, typeStr)
fooName := fmt.Sprintf("%s.foo", resName)
var provider *schema.Provider
resource.Test(t, resource.TestCase{
ProviderFactories: providerFactories(&provider),
@@ -71,24 +51,24 @@ func testAccHostCatalog(t *testing.T, static bool) {
Steps: []resource.TestStep{
{
// test create
Config: testConfig(url, fooOrg, firstProjectFoo, hc),
Config: testConfig(url, fooOrg, firstProjectFoo, projHostCatalog),
Check: resource.ComposeTestCheckFunc(
testAccCheckScopeResourceExists(provider, "boundary_scope.org1"),
testAccCheckScopeResourceExists(provider, "boundary_scope.proj1"),
testAccCheckHostCatalogResourceExists(provider, fooName),
resource.TestCheckResourceAttr(fooName, DescriptionKey, fooHostCatalogDescription),
testAccCheckHostCatalogResourceExists(provider, "boundary_host_catalog.foo"),
resource.TestCheckResourceAttr("boundary_host_catalog.foo", DescriptionKey, fooHostCatalogDescription),
),
},
importStep(fooName),
importStep("boundary_host_catalog.foo"),
{
// test update
Config: testConfig(url, fooOrg, firstProjectFoo, hcu),
Config: testConfig(url, fooOrg, firstProjectFoo, projHostCatalogUpdate),
Check: resource.ComposeTestCheckFunc(
testAccCheckHostCatalogResourceExists(provider, fooName),
resource.TestCheckResourceAttr(fooName, DescriptionKey, fooHostCatalogDescriptionUpdate),
testAccCheckHostCatalogResourceExists(provider, "boundary_host_catalog.foo"),
resource.TestCheckResourceAttr("boundary_host_catalog.foo", DescriptionKey, fooHostCatalogDescriptionUpdate),
),
},
importStep(fooName),
importStep("boundary_host_catalog.foo"),
},
})
}
@@ -122,18 +102,20 @@ func testAccCheckHostCatalogResourceDestroy(t *testing.T, testProvider *schema.P
for _, rs := range s.RootModule().Resources {
switch rs.Type {
case "boundary_host_catalog", "boundary_host_catalog_static":
case "boundary_project":
continue
case "boundary_host_catalog":
id := rs.Primary.ID
hcClient := hostcatalogs.NewClient(md.client)
_, err := hcClient.Read(context.Background(), id)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
return fmt.Errorf("didn't get a 404 when reading destroyed host catalog %q: %v", id, err)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Status != http.StatusNotFound {
return fmt.Errorf("Didn't get a 404 when reading destroyed host catalog %q: %v", id, err)
}
default:
continue
t.Logf("Got unknown resource type %q", rs.Type)
}
}
return nil
@@ -11,105 +11,46 @@ import (
)
const (
hostSetHostIdsKey = "host_ids"
hostSetTypeStatic = "static"
hostsetHostIdsKey = "host_ids"
hostsetTypeStatic = "static"
)
func resourceHostSet() *schema.Resource {
func resourceHostset() *schema.Resource {
return &schema.Resource{
DeprecationMessage: "Deprecated: use `resource_host_set_static` instead.",
Description: "Deprecated: use `resource_host_set_static` instead.",
CreateContext: resourceHostSetStaticCreate,
ReadContext: resourceHostSetStaticRead,
UpdateContext: resourceHostSetStaticUpdate,
DeleteContext: resourceHostSetStaticDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the host set.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The host set name. Defaults to the resource name.",
Type: schema.TypeString,
Optional: true,
},
DescriptionKey: {
Description: "The host set description.",
Type: schema.TypeString,
Optional: true,
},
TypeKey: {
Description: "The type of host set",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
HostCatalogIdKey: {
Description: "The catalog for the host set.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
hostSetHostIdsKey: {
Description: "The list of host IDs contained in this set.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
}
func resourceHostSetStatic() *schema.Resource {
return &schema.Resource{
Description: "The host_set_static resource allows you to configure a Boundary host set. Host sets are " +
Description: "The host_set resource allows you to configure a Boundary host set. Host sets are " +
"always part of a host catalog, so a host catalog resource should be used inline or you " +
"should have the host catalog ID in hand to successfully configure a host set.",
CreateContext: resourceHostSetStaticCreate,
ReadContext: resourceHostSetStaticRead,
UpdateContext: resourceHostSetStaticUpdate,
DeleteContext: resourceHostSetStaticDelete,
CreateContext: resourceHostsetCreate,
ReadContext: resourceHostsetRead,
UpdateContext: resourceHostsetUpdate,
DeleteContext: resourceHostsetDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the host set.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The host set name. Defaults to the resource name.",
Description: "The hostset name. Defaults to the resource name.",
Type: schema.TypeString,
Optional: true,
},
DescriptionKey: {
Description: "The host set description.",
Description: "The hostset description.",
Type: schema.TypeString,
Optional: true,
},
TypeKey: {
Description: "The type of host set",
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Default: hostSetTypeStatic,
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
HostCatalogIdKey: {
Description: "The catalog for the host set.",
Description: "The catalog for the hostset.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
hostSetHostIdsKey: {
hostsetHostIdsKey: {
Description: "The list of host IDs contained in this set.",
Type: schema.TypeSet,
Optional: true,
@@ -119,27 +60,16 @@ func resourceHostSetStatic() *schema.Resource {
}
}
func setFromHostSetStaticResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
if err := d.Set(NameKey, raw["name"]); err != nil {
return err
}
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
return err
}
if err := d.Set(HostCatalogIdKey, raw["host_catalog_id"]); err != nil {
return err
}
if err := d.Set(TypeKey, raw["type"]); err != nil {
return err
}
if err := d.Set(hostSetHostIdsKey, raw["host_ids"]); err != nil {
return err
}
func setFromHostSetResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
d.Set(NameKey, raw["name"])
d.Set(DescriptionKey, raw["description"])
d.Set(HostCatalogIdKey, raw["host_catalog_id"])
d.Set(TypeKey, raw["type"])
d.Set(hostsetHostIdsKey, raw["host_ids"])
d.SetId(raw["id"].(string))
return nil
}
func resourceHostSetStaticCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
func resourceHostsetCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
var hostsetHostCatalogId string
@@ -150,7 +80,7 @@ func resourceHostSetStaticCreate(ctx context.Context, d *schema.ResourceData, me
}
var hostIds []string
if hostIdsVal, ok := d.GetOk(hostSetHostIdsKey); ok {
if hostIdsVal, ok := d.GetOk(hostsetHostIdsKey); ok {
list := hostIdsVal.(*schema.Set).List()
hostIds = make([]string, 0, len(list))
for _, i := range list {
@@ -169,7 +99,7 @@ func resourceHostSetStaticCreate(ctx context.Context, d *schema.ResourceData, me
switch typeStr {
// NOTE: When other types are added, ensure they don't accept hostSetIds if
// it's not allowed
case hostSetTypeStatic:
case hostsetTypeStatic:
default:
return diag.Errorf("invalid type provided")
}
@@ -195,7 +125,7 @@ func resourceHostSetStaticCreate(ctx context.Context, d *schema.ResourceData, me
if hscr == nil {
return diag.Errorf("nil host set after create")
}
raw := hscr.GetResponse().Map
raw := hscr.GetResponseMap()
if hostIds != nil {
hsshr, err := hsClient.SetHosts(ctx, hscr.Item.Id, hscr.Item.Version, hostIds)
@@ -205,23 +135,21 @@ func resourceHostSetStaticCreate(ctx context.Context, d *schema.ResourceData, me
if hsshr == nil {
return diag.Errorf("nil host set after setting hosts")
}
raw = hsshr.GetResponse().Map
raw = hsshr.GetResponseMap()
}
if err := setFromHostSetStaticResponseMap(d, raw); err != nil {
return diag.FromErr(err)
}
setFromHostSetResponseMap(d, raw)
return nil
}
func resourceHostSetStaticRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
func resourceHostsetRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
hsClient := hostsets.NewClient(md.client)
hsrr, err := hsClient.Read(ctx, d.Id())
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Status == int32(http.StatusNotFound) {
d.SetId("")
return nil
}
@@ -231,14 +159,12 @@ func resourceHostSetStaticRead(ctx context.Context, d *schema.ResourceData, meta
return diag.Errorf("host set nil after read")
}
if err := setFromHostSetStaticResponseMap(d, hsrr.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
setFromHostSetResponseMap(d, hsrr.GetResponseMap())
return nil
}
func resourceHostSetStaticUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
func resourceHostsetUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
hsClient := hostsets.NewClient(md.client)
@@ -275,21 +201,17 @@ func resourceHostSetStaticUpdate(ctx context.Context, d *schema.ResourceData, me
}
if d.HasChange(NameKey) {
if err := d.Set(NameKey, name); err != nil {
return diag.FromErr(err)
}
d.Set(NameKey, name)
}
if d.HasChange(DescriptionKey) {
if err := d.Set(DescriptionKey, desc); err != nil {
return diag.FromErr(err)
}
d.Set(DescriptionKey, desc)
}
// The above call may not actually happen, so we use d.Id() and automatic
// versioning here
if d.HasChange(hostSetHostIdsKey) {
if d.HasChange(hostsetHostIdsKey) {
var hostIds []string
if hostIdsVal, ok := d.GetOk(hostSetHostIdsKey); ok {
if hostIdsVal, ok := d.GetOk(hostsetHostIdsKey); ok {
hosts := hostIdsVal.(*schema.Set).List()
for _, host := range hosts {
hostIds = append(hostIds, host.(string))
@@ -299,15 +221,13 @@ func resourceHostSetStaticUpdate(ctx context.Context, d *schema.ResourceData, me
if err != nil {
return diag.Errorf("error updating hosts in host set: %v", err)
}
if err := d.Set(hostSetHostIdsKey, hostIds); err != nil {
return diag.FromErr(err)
}
d.Set(hostsetHostIdsKey, hostIds)
}
return nil
}
func resourceHostSetStaticDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
func resourceHostsetDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
hsClient := hostsets.NewClient(md.client)
@@ -1,356 +0,0 @@
package provider
import (
"context"
"encoding/json"
"errors"
"net/http"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/hostsets"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/go-secure-stdlib/parseutil"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
const (
hostSetTypePlugin = "plugin"
)
func resourceHostSetPlugin() *schema.Resource {
return &schema.Resource{
Description: "The host_set_plugin resource allows you to configure a Boundary host set. Host sets are " +
"always part of a host catalog, so a host catalog resource should be used inline or you " +
"should have the host catalog ID in hand to successfully configure a host set.",
CreateContext: resourceHostSetPluginCreate,
ReadContext: resourceHostSetPluginRead,
UpdateContext: resourceHostSetPluginUpdate,
DeleteContext: resourceHostSetPluginDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the host set.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The host set name. Defaults to the resource name.",
Type: schema.TypeString,
Optional: true,
},
DescriptionKey: {
Description: "The host set description.",
Type: schema.TypeString,
Optional: true,
},
TypeKey: {
Description: "The type of host set",
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Default: hostSetTypePlugin,
},
HostCatalogIdKey: {
Description: "The catalog for the host set.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
PreferredEndpointsKey: {
Description: "The ordered list of preferred endpoints.",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
SyncIntervalSecondsKey: {
Description: "The value to set for the sync interval seconds.",
Type: schema.TypeInt,
Optional: true,
ValidateDiagFunc: func(in interface{}, _ cty.Path) diag.Diagnostics {
val := in.(int)
switch {
case val >= -1:
return nil
default:
return diag.Errorf("invalid value for sync_interval_seconds")
}
},
},
AttributesJsonKey: {
Description: `The attributes for the host set. Either values encoded with the "jsonencode" function, pre-escaped JSON string, or a file:// or env:// path. Set to a string "null" or remove the block to clear all attributes in the host set.`,
Type: schema.TypeString,
Optional: true,
// If set to null in config and nothing comes from API, consider
// it the same. Same if config changes from empty to null.
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
switch {
case old == new:
return true
case old == "null" && new == "":
return true
case old == "" && new == "null":
return true
default:
return false
}
},
},
},
}
}
func setFromHostSetPluginResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
if err := d.Set(NameKey, raw[NameKey]); err != nil {
return err
}
if err := d.Set(DescriptionKey, raw[DescriptionKey]); err != nil {
return err
}
if err := d.Set(HostCatalogIdKey, raw[HostCatalogIdKey]); err != nil {
return err
}
if err := d.Set(TypeKey, raw[TypeKey]); err != nil {
return err
}
if err := d.Set(SyncIntervalSecondsKey, raw[SyncIntervalSecondsKey]); err != nil {
return err
}
if err := d.Set(PreferredEndpointsKey, raw[PreferredEndpointsKey]); err != nil {
return err
}
// Attributes stuff
{
attrRaw, ok := raw["attributes"]
switch ok {
case true:
encodedAttributes, err := json.Marshal(attrRaw)
if err != nil {
return err
}
if err := d.Set(AttributesJsonKey, string(encodedAttributes)); err != nil {
return err
}
default:
d.Set(AttributesJsonKey, nil)
}
}
d.SetId(raw[IDKey].(string))
return nil
}
func resourceHostSetPluginCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
var hostsetHostCatalogId string
if hostsetHostCatalogIdVal, ok := d.GetOk(HostCatalogIdKey); ok {
hostsetHostCatalogId = hostsetHostCatalogIdVal.(string)
} else {
return diag.Errorf("no host catalog ID provided")
}
opts := []hostsets.Option{}
var typeStr string
if typeVal, ok := d.GetOk(TypeKey); ok {
typeStr = typeVal.(string)
} else {
return diag.Errorf("no type provided")
}
switch typeStr {
// NOTE: When other types are added, ensure they don't accept hostSetIds if
// it's not allowed
case hostSetTypePlugin:
default:
return diag.Errorf("invalid type provided")
}
nameVal, ok := d.GetOk(NameKey)
if ok {
nameStr := nameVal.(string)
opts = append(opts, hostsets.WithName(nameStr))
}
descVal, ok := d.GetOk(DescriptionKey)
if ok {
descStr := descVal.(string)
opts = append(opts, hostsets.WithDescription(descStr))
}
syncIntervalSecondsVal, ok := d.GetOk(SyncIntervalSecondsKey)
if ok {
syncIntervalSecondsInt := syncIntervalSecondsVal.(int)
opts = append(opts, hostsets.WithSyncIntervalSeconds(int32(syncIntervalSecondsInt)))
}
var preferredEndpoints []string
if preferredEndpointsVal, ok := d.GetOk(PreferredEndpointsKey); ok {
list := preferredEndpointsVal.([]interface{})
preferredEndpoints = make([]string, 0, len(list))
for _, i := range list {
preferredEndpoints = append(preferredEndpoints, i.(string))
}
opts = append(opts, hostsets.WithPreferredEndpoints(preferredEndpoints))
}
attrsVal, ok := d.GetOk(AttributesJsonKey)
if ok {
attrsStr, err := parseutil.ParsePath(attrsVal.(string))
if err != nil && !errors.Is(err, parseutil.ErrNotAUrl) {
return diag.Errorf("error parsing path with attributes: %v", err)
}
switch attrsStr {
case "null":
opts = append(opts, hostsets.DefaultAttributes())
default:
// What comes in is json-encoded but we want to set a
// map[string]interface{} so we unmarshal it and set that
var m map[string]interface{}
if err := json.Unmarshal([]byte(attrsStr), &m); err != nil {
return diag.Errorf("error unmarshaling attributes: %v", err)
}
opts = append(opts, hostsets.WithAttributes(m))
}
}
hsClient := hostsets.NewClient(md.client)
hscr, err := hsClient.Create(ctx, hostsetHostCatalogId, opts...)
if err != nil {
return diag.Errorf("error creating host set: %v", err)
}
if hscr == nil {
return diag.Errorf("nil host set after create")
}
if err := setFromHostSetPluginResponseMap(d, hscr.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
return nil
}
func resourceHostSetPluginRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
hsClient := hostsets.NewClient(md.client)
hsrr, err := hsClient.Read(ctx, d.Id())
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
d.SetId("")
return nil
}
return diag.Errorf("error reading host set: %v", err)
}
if hsrr == nil {
return diag.Errorf("host set nil after read")
}
if err := setFromHostSetPluginResponseMap(d, hsrr.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
return nil
}
func resourceHostSetPluginUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
hsClient := hostsets.NewClient(md.client)
opts := []hostsets.Option{}
if d.HasChange(NameKey) {
opts = append(opts, hostsets.DefaultName())
nameVal, ok := d.GetOk(NameKey)
if ok {
nameStr := nameVal.(string)
opts = append(opts, hostsets.WithName(nameStr))
}
}
if d.HasChange(DescriptionKey) {
opts = append(opts, hostsets.DefaultDescription())
descVal, ok := d.GetOk(DescriptionKey)
if ok {
descStr := descVal.(string)
opts = append(opts, hostsets.WithDescription(descStr))
}
}
if d.HasChange(SyncIntervalSecondsKey) {
opts = append(opts, hostsets.DefaultSyncIntervalSeconds())
syncIntervalSecondsVal, ok := d.GetOk(SyncIntervalSecondsKey)
if ok {
syncIntervalSecondsInt := syncIntervalSecondsVal.(int)
opts = append(opts, hostsets.WithSyncIntervalSeconds(int32(syncIntervalSecondsInt)))
}
}
if d.HasChange(PreferredEndpointsKey) {
opts = append(opts, hostsets.DefaultPreferredEndpoints())
var preferredEndpoints []string
if preferredEndpointsVal, ok := d.GetOk(PreferredEndpointsKey); ok {
list := preferredEndpointsVal.([]interface{})
preferredEndpoints = make([]string, 0, len(list))
for _, i := range list {
preferredEndpoints = append(preferredEndpoints, i.(string))
}
opts = append(opts, hostsets.WithPreferredEndpoints(preferredEndpoints))
}
}
if d.HasChange(AttributesJsonKey) {
attrsVal, ok := d.GetOk(AttributesJsonKey)
if ok {
attrsStr, err := parseutil.ParsePath(attrsVal.(string))
if err != nil && !errors.Is(err, parseutil.ErrNotAUrl) {
return diag.Errorf("error parsing path with attributes: %v", err)
}
switch attrsStr {
case "null", "":
opts = append(opts, hostsets.DefaultAttributes())
default:
// What comes in is json-encoded but we want to set a
// map[string]interface{} so we unmarshal it and set that
var m map[string]interface{}
if err := json.Unmarshal([]byte(attrsStr), &m); err != nil {
return diag.Errorf("error unmarshaling attributes: %v", err)
}
opts = append(opts, hostsets.WithAttributes(m))
}
} else {
opts = append(opts, hostsets.DefaultAttributes())
}
}
if len(opts) > 0 {
opts = append(opts, hostsets.WithAutomaticVersioning(true))
hsrr, err := hsClient.Update(ctx, d.Id(), 0, opts...)
if err != nil {
return diag.Errorf("error updating host set: %v", err)
}
if hsrr == nil {
return diag.Errorf("host set nil after update")
}
if err := setFromHostSetPluginResponseMap(d, hsrr.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
}
return nil
}
func resourceHostSetPluginDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
hsClient := hostsets.NewClient(md.client)
_, err := hsClient.Delete(ctx, d.Id())
if err != nil {
return diag.Errorf("error deleting host set: %s", err.Error())
}
return nil
}
@@ -1,311 +0,0 @@
package provider
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"testing"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/hostsets"
"github.com/hashicorp/boundary/testing/controller"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var currentPluginHostSetAttributesValue string
func TestAccHostSetPlugin(t *testing.T) {
t.Parallel()
initialPreferredEndpoints := []string{"cidr:1.2.3.4/32", "dns:bar.foo.com"}
initialPreferredEndpointsRaw, err := json.Marshal(initialPreferredEndpoints)
require.NoError(t, err)
initialPreferredEndpointsStr := string(initialPreferredEndpointsRaw)
updatePreferredEndpoints := []string{"dns:bar.foo.com", "cidr:4.3.2.0/24"}
updatePreferredEndpointsRaw, err := json.Marshal(updatePreferredEndpoints)
require.NoError(t, err)
updatePreferredEndpointsStr := string(updatePreferredEndpointsRaw)
initialSyncIntervalSeconds := -1
updateSyncIntervalSeconds := 60
catalogBlock := `
resource "boundary_host_catalog_plugin" "foo" {
scope_id = boundary_scope.proj1.id
depends_on = [boundary_role.proj1_admin]
plugin_name = "loopback"
}`
hostSetBlock := `
resource "boundary_host_set_plugin" "foo" {
host_catalog_id = boundary_host_catalog_plugin.foo.id
name = "test"
description = "test hostset"
preferred_endpoints = %s
sync_interval_seconds = %d
%s
}`
tc := controller.NewTestController(t, tcConfig...)
defer tc.Shutdown()
url := tc.ApiAddrs()[0]
fooSetName := "boundary_host_set_plugin.foo"
attrBlock1 := `attributes_json = jsonencode({
foo = "bar"
zip = "zap"
})`
attrBlock2 := `attributes_json = jsonencode({
foo = "bar"
zip = "zoop"
})`
attrBlock3 := `attributes_json = jsonencode({
foo = "bar"
zip = "zoop"
})`
attrBlock4 := ``
attrBlock5 := `attributes_json = jsonencode({
foo = "bar"
zip = "zoop"
})`
attrBlock6 := `attributes_json = "null"`
hsBlock := fmt.Sprintf(hostSetBlock, initialPreferredEndpointsStr, initialSyncIntervalSeconds, attrBlock1)
hsUpdate1Block := fmt.Sprintf(hostSetBlock, updatePreferredEndpointsStr, updateSyncIntervalSeconds, attrBlock2)
hsUpdate2Block := fmt.Sprintf(hostSetBlock, updatePreferredEndpointsStr, updateSyncIntervalSeconds, attrBlock3)
hsUpdate3Block := fmt.Sprintf(hostSetBlock, updatePreferredEndpointsStr, updateSyncIntervalSeconds, attrBlock4)
hsUpdate4Block := fmt.Sprintf(hostSetBlock, updatePreferredEndpointsStr, updateSyncIntervalSeconds, attrBlock5)
hsUpdate5Block := fmt.Sprintf(hostSetBlock, updatePreferredEndpointsStr, updateSyncIntervalSeconds, attrBlock6)
var provider *schema.Provider
resource.Test(t, resource.TestCase{
ProviderFactories: providerFactories(&provider),
CheckDestroy: testAccCheckHostSetPluginResourceDestroy(t, provider),
Steps: []resource.TestStep{
{
// test project hostset create
Config: testConfig(url, fooOrg, firstProjectFoo, catalogBlock, hsBlock),
Check: resource.ComposeTestCheckFunc(
testAccCheckHostSetPluginResourceExists(provider, fooSetName, expectedAttributesStatePreviouslyEmptyNowSet),
resource.TestCheckResourceAttr(fooSetName, NameKey, "test"),
resource.TestCheckResourceAttr(fooSetName, DescriptionKey, "test hostset"),
resource.TestCheckResourceAttr(fooSetName, SyncIntervalSecondsKey, fmt.Sprintf("%d", initialSyncIntervalSeconds)),
testAccCheckHostSetPluginPreferredEndpoints(t, provider, fooSetName, initialPreferredEndpoints),
),
ExpectNonEmptyPlan: true,
},
importStep(fooSetName),
{
// test project hostset update
Config: testConfig(url, fooOrg, firstProjectFoo, catalogBlock, hsUpdate1Block),
Check: resource.ComposeTestCheckFunc(
testAccCheckHostSetPluginResourceExists(provider, fooSetName, expectedAttributesStatePreviouslySetButChanged),
resource.TestCheckResourceAttr(fooSetName, NameKey, "test"),
resource.TestCheckResourceAttr(fooSetName, DescriptionKey, "test hostset"),
resource.TestCheckResourceAttr(fooSetName, SyncIntervalSecondsKey, fmt.Sprintf("%d", updateSyncIntervalSeconds)),
testAccCheckHostSetPluginPreferredEndpoints(t, provider, fooSetName, updatePreferredEndpoints),
),
ExpectNonEmptyPlan: true,
},
importStep(fooSetName),
{
// test project hostset update
Config: testConfig(url, fooOrg, firstProjectFoo, catalogBlock, hsUpdate2Block),
Check: resource.ComposeTestCheckFunc(
testAccCheckHostSetPluginResourceExists(provider, fooSetName, expectedAttributesStatePreviouslySetNoChange),
resource.TestCheckResourceAttr(fooSetName, NameKey, "test"),
resource.TestCheckResourceAttr(fooSetName, DescriptionKey, "test hostset"),
resource.TestCheckResourceAttr(fooSetName, SyncIntervalSecondsKey, fmt.Sprintf("%d", updateSyncIntervalSeconds)),
testAccCheckHostSetPluginPreferredEndpoints(t, provider, fooSetName, updatePreferredEndpoints),
),
ExpectNonEmptyPlan: true,
},
importStep(fooSetName),
{
// test project hostset update
Config: testConfig(url, fooOrg, firstProjectFoo, catalogBlock, hsUpdate3Block),
Check: resource.ComposeTestCheckFunc(
testAccCheckHostSetPluginResourceExists(provider, fooSetName, expectedAttributesStatePreviouslySetNowEmpty),
resource.TestCheckResourceAttr(fooSetName, NameKey, "test"),
resource.TestCheckResourceAttr(fooSetName, DescriptionKey, "test hostset"),
resource.TestCheckResourceAttr(fooSetName, SyncIntervalSecondsKey, fmt.Sprintf("%d", updateSyncIntervalSeconds)),
testAccCheckHostSetPluginPreferredEndpoints(t, provider, fooSetName, updatePreferredEndpoints),
),
ExpectNonEmptyPlan: true,
},
importStep(fooSetName),
{
// test project hostset update
Config: testConfig(url, fooOrg, firstProjectFoo, catalogBlock, hsUpdate4Block),
Check: resource.ComposeTestCheckFunc(
testAccCheckHostSetPluginResourceExists(provider, fooSetName, expectedAttributesStatePreviouslyEmptyNowSet),
resource.TestCheckResourceAttr(fooSetName, NameKey, "test"),
resource.TestCheckResourceAttr(fooSetName, DescriptionKey, "test hostset"),
resource.TestCheckResourceAttr(fooSetName, SyncIntervalSecondsKey, fmt.Sprintf("%d", updateSyncIntervalSeconds)),
testAccCheckHostSetPluginPreferredEndpoints(t, provider, fooSetName, updatePreferredEndpoints),
),
ExpectNonEmptyPlan: true,
},
importStep(fooSetName),
{
// test project hostset update
Config: testConfig(url, fooOrg, firstProjectFoo, catalogBlock, hsUpdate5Block),
Check: resource.ComposeTestCheckFunc(
testAccCheckHostSetPluginResourceExists(provider, fooSetName, expectedAttributesStatePreviouslySetNowEmpty),
resource.TestCheckResourceAttr(fooSetName, NameKey, "test"),
resource.TestCheckResourceAttr(fooSetName, DescriptionKey, "test hostset"),
resource.TestCheckResourceAttr(fooSetName, SyncIntervalSecondsKey, fmt.Sprintf("%d", updateSyncIntervalSeconds)),
testAccCheckHostSetPluginPreferredEndpoints(t, provider, fooSetName, updatePreferredEndpoints),
),
ExpectNonEmptyPlan: true,
},
importStep(fooSetName),
},
})
}
func testAccCheckHostSetPluginResourceExists(testProvider *schema.Provider, name string, expAttrs expectedAttributesState) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Not found: %s", name)
}
id := rs.Primary.ID
if id == "" {
return fmt.Errorf("No ID is set")
}
md := testProvider.Meta().(*metaData)
hostsetsClient := hostsets.NewClient(md.client)
hsrr, err := hostsetsClient.Read(context.Background(), id)
if err != nil {
return fmt.Errorf("Got an error when reading hostset %q: %v", id, err)
}
attrs := hsrr.GetResponse().Map["attributes"]
switch expAttrs {
case expectedAttributesStatePreviouslyEmptyNowSet:
if currentPluginHostSetAttributesValue != "" {
return fmt.Errorf("expected no previous attributes value, got %s", currentPluginHostSetAttributesValue)
}
if attrs == nil {
return errors.New("expected non-empty new attributes value")
}
attrsVal, err := json.Marshal(attrs)
if err != nil {
return fmt.Errorf("error marshaling attrs: %w", err)
}
currentPluginHostSetAttributesValue = string(attrsVal)
case expectedAttributesStatePreviouslySetButChanged:
if currentPluginHostSetAttributesValue == "" {
return errors.New("expected previous attributes value")
}
if attrs == nil {
return errors.New("expected non-empty new attributes value")
}
attrsVal, err := json.Marshal(attrs)
if err != nil {
return fmt.Errorf("error marshaling attrs: %w", err)
}
if string(attrsVal) == currentPluginHostSetAttributesValue {
return errors.New("expected changed attrs value")
}
currentPluginHostSetAttributesValue = string(attrsVal)
case expectedAttributesStatePreviouslySetNoChange:
if currentPluginHostSetAttributesValue == "" {
return errors.New("expected previous attributes value")
}
if attrs == nil {
return errors.New("expected non-empty new attributes value")
}
attrsVal, err := json.Marshal(attrs)
if err != nil {
return fmt.Errorf("error marshaling attrs: %w", err)
}
if string(attrsVal) == "" {
return errors.New("expected non-empty new attributes value")
}
if string(attrsVal) != currentPluginHostSetAttributesValue {
return errors.New("expected same attributes value")
}
case expectedAttributesStatePreviouslySetNowEmpty:
if currentPluginHostSetAttributesValue == "" {
return errors.New("expected previous attributes value")
}
if attrs != nil {
return fmt.Errorf("expected empty new attributes value, got %s", attrs)
}
currentPluginHostSetAttributesValue = ""
}
return nil
}
}
func testAccCheckHostSetPluginResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
return func(s *terraform.State) error {
if testProvider.Meta() == nil {
t.Fatal("got nil provider metadata")
}
md := testProvider.Meta().(*metaData)
for _, rs := range s.RootModule().Resources {
switch rs.Type {
case "boundary_host_set":
id := rs.Primary.ID
hostsetsClient := hostsets.NewClient(md.client)
_, err := hostsetsClient.Read(context.Background(), id)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
return fmt.Errorf("didn't get a 404 when reading destroyed host set %q: %v", id, apiErr)
}
default:
continue
}
}
return nil
}
}
func testAccCheckHostSetPluginPreferredEndpoints(t *testing.T, testProvider *schema.Provider, name string, wantPreferredEndpoints []string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("host set resource not found: %s", name)
}
id := rs.Primary.ID
if id == "" {
return fmt.Errorf("host set resource ID is not set")
}
// check boundary to ensure it matches
md := testProvider.Meta().(*metaData)
client := md.client.Clone()
hstClient := hostsets.NewClient(client)
hs, err := hstClient.Read(context.Background(), id)
if err != nil {
return fmt.Errorf("Got an error when reading hostset %q: %v", id, err)
}
assert.Equal(t, wantPreferredEndpoints, hs.Item.PreferredEndpoints)
return nil
}
}
@@ -14,104 +14,97 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)
func TestAccHostSetStatic(t *testing.T) {
t.Run("non-static", func(t *testing.T) {
t.Parallel()
testAccHostSetStatic(t, false)
})
t.Run("static", func(t *testing.T) {
t.Parallel()
testAccHostSetStatic(t, true)
})
var (
fooHostset = `
resource "boundary_host_catalog" "foo" {
scope_id = boundary_scope.proj1.id
type = "static"
depends_on = [boundary_role.proj1_admin]
}
func testAccHostSetStatic(t *testing.T, static bool) {
catalogBlock := `
resource "%s" "foo" {
%s
scope_id = boundary_scope.proj1.id
depends_on = [boundary_role.proj1_admin]
}`
resource "boundary_host" "foo" {
type = "static"
host_catalog_id = boundary_host_catalog.foo.id
address = "10.0.0.1"
}
hostBlock := `
resource "%s" "foo" {
host_catalog_id = %s.foo.id
%s
address = "10.0.0.1"
}`
resource "boundary_host_set" "foo" {
type = "static"
name = "test"
description = "test hostset"
host_catalog_id = boundary_host_catalog.foo.id
host_ids = [boundary_host.foo.id]
}`
hostSetBlock := `
resource "%s" "foo" {
host_catalog_id = %s.foo.id
name = "test"
description = "test hostset"
%s
host_ids = [%s.foo.id%s]
}`
fooHostsetUpdate = `
resource "boundary_host_catalog" "foo" {
scope_id = boundary_scope.proj1.id
type = "static"
depends_on = [boundary_role.proj1_admin]
}
host2Block := `
resource "%s" "bar" {
host_catalog_id = %s.foo.id
%s
address = "10.0.0.2"
}`
resource "boundary_host" "foo" {
type = "static"
host_catalog_id = boundary_host_catalog.foo.id
address = "10.0.0.1"
}
resource "boundary_host" "bar" {
type = "static"
host_catalog_id = boundary_host_catalog.foo.id
address = "10.0.0.2"
}
resource "boundary_host_set" "foo" {
type = "static"
host_catalog_id = boundary_host_catalog.foo.id
name = "test"
description = "test hostset"
host_ids = [
boundary_host.foo.id,
boundary_host.bar.id,
]
}`
)
func TestAccHostset(t *testing.T) {
tc := controller.NewTestController(t, tcConfig...)
defer tc.Shutdown()
// org := iam.TestOrg(t, tc.IamRepo())
url := tc.ApiAddrs()[0]
catalogName := "boundary_host_catalog"
hostName := "boundary_host"
setName := "boundary_host_set"
typeStr := `type = "static"`
if static {
catalogName = "boundary_host_catalog_static"
hostName = "boundary_host_static"
setName = "boundary_host_set_static"
typeStr = ""
}
fooSetName := fmt.Sprintf("%s.foo", setName)
fooHostName := fmt.Sprintf("%s.foo", hostName)
barHostName := fmt.Sprintf("%s.bar", hostName)
hcBlock := fmt.Sprintf(catalogBlock, catalogName, typeStr)
hBlock := fmt.Sprintf(hostBlock, hostName, catalogName, typeStr)
h2Block := fmt.Sprintf(host2Block, hostName, catalogName, typeStr)
hsBlock := fmt.Sprintf(hostSetBlock, setName, catalogName, typeStr, hostName, "")
hsUpdateBlock := fmt.Sprintf(hostSetBlock, setName, catalogName, typeStr, hostName, fmt.Sprintf(`, %s.bar.id`, hostName))
var provider *schema.Provider
resource.Test(t, resource.TestCase{
ProviderFactories: providerFactories(&provider),
CheckDestroy: testAccCheckHostSetStaticResourceDestroy(t, provider),
CheckDestroy: testAccCheckHostsetResourceDestroy(t, provider),
Steps: []resource.TestStep{
{
// test project hostset create
Config: testConfig(url, fooOrg, firstProjectFoo, hcBlock, hBlock, hsBlock),
Config: testConfig(url, fooOrg, firstProjectFoo, fooHostset),
Check: resource.ComposeTestCheckFunc(
testAccCheckHostSetStaticResourceExists(provider, fooSetName),
testAccCheckHostSetStaticHostIDsSet(provider, fooSetName, []string{fooHostName}),
resource.TestCheckResourceAttr(fooSetName, "name", "test"),
resource.TestCheckResourceAttr(fooSetName, "description", "test hostset"),
testAccCheckHostsetResourceExists(provider, "boundary_host_set.foo"),
testAccCheckHostsetHostIDsSet(provider, "boundary_host_set.foo", []string{"boundary_host.foo"}),
resource.TestCheckResourceAttr("boundary_host_set.foo", "name", "test"),
resource.TestCheckResourceAttr("boundary_host_set.foo", "description", "test hostset"),
),
},
importStep(fooSetName),
importStep("boundary_host_set.foo"),
{
// test project hostset update
Config: testConfig(url, fooOrg, firstProjectFoo, hcBlock, hBlock, h2Block, hsUpdateBlock),
Config: testConfig(url, fooOrg, firstProjectFoo, fooHostsetUpdate),
Check: resource.ComposeTestCheckFunc(
testAccCheckHostSetStaticResourceExists(provider, fooSetName),
testAccCheckHostSetStaticHostIDsSet(provider, fooSetName, []string{fooHostName, barHostName}),
resource.TestCheckResourceAttr(fooSetName, "name", "test"),
resource.TestCheckResourceAttr(fooSetName, "description", "test hostset"),
testAccCheckHostsetResourceExists(provider, "boundary_host_set.foo"),
testAccCheckHostsetHostIDsSet(provider, "boundary_host_set.foo", []string{"boundary_host.foo", "boundary_host.bar"}),
resource.TestCheckResourceAttr("boundary_host_set.foo", "name", "test"),
resource.TestCheckResourceAttr("boundary_host_set.foo", "description", "test hostset"),
),
},
importStep(fooSetName),
importStep("boundary_host_set.foo"),
},
})
}
func testAccCheckHostSetStaticResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
func testAccCheckHostsetResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
@@ -134,7 +127,7 @@ func testAccCheckHostSetStaticResourceExists(testProvider *schema.Provider, name
}
}
func testAccCheckHostSetStaticHostIDsSet(testProvider *schema.Provider, name string, wantHostIDs []string) resource.TestCheckFunc {
func testAccCheckHostsetHostIDsSet(testProvider *schema.Provider, name string, wantHostIDs []string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
@@ -192,7 +185,7 @@ func testAccCheckHostSetStaticHostIDsSet(testProvider *schema.Provider, name str
}
}
func testAccCheckHostSetStaticResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
func testAccCheckHostsetResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
return func(s *terraform.State) error {
if testProvider.Meta() == nil {
t.Fatal("got nil provider metadata")
@@ -201,6 +194,8 @@ func testAccCheckHostSetStaticResourceDestroy(t *testing.T, testProvider *schema
for _, rs := range s.RootModule().Resources {
switch rs.Type {
case "boundary_scope":
continue
case "boundary_host_set":
id := rs.Primary.ID
@@ -208,8 +203,8 @@ func testAccCheckHostSetStaticResourceDestroy(t *testing.T, testProvider *schema
hostsetsClient := hostsets.NewClient(md.client)
_, err := hostsetsClient.Read(context.Background(), id)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
return fmt.Errorf("didn't get a 404 when reading destroyed host set %q: %v", id, apiErr)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Status != http.StatusNotFound {
return fmt.Errorf("Didn't get a 404 when reading destroyed host set %q: %v", id, apiErr)
}
default:
@@ -15,59 +15,53 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)
func TestAccHost(t *testing.T) {
t.Run("non-static", func(t *testing.T) {
t.Parallel()
testAccHost(t, false)
})
t.Run("static", func(t *testing.T) {
t.Parallel()
testAccHost(t, true)
})
const (
fooHostAddress = "10.0.0.1"
fooHostAddressUpdate = "10.10.0.0"
)
var (
projHost = fmt.Sprintf(`
resource "boundary_host_catalog" "foo" {
name = "test"
description = "test catalog"
scope_id = boundary_scope.proj1.id
type = "static"
depends_on = [boundary_role.proj1_admin]
}
func testAccHost(t *testing.T, static bool) {
const (
fooHostAddress = "10.0.0.1"
fooHostAddressUpdate = "10.10.0.0"
)
resource "boundary_host" "foo" {
type = "static"
host_catalog_id = boundary_host_catalog.foo.id
name = "test"
description = "test host"
address = "%s"
}`, fooHostAddress)
projCatalog := `
resource "%s" "foo" {
name = "test"
description = "test catalog"
%s
scope_id = boundary_scope.proj1.id
depends_on = [boundary_role.proj1_admin]
}`
projHostUpdate = fmt.Sprintf(`
resource "boundary_host_catalog" "foo" {
name = "test"
description = "test catalog"
scope_id = boundary_scope.proj1.id
type = "static"
depends_on = [boundary_role.proj1_admin]
}
projHost := `
resource "%s" "foo" {
host_catalog_id = %s.foo.id
name = "test"
description = "test host"
%s
address = "%s"
}`
resource "boundary_host" "foo" {
type = "static"
host_catalog_id = boundary_host_catalog.foo.id
name = "test"
description = "test host"
address = "%s"
}`, fooHostAddressUpdate)
)
func TestAccHost(t *testing.T) {
tc := controller.NewTestController(t, tcConfig...)
defer tc.Shutdown()
// org := iam.TestOrg(t, tc.IamRepo())
url := tc.ApiAddrs()[0]
catalogName := "boundary_host_catalog"
hostName := "boundary_host"
typeStr := `type = "static"`
if static {
catalogName = "boundary_host_catalog_static"
hostName = "boundary_host_static"
typeStr = ""
}
fooHostName := fmt.Sprintf("%s.foo", hostName)
hcBlock := fmt.Sprintf(projCatalog, catalogName, typeStr)
hostBlock := fmt.Sprintf(projHost, hostName, catalogName, typeStr, fooHostAddress)
hostUpdateBlock := fmt.Sprintf(projHost, hostName, catalogName, typeStr, fooHostAddressUpdate)
var provider *schema.Provider
resource.Test(t, resource.TestCase{
ProviderFactories: providerFactories(&provider),
@@ -75,28 +69,28 @@ func testAccHost(t *testing.T, static bool) {
Steps: []resource.TestStep{
{
// test project host create
Config: testConfig(url, fooOrg, firstProjectFoo, hcBlock, hostBlock),
Config: testConfig(url, fooOrg, firstProjectFoo, projHost),
Check: resource.ComposeTestCheckFunc(
testAccCheckHostAttributeSet(provider, fooHostName, "address", fooHostAddress),
testAccCheckHostResourceExists(provider, fooHostName),
resource.TestCheckResourceAttr(fooHostName, "name", "test"),
resource.TestCheckResourceAttr(fooHostName, "description", "test host"),
resource.TestCheckResourceAttr(fooHostName, "address", fooHostAddress),
testAccCheckHostAttributeSet(provider, "boundary_host.foo", "address", fooHostAddress),
testAccCheckHostResourceExists(provider, "boundary_host.foo"),
resource.TestCheckResourceAttr("boundary_host.foo", "name", "test"),
resource.TestCheckResourceAttr("boundary_host.foo", "description", "test host"),
resource.TestCheckResourceAttr("boundary_host.foo", "address", fooHostAddress),
),
},
importStep(fooHostName),
importStep("boundary_host.foo"),
{
// test project host update
Config: testConfig(url, fooOrg, firstProjectFoo, hcBlock, hostUpdateBlock),
Config: testConfig(url, fooOrg, firstProjectFoo, projHostUpdate),
Check: resource.ComposeTestCheckFunc(
testAccCheckHostAttributeSet(provider, fooHostName, "address", fooHostAddressUpdate),
testAccCheckHostResourceExists(provider, fooHostName),
resource.TestCheckResourceAttr(fooHostName, "name", "test"),
resource.TestCheckResourceAttr(fooHostName, "description", "test host"),
resource.TestCheckResourceAttr(fooHostName, "address", fooHostAddressUpdate),
testAccCheckHostAttributeSet(provider, "boundary_host.foo", "address", fooHostAddressUpdate),
testAccCheckHostResourceExists(provider, "boundary_host.foo"),
resource.TestCheckResourceAttr("boundary_host.foo", "name", "test"),
resource.TestCheckResourceAttr("boundary_host.foo", "description", "test host"),
resource.TestCheckResourceAttr("boundary_host.foo", "address", fooHostAddressUpdate),
),
},
importStep(fooHostName),
importStep("boundary_host.foo"),
},
})
}
@@ -171,14 +165,16 @@ func testAccCheckHostResourceDestroy(t *testing.T, testProvider *schema.Provider
for _, rs := range s.RootModule().Resources {
switch rs.Type {
case "boundary_scope":
continue
case "boundary_host":
id := rs.Primary.ID
hostsClient := hosts.NewClient(md.client)
_, err := hostsClient.Read(context.Background(), id)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
return fmt.Errorf("didn't get a 404 when reading destroyed host %q: %v", id, apiErr)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Status != http.StatusNotFound {
return fmt.Errorf("Didn't get a 404 when reading destroyed host %q: %v", id, apiErr)
}
default:
-227
View File
@@ -1,227 +0,0 @@
package provider
import (
"context"
"net/http"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/managedgroups"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
const (
managedGroupFilterKey = "filter"
)
func resourceManagedGroup() *schema.Resource {
return &schema.Resource{
Description: "The managed group resource allows you to configure a Boundary group.",
CreateContext: resourceManagedGroupCreate,
ReadContext: resourceManagedGroupRead,
UpdateContext: resourceManagedGroupUpdate,
DeleteContext: resourceManagedGroupDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the group.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The managed group name. Defaults to the resource name.",
Type: schema.TypeString,
Optional: true,
},
DescriptionKey: {
Description: "The managed group description.",
Type: schema.TypeString,
Optional: true,
},
AuthMethodIdKey: {
Description: "The resource ID for the auth method.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
managedGroupFilterKey: {
Description: "Boolean expression to filter the workers for this managed group.",
Type: schema.TypeString,
Required: true,
},
},
}
}
func setFromManagedGroupResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
if err := d.Set(NameKey, raw[NameKey]); err != nil {
return err
}
if err := d.Set(DescriptionKey, raw[DescriptionKey]); err != nil {
return err
}
if err := d.Set(AuthMethodIdKey, raw[AuthMethodIdKey]); err != nil {
return err
}
if attrsVal, ok := raw["attributes"]; ok {
attrs := attrsVal.(map[string]interface{})
if v, ok := attrs[managedGroupFilterKey]; ok {
if err := d.Set(managedGroupFilterKey, v); err != nil {
return err
}
}
}
d.SetId(raw[IDKey].(string))
return nil
}
func resourceManagedGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
grpClient := managedgroups.NewClient(md.client)
var authMethodId string
authMethodVal, ok := d.GetOk(AuthMethodIdKey)
if !ok {
return diag.Errorf("no auth method ID provided")
}
authMethodId = authMethodVal.(string)
var opts []managedgroups.Option
nameVal, ok := d.GetOk(NameKey)
if ok {
nameStr := nameVal.(string)
opts = append(opts, managedgroups.WithName(nameStr))
}
descVal, ok := d.GetOk(DescriptionKey)
if ok {
descStr := descVal.(string)
opts = append(opts, managedgroups.WithDescription(descStr))
}
v, ok := d.GetOk(managedGroupFilterKey)
if ok {
str := v.(string)
opts = append(opts, managedgroups.WithOidcManagedGroupFilter(str))
}
grp, err := grpClient.Create(ctx, authMethodId, opts...)
if err != nil {
return diag.Errorf("error creating managed group: %v", err)
}
if grp == nil {
return diag.Errorf("managed group nil after create")
}
raw := grp.GetResponse().Map
if err := setFromManagedGroupResponseMap(d, raw); err != nil {
return diag.FromErr(err)
}
return nil
}
func resourceManagedGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
grpClient := managedgroups.NewClient(md.client)
grp, err := grpClient.Read(ctx, d.Id())
if err != nil {
if apiErr := api.AsServerError(err); apiErr.Response().StatusCode() == http.StatusNotFound {
d.SetId("")
return nil
}
return diag.Errorf("error reading managed group: %v", err)
}
if grp == nil {
return diag.Errorf("managed group nil after read")
}
if err := setFromManagedGroupResponseMap(d, grp.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
return nil
}
func resourceManagedGroupUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
grpClient := managedgroups.NewClient(md.client)
var opts []managedgroups.Option
var name *string
if d.HasChange(NameKey) {
opts = append(opts, managedgroups.DefaultName())
nameVal, ok := d.GetOk(NameKey)
if ok {
nameStr := nameVal.(string)
name = &nameStr
opts = append(opts, managedgroups.WithName(nameStr))
}
}
var desc *string
if d.HasChange(DescriptionKey) {
opts = append(opts, managedgroups.DefaultDescription())
descVal, ok := d.GetOk(DescriptionKey)
if ok {
descStr := descVal.(string)
desc = &descStr
opts = append(opts, managedgroups.WithDescription(descStr))
}
}
var filter *string
if d.HasChange(managedGroupFilterKey) {
if f, ok := d.GetOk(managedGroupFilterKey); ok {
filterStr := f.(string)
filter = &filterStr
opts = append(opts, managedgroups.WithOidcManagedGroupFilter(filterStr))
}
}
if len(opts) > 0 {
opts = append(opts, managedgroups.WithAutomaticVersioning(true))
_, err := grpClient.Update(ctx, d.Id(), 0, opts...)
if err != nil {
return diag.Errorf("error updating managed group: %v", err)
}
}
if d.HasChange(NameKey) {
if err := d.Set(NameKey, name); err != nil {
return diag.FromErr(err)
}
}
if d.HasChange(DescriptionKey) {
if err := d.Set(DescriptionKey, desc); err != nil {
return diag.FromErr(err)
}
}
if d.HasChange(managedGroupFilterKey) {
if err := d.Set(managedGroupFilterKey, filter); err != nil {
return diag.FromErr(err)
}
}
return nil
}
func resourceManagedGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
grpClient := managedgroups.NewClient(md.client)
_, err := grpClient.Delete(ctx, d.Id())
if err != nil {
return diag.Errorf("error calling delete managed group: %s", err.Error())
}
return nil
}
@@ -1,133 +0,0 @@
package provider
import (
"context"
"fmt"
"net/http"
"strings"
"testing"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/managedgroups"
"github.com/hashicorp/boundary/testing/controller"
"github.com/hashicorp/cap/oidc"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)
const (
managedGroupName = "test"
managedGroupDescription = "test managed group"
managedGroupUpdate = "_update"
)
var (
fooManagedGroup = fmt.Sprintf(`
resource "boundary_managed_group" "foo" {
name = "%s"
description = "%s"
auth_method_id = boundary_auth_method_oidc.foo.id
filter = "name == \"foo\""
}`, managedGroupName, managedGroupDescription)
fooManagedGroupUpdate = fmt.Sprintf(`
resource "boundary_managed_group" "foo" {
name = "%s"
description = "%s"
auth_method_id = boundary_auth_method_oidc.foo.id
filter = "name == \"bar\""
}`, managedGroupName+managedGroupUpdate, managedGroupDescription+managedGroupUpdate)
)
func TestAccManagedGroup(t *testing.T) {
wrapper := testWrapper(t, tcRecoveryKey)
tp := oidc.StartTestProvider(t)
tc := controller.NewTestController(t, append(tcConfig, controller.WithRecoveryKms(wrapper))...)
tpCert := strings.TrimSpace(tp.CACert())
createConfig := fmt.Sprintf(fooAuthMethodOidc, fooAuthMethodOidcDesc, tp.Addr(), tpCert)
defer tc.Shutdown()
url := tc.ApiAddrs()[0]
var provider *schema.Provider
resource.Test(t, resource.TestCase{
ProviderFactories: providerFactories(&provider),
CheckDestroy: testAccCheckManagedGroupResourceDestroy(t, provider),
Steps: []resource.TestStep{
{
// test create
Config: testConfig(url, fooOrg, createConfig, fooManagedGroup),
Check: resource.ComposeTestCheckFunc(
testAccCheckManagedGroupResourceExists(provider, "boundary_managed_group.foo"),
resource.TestCheckResourceAttr("boundary_managed_group.foo", DescriptionKey, managedGroupDescription),
resource.TestCheckResourceAttr("boundary_managed_group.foo", NameKey, managedGroupName),
resource.TestCheckResourceAttr("boundary_managed_group.foo", managedGroupFilterKey, `name == "foo"`),
),
},
importStep("boundary_managed_group.foo"),
{
// test update
Config: testConfig(url, fooOrg, createConfig, fooManagedGroupUpdate),
Check: resource.ComposeTestCheckFunc(
testAccCheckManagedGroupResourceExists(provider, "boundary_managed_group.foo"),
resource.TestCheckResourceAttr("boundary_managed_group.foo", DescriptionKey, managedGroupDescription+managedGroupUpdate),
resource.TestCheckResourceAttr("boundary_managed_group.foo", NameKey, managedGroupName+managedGroupUpdate),
resource.TestCheckResourceAttr("boundary_managed_group.foo", managedGroupFilterKey, `name == "bar"`),
),
},
},
})
}
func testAccCheckManagedGroupResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Not found: %s", name)
}
id := rs.Primary.ID
if id == "" {
return fmt.Errorf("No ID is set")
}
md := testProvider.Meta().(*metaData)
grpClient := managedgroups.NewClient(md.client)
_, err := grpClient.Read(context.Background(), id)
if err != nil {
return fmt.Errorf("Got an error when reading group %q: %v", id, err)
}
return nil
}
}
func testAccCheckManagedGroupResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
return func(s *terraform.State) error {
if testProvider.Meta() == nil {
t.Fatal("got nil provider metadata")
}
md := testProvider.Meta().(*metaData)
for _, rs := range s.RootModule().Resources {
switch rs.Type {
case "boundary_managed_group":
grpClient := managedgroups.NewClient(md.client)
id := rs.Primary.ID
_, err := grpClient.Read(context.Background(), id)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
return fmt.Errorf("didn't get a 404 when reading destroyed resource %q: %v", id, apiErr)
}
default:
continue
}
}
return nil
}
}
+18 -50
View File
@@ -30,11 +30,6 @@ func resourceRole() *schema.Resource {
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the role.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The role name. Defaults to the resource name.",
Type: schema.TypeString,
@@ -77,27 +72,14 @@ func resourceRole() *schema.Resource {
}
}
func setFromRoleResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
if err := d.Set(NameKey, raw["name"]); err != nil {
return err
}
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
return err
}
if err := d.Set(ScopeIdKey, raw["scope_id"]); err != nil {
return err
}
if err := d.Set(rolePrincipalIdsKey, raw["principal_ids"]); err != nil {
return err
}
if err := d.Set(roleGrantStringsKey, raw["grant_strings"]); err != nil {
return err
}
if err := d.Set(roleGrantScopeIdKey, raw["grant_scope_id"]); err != nil {
return err
}
func setFromRoleResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
d.Set(NameKey, raw["name"])
d.Set(DescriptionKey, raw["description"])
d.Set(ScopeIdKey, raw["scope_id"])
d.Set(rolePrincipalIdsKey, raw["principal_ids"])
d.Set(roleGrantStringsKey, raw["grant_strings"])
d.Set(roleGrantScopeIdKey, raw["grant_scope_id"])
d.SetId(raw["id"].(string))
return nil
}
func resourceRoleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
@@ -162,7 +144,7 @@ func resourceRoleCreate(ctx context.Context, d *schema.ResourceData, meta interf
if tcr == nil {
return diag.Errorf("nil role after create")
}
raw := tcr.GetResponse().Map
raw := tcr.GetResponseMap()
if principalIds != nil {
tspr, err := rc.SetPrincipals(ctx, tcr.Item.Id, 0, principalIds, roles.WithAutomaticVersioning(true))
@@ -172,7 +154,7 @@ func resourceRoleCreate(ctx context.Context, d *schema.ResourceData, meta interf
if tspr == nil {
return diag.Errorf("nil role after setting principal IDs")
}
raw = tspr.GetResponse().Map
raw = tspr.GetResponseMap()
}
if grantStrings != nil {
@@ -183,12 +165,10 @@ func resourceRoleCreate(ctx context.Context, d *schema.ResourceData, meta interf
if tsgr == nil {
return diag.Errorf("nil role after setting grant strings")
}
raw = tsgr.GetResponse().Map
raw = tsgr.GetResponseMap()
}
if err := setFromRoleResponseMap(d, raw); err != nil {
return diag.FromErr(err)
}
setFromRoleResponseMap(d, raw)
return nil
}
@@ -199,7 +179,7 @@ func resourceRoleRead(ctx context.Context, d *schema.ResourceData, meta interfac
trr, err := rc.Read(ctx, d.Id())
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Status == int32(http.StatusNotFound) {
d.SetId("")
return nil
}
@@ -209,9 +189,7 @@ func resourceRoleRead(ctx context.Context, d *schema.ResourceData, meta interfac
return diag.Errorf("role nil after read")
}
if err := setFromRoleResponseMap(d, trr.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
setFromRoleResponseMap(d, trr.GetResponseMap())
return nil
}
@@ -264,19 +242,13 @@ func resourceRoleUpdate(ctx context.Context, d *schema.ResourceData, meta interf
}
if d.HasChange(NameKey) {
if err := d.Set(NameKey, name); err != nil {
return diag.FromErr(err)
}
d.Set(NameKey, name)
}
if d.HasChange(DescriptionKey) {
if err := d.Set(DescriptionKey, desc); err != nil {
return diag.FromErr(err)
}
d.Set(DescriptionKey, desc)
}
if d.HasChange(roleGrantScopeIdKey) {
if err := d.Set(roleGrantScopeIdKey, grantScopeId); err != nil {
return diag.FromErr(err)
}
d.Set(roleGrantScopeIdKey, grantScopeId)
}
if d.HasChange(roleGrantStringsKey) {
@@ -291,9 +263,7 @@ func resourceRoleUpdate(ctx context.Context, d *schema.ResourceData, meta interf
if err != nil {
return diag.Errorf("error updating grant strings on role: %v", err)
}
if err := d.Set(roleGrantStringsKey, grantStrings); err != nil {
return diag.FromErr(err)
}
d.Set(roleGrantStringsKey, grantStrings)
}
if d.HasChange(rolePrincipalIdsKey) {
@@ -308,9 +278,7 @@ func resourceRoleUpdate(ctx context.Context, d *schema.ResourceData, meta interf
if err != nil {
return diag.Errorf("error updating grant strings on role: %v", err)
}
if err := d.Set(rolePrincipalIdsKey, principalIds); err != nil {
return diag.FromErr(err)
}
d.Set(rolePrincipalIdsKey, principalIds)
}
return nil
+9 -3
View File
@@ -345,7 +345,7 @@ func testAccCheckRoleDestroyed(testProvider *schema.Provider, name string) resou
rolesClient := roles.NewClient(md.client)
_, err := rolesClient.Read(context.Background(), id)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Status != http.StatusNotFound {
errs = append(errs, fmt.Sprintf("Role not destroyed %q: %v", id, apiErr))
}
@@ -540,14 +540,20 @@ func testAccCheckRoleResourceDestroy(t *testing.T, testProvider *schema.Provider
for _, rs := range s.RootModule().Resources {
switch rs.Type {
case "boundary_scope":
continue
case "boundary_user":
continue
case "boundary_group":
continue
case "boundary_role":
id := rs.Primary.ID
rolesClient := roles.NewClient(md.client)
_, err := rolesClient.Read(context.Background(), id)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
return fmt.Errorf("didn't get a 404 when reading destroyed role %q: %v", id, err)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Status != http.StatusNotFound {
return fmt.Errorf("Didn't get a 404 when reading destroyed role %q: %v", id, err)
}
default:
+10 -33
View File
@@ -29,11 +29,6 @@ func resourceScope() *schema.Resource {
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the scope.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The scope name. Defaults to the resource name.",
Type: schema.TypeString,
@@ -69,25 +64,15 @@ func resourceScope() *schema.Resource {
}
}
func setFromScopeResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
if err := d.Set(NameKey, raw["name"]); err != nil {
return err
}
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
return err
}
func setFromScopeResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
d.Set(NameKey, raw["name"])
d.Set(DescriptionKey, raw["description"])
if d.Id() == "global" {
if err := d.Set(ScopeIdKey, "global"); err != nil {
return err
}
d.Set(ScopeIdKey, "global")
} else {
if err := d.Set(ScopeIdKey, raw["scope_id"]); err != nil {
return err
}
d.Set(ScopeIdKey, raw["scope_id"])
}
d.SetId(raw["id"].(string))
return nil
}
func resourceScopeCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
@@ -146,9 +131,7 @@ func resourceScopeCreate(ctx context.Context, d *schema.ResourceData, meta inter
return diag.Errorf("scope nil after create")
}
if err := setFromScopeResponseMap(d, scr.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
setFromScopeResponseMap(d, scr.GetResponseMap())
return nil
}
@@ -159,7 +142,7 @@ func resourceScopeRead(ctx context.Context, d *schema.ResourceData, meta interfa
srr, err := scp.Read(ctx, d.Id())
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Status == int32(http.StatusNotFound) {
d.SetId("")
return nil
}
@@ -169,9 +152,7 @@ func resourceScopeRead(ctx context.Context, d *schema.ResourceData, meta interfa
return diag.Errorf("scope nil after read")
}
if err := setFromScopeResponseMap(d, srr.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
setFromScopeResponseMap(d, srr.GetResponseMap())
return nil
}
@@ -213,14 +194,10 @@ func resourceScopeUpdate(ctx context.Context, d *schema.ResourceData, meta inter
}
if d.HasChange(NameKey) {
if err := d.Set(NameKey, name); err != nil {
return diag.FromErr(err)
}
d.Set(NameKey, name)
}
if d.HasChange(DescriptionKey) {
if err := d.Set(DescriptionKey, desc); err != nil {
return diag.FromErr(err)
}
d.Set(DescriptionKey, desc)
}
return nil
+3 -3
View File
@@ -166,11 +166,11 @@ func testAccCheckScopeResourceDestroy(t *testing.T, testProvider *schema.Provide
continue
}
_, err := scp.Read(context.Background(), id)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
return fmt.Errorf("didn't get a 404 when reading destroyed resource %q: %w", id, err)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Status != http.StatusNotFound {
return fmt.Errorf("Didn't get a 404 when reading destroyed resource %q: %w", id, err)
}
default:
continue
t.Logf("Got unknown resource type %q", rs.Type)
}
}
return nil
+45 -170
View File
@@ -12,12 +12,10 @@ import (
)
const (
targetHostSourceIdsKey = "host_source_ids"
targetApplicationCredentialSourceIdsKey = "application_credential_source_ids"
targetDefaultPortKey = "default_port"
targetSessionMaxSecondsKey = "session_max_seconds"
targetSessionConnectionLimitKey = "session_connection_limit"
targetWorkerFilterKey = "worker_filter"
targetHostSetIdsKey = "host_set_ids"
targetDefaultPortKey = "default_port"
targetSessionMaxSecondsKey = "session_max_seconds"
targetSessionConnectionLimitKey = "session_connection_limit"
targetTypeTcp = "tcp"
)
@@ -35,11 +33,6 @@ func resourceTarget() *schema.Resource {
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the target.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The target name. Defaults to the resource name.",
Type: schema.TypeString,
@@ -67,14 +60,8 @@ func resourceTarget() *schema.Resource {
Type: schema.TypeInt,
Optional: true,
},
targetHostSourceIdsKey: {
Description: "A list of host source ID's.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
targetApplicationCredentialSourceIdsKey: {
Description: "A list of application credential source ID's.",
targetHostSetIdsKey: {
Description: "A list of host set ID's.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
@@ -89,43 +76,18 @@ func resourceTarget() *schema.Resource {
Optional: true,
Computed: true,
},
targetWorkerFilterKey: {
Description: "Boolean expression to filter the workers for this target",
Type: schema.TypeString,
Optional: true,
},
},
}
}
func setFromTargetResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
if err := d.Set(NameKey, raw["name"]); err != nil {
return err
}
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
return err
}
if err := d.Set(ScopeIdKey, raw["scope_id"]); err != nil {
return err
}
if err := d.Set(TypeKey, raw["type"]); err != nil {
return err
}
if err := d.Set(targetHostSourceIdsKey, raw["host_source_ids"]); err != nil {
return err
}
if err := d.Set(targetApplicationCredentialSourceIdsKey, raw["application_credential_source_ids"]); err != nil {
return err
}
if err := d.Set(targetSessionMaxSecondsKey, raw["session_max_seconds"]); err != nil {
return err
}
if err := d.Set(targetSessionConnectionLimitKey, raw["session_connection_limit"]); err != nil {
return err
}
if err := d.Set(targetWorkerFilterKey, raw["worker_filter"]); err != nil {
return err
}
func setFromTargetResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
d.Set(NameKey, raw["name"])
d.Set(DescriptionKey, raw["description"])
d.Set(ScopeIdKey, raw["scope_id"])
d.Set(TypeKey, raw["type"])
d.Set(targetHostSetIdsKey, raw["host_set_ids"])
d.Set(targetSessionMaxSecondsKey, raw["session_max_seconds"])
d.Set(targetSessionConnectionLimitKey, raw["session_connection_limit"])
switch raw["type"].(string) {
case targetTypeTcp:
@@ -133,15 +95,12 @@ func setFromTargetResponseMap(d *schema.ResourceData, raw map[string]interface{}
attrs := attrsVal.(map[string]interface{})
if defPort, ok := attrs["default_port"].(json.Number); ok {
defPortInt, _ := defPort.Int64()
if err := d.Set(targetDefaultPortKey, int(defPortInt)); err != nil {
return err
}
d.Set(targetDefaultPortKey, int(defPortInt))
}
}
}
d.SetId(raw["id"].(string))
return nil
}
func resourceTargetCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
@@ -166,7 +125,8 @@ func resourceTargetCreate(ctx context.Context, d *schema.ResourceData, meta inte
return diag.Errorf("invalid type provided")
}
var opts []targets.Option
opts := []targets.Option{}
nameVal, ok := d.GetOk(NameKey)
if ok {
nameStr := nameVal.(string)
@@ -206,30 +166,15 @@ func resourceTargetCreate(ctx context.Context, d *schema.ResourceData, meta inte
opts = append(opts, targets.WithSessionConnectionLimit(int32(sessionConnectionLimitInt)))
}
var hostSourceIds []string
if hostSourceIdsVal, ok := d.GetOk(targetHostSourceIdsKey); ok {
list := hostSourceIdsVal.(*schema.Set).List()
hostSourceIds = make([]string, 0, len(list))
var hostSetIds []string
if hostSetIdsVal, ok := d.GetOk(targetHostSetIdsKey); ok {
list := hostSetIdsVal.(*schema.Set).List()
hostSetIds = make([]string, 0, len(list))
for _, i := range list {
hostSourceIds = append(hostSourceIds, i.(string))
hostSetIds = append(hostSetIds, i.(string))
}
}
var credentialSourceIds []string
if credentialSourceIdsVal, ok := d.GetOk(targetApplicationCredentialSourceIdsKey); ok {
list := credentialSourceIdsVal.(*schema.Set).List()
credentialSourceIds = make([]string, 0, len(list))
for _, i := range list {
credentialSourceIds = append(credentialSourceIds, i.(string))
}
}
workerFilterVal, ok := d.GetOk(targetWorkerFilterKey)
if ok {
workerFilterStr := workerFilterVal.(string)
opts = append(opts, targets.WithWorkerFilter(workerFilterStr))
}
tc := targets.NewClient(md.client)
tcr, err := tc.Create(ctx, typeStr, scopeId, opts...)
@@ -239,29 +184,17 @@ func resourceTargetCreate(ctx context.Context, d *schema.ResourceData, meta inte
if tcr == nil {
return diag.Errorf("target nil after create")
}
raw := tcr.GetResponse().Map
raw := tcr.GetResponseMap()
version := tcr.Item.Version
if hostSourceIds != nil {
tur, err := tc.SetHostSources(ctx, tcr.Item.Id, version, hostSourceIds)
if hostSetIds != nil {
tur, err := tc.SetHostSets(ctx, tcr.Item.Id, tcr.Item.Version, hostSetIds)
if err != nil {
return diag.Errorf("error setting host sources on target: %v", err)
return diag.Errorf("error setting host sets on target: %v", err)
}
raw = tur.GetResponse().Map
version = tur.Item.Version
raw = tur.GetResponseMap()
}
if credentialSourceIds != nil {
tur, err := tc.SetCredentialSources(ctx, tcr.Item.Id, version, targets.WithApplicationCredentialSourceIds(credentialSourceIds))
if err != nil {
return diag.Errorf("error setting credential sources on target: %v", err)
}
raw = tur.GetResponse().Map
}
if err := setFromTargetResponseMap(d, raw); err != nil {
return diag.FromErr(err)
}
setFromTargetResponseMap(d, raw)
return nil
}
@@ -272,7 +205,7 @@ func resourceTargetRead(ctx context.Context, d *schema.ResourceData, meta interf
trr, err := tc.Read(ctx, d.Id())
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Status == int32(http.StatusNotFound) {
d.SetId("")
return nil
}
@@ -282,9 +215,7 @@ func resourceTargetRead(ctx context.Context, d *schema.ResourceData, meta interf
return diag.Errorf("target nil after read")
}
if err := setFromTargetResponseMap(d, trr.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
setFromTargetResponseMap(d, trr.GetResponseMap())
return nil
}
@@ -293,7 +224,7 @@ func resourceTargetUpdate(ctx context.Context, d *schema.ResourceData, meta inte
md := meta.(*metaData)
tc := targets.NewClient(md.client)
var opts []targets.Option
opts := []targets.Option{}
var name *string
if d.HasChange(NameKey) {
@@ -359,17 +290,6 @@ func resourceTargetUpdate(ctx context.Context, d *schema.ResourceData, meta inte
}
}
var workerFilter *string
if d.HasChange(targetWorkerFilterKey) {
opts = append(opts, targets.DefaultWorkerFilter())
workerFilterVal, ok := d.GetOk(targetWorkerFilterKey)
if ok {
workerFilterStr := workerFilterVal.(string)
workerFilter = &workerFilterStr
opts = append(opts, targets.WithWorkerFilter(workerFilterStr))
}
}
if len(opts) > 0 {
opts = append(opts, targets.WithAutomaticVersioning(true))
_, err := tc.Update(ctx, d.Id(), 0, opts...)
@@ -379,81 +299,36 @@ func resourceTargetUpdate(ctx context.Context, d *schema.ResourceData, meta inte
}
if d.HasChange(NameKey) {
if err := d.Set(NameKey, name); err != nil {
return diag.FromErr(err)
}
d.Set(NameKey, name)
}
if d.HasChange(DescriptionKey) {
if err := d.Set(DescriptionKey, desc); err != nil {
return diag.FromErr(err)
}
d.Set(DescriptionKey, desc)
}
if d.HasChange(targetDefaultPortKey) {
if err := d.Set(targetDefaultPortKey, defaultPort); err != nil {
return diag.FromErr(err)
}
d.Set(targetDefaultPortKey, defaultPort)
}
if d.HasChange(targetSessionMaxSecondsKey) {
if err := d.Set(targetSessionMaxSecondsKey, sessionMaxSeconds); err != nil {
return diag.FromErr(err)
}
d.Set(targetSessionMaxSecondsKey, sessionMaxSeconds)
}
if d.HasChange(targetSessionConnectionLimitKey) {
if err := d.Set(targetSessionConnectionLimitKey, sessionConnectionLimit); err != nil {
return diag.FromErr(err)
}
}
if d.HasChange(targetWorkerFilterKey) {
if err := d.Set(targetWorkerFilterKey, workerFilter); err != nil {
return diag.FromErr(err)
}
d.Set(targetSessionConnectionLimitKey, sessionConnectionLimit)
}
// The above call may not actually happen, so we use d.Id() and automatic
// versioning here
if d.HasChange(targetHostSourceIdsKey) {
var hostSourceIds []string
if hostSourceIdsVal, ok := d.GetOk(targetHostSourceIdsKey); ok {
hostSources := hostSourceIdsVal.(*schema.Set).List()
for _, hostSource := range hostSources {
hostSourceIds = append(hostSourceIds, hostSource.(string))
if d.HasChange(targetHostSetIdsKey) {
var hostSetIds []string
if hostSetIdsVal, ok := d.GetOk(targetHostSetIdsKey); ok {
hostSets := hostSetIdsVal.(*schema.Set).List()
for _, hostSet := range hostSets {
hostSetIds = append(hostSetIds, hostSet.(string))
}
}
_, err := tc.SetHostSources(ctx, d.Id(), 0, hostSourceIds, targets.WithAutomaticVersioning(true))
_, err := tc.SetHostSets(ctx, d.Id(), 0, hostSetIds, targets.WithAutomaticVersioning(true))
if err != nil {
return diag.Errorf("error updating host sources in target: %v", err)
}
if err := d.Set(targetHostSourceIdsKey, hostSourceIds); err != nil {
return diag.FromErr(err)
}
}
// The above calls may not actually happen, so we use d.Id() and automatic
// versioning here
if d.HasChange(targetApplicationCredentialSourceIdsKey) {
var credentialSourceIds []string
if credentialSourceIdsVal, ok := d.GetOk(targetApplicationCredentialSourceIdsKey); ok {
credSourceIds := credentialSourceIdsVal.(*schema.Set).List()
for _, credSourceId := range credSourceIds {
credentialSourceIds = append(credentialSourceIds, credSourceId.(string))
}
}
opts := []targets.Option{
targets.WithAutomaticVersioning(true),
targets.DefaultApplicationCredentialSourceIds(),
}
if len(credentialSourceIds) > 0 {
opts = append(opts, targets.WithApplicationCredentialSourceIds(credentialSourceIds))
}
_, err := tc.SetCredentialSources(ctx, d.Id(), 0, opts...)
if err != nil {
return diag.Errorf("error updating credential sources in target: %v", err)
}
if err := d.Set(targetApplicationCredentialSourceIdsKey, credentialSourceIds); err != nil {
return diag.FromErr(err)
return diag.Errorf("error updating host sets in target: %v", err)
}
d.Set(targetHostSetIdsKey, hostSetIds)
}
return nil
+23 -160
View File
@@ -9,7 +9,6 @@ import (
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/targets"
"github.com/hashicorp/boundary/testing/controller"
"github.com/hashicorp/boundary/testing/vault"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
@@ -21,7 +20,7 @@ const (
)
var (
fooBarHostSet = `
fooHostSet = `
resource "boundary_host_catalog" "foo" {
type = "static"
name = "test"
@@ -50,53 +49,23 @@ resource "boundary_host_set" "foo" {
host_catalog_id = boundary_host_catalog.foo.id
host_ids = [
boundary_host.foo.id,
]
}
resource "boundary_host_set" "bar" {
name = "bar"
type = "static"
host_catalog_id = boundary_host_catalog.foo.id
host_ids = [
boundary_host.bar.id,
]
}`
fooBarCredLibs = `
resource "boundary_credential_library_vault" "foo" {
name = "foo"
description = "foo library"
credential_store_id = boundary_credential_store_vault.example.id
path = "foo/bar"
http_method = "GET"
}
resource "boundary_credential_library_vault" "bar" {
name = "bar"
description = "bar library"
credential_store_id = boundary_credential_store_vault.example.id
path = "bar/foo"
http_method = "GET"
}
`
fooTarget = fmt.Sprintf(`
resource "boundary_target" "foo" {
name = "test"
description = "%s"
type = "tcp"
scope_id = boundary_scope.proj1.id
host_source_ids = [
host_set_ids = [
boundary_host_set.foo.id
]
application_credential_source_ids = [
boundary_credential_library_vault.foo.id
]
default_port = 22
depends_on = [boundary_role.proj1_admin]
session_max_seconds = 6000
session_connection_limit = 6
worker_filter = "type == \"foo\""
}`, fooTargetDescription)
fooTargetUpdate = fmt.Sprintf(`
@@ -105,30 +74,13 @@ resource "boundary_target" "foo" {
description = "%s"
type = "tcp"
scope_id = boundary_scope.proj1.id
host_source_ids = [
boundary_host_set.bar.id
]
application_credential_source_ids = [
boundary_credential_library_vault.bar.id
host_set_ids = [
boundary_host_set.foo.id
]
default_port = 80
depends_on = [boundary_role.proj1_admin]
session_max_seconds = 7000
session_connection_limit = 7
worker_filter = "type == \"bar\""
}`, fooTargetDescriptionUpdate)
fooTargetUpdateUnsetHostAndCredSources = fmt.Sprintf(`
resource "boundary_target" "foo" {
name = "test"
description = "%s"
type = "tcp"
scope_id = boundary_scope.proj1.id
default_port = 80
depends_on = [boundary_role.proj1_admin]
session_max_seconds = 7000
session_connection_limit = 7
worker_filter = "type == \"bar\""
}`, fooTargetDescriptionUpdate)
)
@@ -138,25 +90,14 @@ func TestAccTarget(t *testing.T) {
defer tc.Shutdown()
url := tc.ApiAddrs()[0]
vc := vault.NewTestVaultServer(t)
_, token := vc.CreateToken(t)
credStoreRes := vaultCredStoreResource(vc,
vaultCredStoreName,
vaultCredStoreDesc,
vaultCredStoreNamespace,
"www.original.com",
token,
true)
var provider *schema.Provider
resource.Test(t, resource.TestCase{
IsUnitTest: true,
ProviderFactories: providerFactories(&provider),
CheckDestroy: testAccCheckTargetResourceDestroy(t, provider),
Steps: []resource.TestStep{
{
// test create
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, fooBarCredLibs, fooBarHostSet, fooTarget),
Config: testConfig(url, fooOrg, firstProjectFoo, fooHostSet, fooTarget),
Check: resource.ComposeTestCheckFunc(
testAccCheckTargetResourceExists(provider, "boundary_target.foo"),
resource.TestCheckResourceAttr("boundary_target.foo", DescriptionKey, fooTargetDescription),
@@ -164,39 +105,18 @@ func TestAccTarget(t *testing.T) {
resource.TestCheckResourceAttr("boundary_target.foo", targetDefaultPortKey, "22"),
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionMaxSecondsKey, "6000"),
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionConnectionLimitKey, "6"),
resource.TestCheckResourceAttr("boundary_target.foo", targetWorkerFilterKey, `type == "foo"`),
testAccCheckTargetResourceHostSource(provider, "boundary_target.foo", []string{"boundary_host_set.foo"}),
testAccCheckTargetResourceAppCredSources(provider, "boundary_target.foo", []string{"boundary_credential_library_vault.foo"}),
),
},
importStep("boundary_target.foo"),
{
// test update
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, fooBarCredLibs, fooBarHostSet, fooTargetUpdate),
Config: testConfig(url, fooOrg, firstProjectFoo, fooHostSet, fooTargetUpdate),
Check: resource.ComposeTestCheckFunc(
testAccCheckTargetResourceExists(provider, "boundary_target.foo"),
resource.TestCheckResourceAttr("boundary_target.foo", DescriptionKey, fooTargetDescriptionUpdate),
resource.TestCheckResourceAttr("boundary_target.foo", targetDefaultPortKey, "80"),
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionMaxSecondsKey, "7000"),
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionConnectionLimitKey, "7"),
resource.TestCheckResourceAttr("boundary_target.foo", targetWorkerFilterKey, `type == "bar"`),
testAccCheckTargetResourceHostSource(provider, "boundary_target.foo", []string{"boundary_host_set.bar"}),
testAccCheckTargetResourceAppCredSources(provider, "boundary_target.foo", []string{"boundary_credential_library_vault.bar"}),
),
},
importStep("boundary_target.foo"),
{
// test unset hosts and cred sources
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, fooBarCredLibs, fooBarHostSet, fooTargetUpdateUnsetHostAndCredSources),
Check: resource.ComposeTestCheckFunc(
testAccCheckTargetResourceExists(provider, "boundary_target.foo"),
resource.TestCheckResourceAttr("boundary_target.foo", DescriptionKey, fooTargetDescriptionUpdate),
resource.TestCheckResourceAttr("boundary_target.foo", targetDefaultPortKey, "80"),
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionMaxSecondsKey, "7000"),
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionConnectionLimitKey, "7"),
resource.TestCheckResourceAttr("boundary_target.foo", targetWorkerFilterKey, `type == "bar"`),
testAccCheckTargetResourceHostSource(provider, "boundary_target.foo", nil),
testAccCheckTargetResourceAppCredSources(provider, "boundary_target.foo", nil),
),
},
importStep("boundary_target.foo"),
@@ -204,7 +124,7 @@ func TestAccTarget(t *testing.T) {
})
}
func testAccCheckTargetResourceHostSource(testProvider *schema.Provider, name string, hostSources []string) resource.TestCheckFunc {
func testAccCheckTargetResourceMembersSet(testProvider *schema.Provider, name string, hostSets []string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
@@ -216,20 +136,20 @@ func testAccCheckTargetResourceHostSource(testProvider *schema.Provider, name st
return fmt.Errorf("target resource ID is not set")
}
// ensure host sources are declared in state
var hostSourceIDs []string
for _, hostSourceResourceID := range hostSources {
hs, ok := s.RootModule().Resources[hostSourceResourceID]
// ensure host sets are declared in state
hostSetIDs := []string{}
for _, hostSetResourceID := range hostSets {
hs, ok := s.RootModule().Resources[hostSetResourceID]
if !ok {
return fmt.Errorf("host source resource not found: %s", hostSourceResourceID)
return fmt.Errorf("host set resource not found: %s", hostSetResourceID)
}
hostSourceID := hs.Primary.ID
hostSetID := hs.Primary.ID
if id == "" {
return fmt.Errorf("host source resource ID not set")
return fmt.Errorf("host set resource ID not set")
}
hostSourceIDs = append(hostSourceIDs, hostSourceID)
hostSetIDs = append(hostSetIDs, hostSetID)
}
// check boundary to ensure it matches
@@ -241,76 +161,19 @@ func testAccCheckTargetResourceHostSource(testProvider *schema.Provider, name st
return fmt.Errorf("Got an error when reading target %q: %v", id, err)
}
if len(t.Item.HostSourceIds) != len(hostSourceIDs) {
return fmt.Errorf("tf state and boundary have different number of host sources")
if len(t.Item.HostSetIds) == 0 {
return fmt.Errorf("no hostSets found on target")
}
for _, stateHostSourceId := range t.Item.HostSourceIds {
for _, stateHostSet := range t.Item.HostSetIds {
ok := false
for _, gotHostSourceID := range hostSourceIDs {
if gotHostSourceID == stateHostSourceId {
for _, gotHostSetID := range hostSetIDs {
if gotHostSetID == stateHostSet {
ok = true
}
}
if !ok {
return fmt.Errorf("host source id in state not set in boundary: %s", stateHostSourceId)
}
}
return nil
}
}
func testAccCheckTargetResourceAppCredSources(testProvider *schema.Provider, name string, credSources []string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("target resource not found: %s", name)
}
id := rs.Primary.ID
if id == "" {
return fmt.Errorf("target resource ID is not set")
}
// ensure cred sources are declared in state
var credSourceIDs []string
for _, credSourceResourceID := range credSources {
cl, ok := s.RootModule().Resources[credSourceResourceID]
if !ok {
return fmt.Errorf("credential source resource not found: %s", credSourceResourceID)
}
credSourceID := cl.Primary.ID
if id == "" {
return fmt.Errorf("credential source resource ID not set")
}
credSourceIDs = append(credSourceIDs, credSourceID)
}
// check boundary to ensure it matches
md := testProvider.Meta().(*metaData)
tgtsClient := targets.NewClient(md.client)
t, err := tgtsClient.Read(context.Background(), id)
if err != nil {
return fmt.Errorf("got an error when reading target %q: %w", id, err)
}
if len(t.Item.ApplicationCredentialSourceIds) != len(credSourceIDs) {
return fmt.Errorf("tf state and boundary have different number of application credential sources")
}
for _, stateCredSourceId := range t.Item.ApplicationCredentialSourceIds {
ok := false
for _, gotCredSourceID := range credSourceIDs {
if gotCredSourceID == stateCredSourceId {
ok = true
}
}
if !ok {
return fmt.Errorf("application credential source id in state not set in boundary: %s", stateCredSourceId)
return fmt.Errorf("host set in state not set in boundary: %s", stateHostSet)
}
}
@@ -357,8 +220,8 @@ func testAccCheckTargetResourceDestroy(t *testing.T, testProvider *schema.Provid
id := rs.Primary.ID
_, err := tgts.Read(context.Background(), id)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
return fmt.Errorf("didn't get a 404 when reading destroyed target %q: %v", id, apiErr)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Status != http.StatusNotFound {
return fmt.Errorf("Didn't get a 404 when reading destroyed target %q: %v", id, apiErr)
}
default:
+13 -37
View File
@@ -25,11 +25,6 @@ func resourceUser() *schema.Resource {
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the user.",
Type: schema.TypeString,
Computed: true,
},
NameKey: {
Description: "The username. Defaults to the resource name.",
Type: schema.TypeString,
@@ -56,21 +51,12 @@ func resourceUser() *schema.Resource {
}
}
func setFromUserResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
if err := d.Set(NameKey, raw["name"]); err != nil {
return err
}
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
return err
}
if err := d.Set(ScopeIdKey, raw["scope_id"]); err != nil {
return err
}
if err := d.Set(userAccountIDsKey, raw["account_ids"]); err != nil {
return err
}
func setFromUserResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
d.Set(NameKey, raw["name"])
d.Set(DescriptionKey, raw["description"])
d.Set(ScopeIdKey, raw["scope_id"])
d.Set(userAccountIDsKey, raw["account_ids"])
d.SetId(raw["id"].(string))
return nil
}
func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
@@ -106,7 +92,7 @@ func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interf
if ucr == nil {
return diag.Errorf("user nil after create")
}
raw := ucr.GetResponse().Map
raw := ucr.GetResponseMap()
if val, ok := d.GetOk(userAccountIDsKey); ok {
list := val.(*schema.Set).List()
@@ -121,12 +107,10 @@ func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interf
if usrac == nil {
return diag.Errorf("user nil after setting accounts")
}
raw = usrac.GetResponse().Map
raw = usrac.GetResponseMap()
}
if err := setFromUserResponseMap(d, raw); err != nil {
return diag.FromErr(err)
}
setFromUserResponseMap(d, raw)
return nil
}
@@ -137,7 +121,7 @@ func resourceUserRead(ctx context.Context, d *schema.ResourceData, meta interfac
urr, err := usrs.Read(ctx, d.Id())
if err != nil {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Status == int32(http.StatusNotFound) {
d.SetId("")
return nil
}
@@ -147,9 +131,7 @@ func resourceUserRead(ctx context.Context, d *schema.ResourceData, meta interfac
return diag.Errorf("user nil after read")
}
if err := setFromUserResponseMap(d, urr.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
setFromUserResponseMap(d, urr.GetResponseMap())
return nil
}
@@ -191,14 +173,10 @@ func resourceUserUpdate(ctx context.Context, d *schema.ResourceData, meta interf
}
if d.HasChange(NameKey) {
if err := d.Set(NameKey, name); err != nil {
return diag.FromErr(err)
}
d.Set(NameKey, name)
}
if d.HasChange(DescriptionKey) {
if err := d.Set(DescriptionKey, desc); err != nil {
return diag.FromErr(err)
}
d.Set(DescriptionKey, desc)
}
if d.HasChange(userAccountIDsKey) {
@@ -214,9 +192,7 @@ func resourceUserUpdate(ctx context.Context, d *schema.ResourceData, meta interf
if err != nil {
return diag.Errorf("error updating accounts on user: %v", err)
}
if err := d.Set(userAccountIDsKey, accountIds); err != nil {
return diag.FromErr(err)
}
d.Set(userAccountIDsKey, accountIds)
}
return nil
+3 -3
View File
@@ -232,12 +232,12 @@ func testAccCheckUserResourceDestroy(t *testing.T, testProvider *schema.Provider
usrs := users.NewClient(md.client)
_, err := usrs.Read(context.Background(), id)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
return fmt.Errorf("didn't get a 404 when reading destroyed user %q: %v", id, err)
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Status != http.StatusNotFound {
return fmt.Errorf("Didn't get a 404 when reading destroyed user %q: %v", id, err)
}
default:
continue
t.Logf("Got unknown resource type %q", rs.Type)
}
}
return nil
-10
View File
@@ -5,16 +5,6 @@ import (
"github.com/hashicorp/terraform-provider-boundary/internal/provider"
)
// Run "go generate" to format example terraform files and generate the docs for the registry/website
// If you do not have terraform installed, you can remove the formatting command, but its suggested to
// ensure the documentation is formatted properly.
//go:generate terraform fmt -recursive ./examples/
// Run the docs generation tool, check its repository for more information on how it works and how docs
// can be customized.
//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs
func main() {
plugin.Serve(&plugin.ServeOpts{ProviderFunc: provider.New})
}
+11
View File
@@ -0,0 +1,11 @@
# Editors
.idea
.vscode
*.swp
# Test files
*.test
coverage.txt
# Other
.DS_Store
+1695
View File
File diff suppressed because it is too large. Load diff
+44
View File
@@ -0,0 +1,44 @@
# Contributor Code of Conduct
As contributors and maintainers of this project,
and in the interest of fostering an open and welcoming community,
we pledge to respect all people who contribute through reporting issues,
posting feature requests, updating documentation,
submitting pull requests or patches, and other activities.
We are committed to making participation in this project
a harassment-free experience for everyone,
regardless of level of experience, gender, gender identity and expression,
sexual orientation, disability, personal appearance,
body size, race, ethnicity, age, religion, or nationality.
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing other's private information,
such as physical or electronic
addresses, without explicit permission
* Other unethical or unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct.
By adopting this Code of Conduct,
project maintainers commit themselves to fairly and consistently
applying these principles to every aspect of managing this project.
Project maintainers who do not follow or enforce the Code of Conduct
may be permanently removed from the project team.
This code of conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior
may be reported by opening an issue
or contacting one or more of the project maintainers.
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,
available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
+260
View File
@@ -0,0 +1,260 @@
# Contributing
1. [File an issue](https://github.com/googleapis/google-cloud-go/issues/new/choose).
The issue will be used to discuss the bug or feature and should be created
before sending a CL.
1. [Install Go](https://golang.org/dl/).
1. Ensure that your `GOBIN` directory (by default `$(go env GOPATH)/bin`)
is in your `PATH`.
1. Check it's working by running `go version`.
* If it doesn't work, check the install location, usually
`/usr/local/go`, is on your `PATH`.
1. Sign one of the
[contributor license agreements](#contributor-license-agreements) below.
1. Clone the repo:
`git clone https://github.com/googleapis/google-cloud-go`
1. Change into the checked out source:
`cd google-cloud-go`
1. Fork the repo.
1. Set your fork as a remote:
`git remote add fork git@github.com:GITHUB_USERNAME/google-cloud-go.git`
1. Make changes, commit to your fork.
Commit messages should follow the
[Conventional Commits Style](https://www.conventionalcommits.org). The scope
portion should always be filled with the name of the package affected by the
changes being made. For example:
```
feat(functions): add gophers codelab
```
1. Send a pull request with your changes.
To minimize friction, consider setting `Allow edits from maintainers` on the
PR, which will enable project committers and automation to update your PR.
1. A maintainer will review the pull request and make comments.
Prefer adding additional commits over amending and force-pushing since it can
be difficult to follow code reviews when the commit history changes.
Commits will be squashed when they're merged.
## Testing
We test code against two versions of Go, the minimum and maximum versions
supported by our clients. To see which versions these are checkout our
[README](README.md#supported-versions).
### Integration Tests
In addition to the unit tests, you may run the integration test suite. These
directions describe setting up your environment to run integration tests for
_all_ packages: note that many of these instructions may be redundant if you
intend only to run integration tests on a single package.
#### GCP Setup
To run the integrations tests, creation and configuration of two projects in
the Google Developers Console is required: one specifically for Firestore
integration tests, and another for all other integration tests. We'll refer to
these projects as "general project" and "Firestore project".
After creating each project, you must [create a service account](https://developers.google.com/identity/protocols/OAuth2ServiceAccount#creatinganaccount)
for each project. Ensure the project-level **Owner**
[IAM role](console.cloud.google.com/iam-admin/iam/project) role is added to
each service account. During the creation of the service account, you should
download the JSON credential file for use later.
Next, ensure the following APIs are enabled in the general project:
- BigQuery API
- BigQuery Data Transfer API
- Cloud Dataproc API
- Cloud Dataproc Control API Private
- Cloud Datastore API
- Cloud Firestore API
- Cloud Key Management Service (KMS) API
- Cloud Natural Language API
- Cloud OS Login API
- Cloud Pub/Sub API
- Cloud Resource Manager API
- Cloud Spanner API
- Cloud Speech API
- Cloud Translation API
- Cloud Video Intelligence API
- Cloud Vision API
- Compute Engine API
- Compute Engine Instance Group Manager API
- Container Registry API
- Firebase Rules API
- Google Cloud APIs
- Google Cloud Deployment Manager V2 API
- Google Cloud SQL
- Google Cloud Storage
- Google Cloud Storage JSON API
- Google Compute Engine Instance Group Updater API
- Google Compute Engine Instance Groups API
- Kubernetes Engine API
- Stackdriver Error Reporting API
Next, create a Datastore database in the general project, and a Firestore
database in the Firestore project.
Finally, in the general project, create an API key for the translate API:
- Go to GCP Developer Console.
- Navigate to APIs & Services > Credentials.
- Click Create Credentials > API Key.
- Save this key for use in `GCLOUD_TESTS_API_KEY` as described below.
#### Local Setup
Once the two projects are created and configured, set the following environment
variables:
- `GCLOUD_TESTS_GOLANG_PROJECT_ID`: Developers Console project's ID (e.g.
bamboo-shift-455) for the general project.
- `GCLOUD_TESTS_GOLANG_KEY`: The path to the JSON key file of the general
project's service account.
- `GCLOUD_TESTS_GOLANG_FIRESTORE_PROJECT_ID`: Developers Console project's ID
(e.g. doorway-cliff-677) for the Firestore project.
- `GCLOUD_TESTS_GOLANG_FIRESTORE_KEY`: The path to the JSON key file of the
Firestore project's service account.
- `GCLOUD_TESTS_GOLANG_KEYRING`: The full name of the keyring for the tests,
in the form
"projects/P/locations/L/keyRings/R". The creation of this is described below.
- `GCLOUD_TESTS_API_KEY`: API key for using the Translate API.
- `GCLOUD_TESTS_GOLANG_ZONE`: Compute Engine zone.
Install the [gcloud command-line tool][gcloudcli] to your machine and use it to
create some resources used in integration tests.
From the project's root directory:
``` sh
# Sets the default project in your env.
$ gcloud config set project $GCLOUD_TESTS_GOLANG_PROJECT_ID
# Authenticates the gcloud tool with your account.
$ gcloud auth login
# Create the indexes used in the datastore integration tests.
$ gcloud datastore indexes create datastore/testdata/index.yaml
# Creates a Google Cloud storage bucket with the same name as your test project,
# and with the Stackdriver Logging service account as owner, for the sink
# integration tests in logging.
$ gsutil mb gs://$GCLOUD_TESTS_GOLANG_PROJECT_ID
$ gsutil acl ch -g cloud-logs@google.com:O gs://$GCLOUD_TESTS_GOLANG_PROJECT_ID
# Creates a PubSub topic for integration tests of storage notifications.
$ gcloud beta pubsub topics create go-storage-notification-test
# Next, go to the Pub/Sub dashboard in GCP console. Authorize the user
# "service-<numberic project id>@gs-project-accounts.iam.gserviceaccount.com"
# as a publisher to that topic.
# Creates a Spanner instance for the spanner integration tests.
$ gcloud beta spanner instances create go-integration-test --config regional-us-central1 --nodes 10 --description 'Instance for go client test'
# NOTE: Spanner instances are priced by the node-hour, so you may want to
# delete the instance after testing with 'gcloud beta spanner instances delete'.
$ export MY_KEYRING=some-keyring-name
$ export MY_LOCATION=global
# Creates a KMS keyring, in the same location as the default location for your
# project's buckets.
$ gcloud kms keyrings create $MY_KEYRING --location $MY_LOCATION
# Creates two keys in the keyring, named key1 and key2.
$ gcloud kms keys create key1 --keyring $MY_KEYRING --location $MY_LOCATION --purpose encryption
$ gcloud kms keys create key2 --keyring $MY_KEYRING --location $MY_LOCATION --purpose encryption
# Sets the GCLOUD_TESTS_GOLANG_KEYRING environment variable.
$ export GCLOUD_TESTS_GOLANG_KEYRING=projects/$GCLOUD_TESTS_GOLANG_PROJECT_ID/locations/$MY_LOCATION/keyRings/$MY_KEYRING
# Authorizes Google Cloud Storage to encrypt and decrypt using key1.
gsutil kms authorize -p $GCLOUD_TESTS_GOLANG_PROJECT_ID -k $GCLOUD_TESTS_GOLANG_KEYRING/cryptoKeys/key1
```
#### Running
Once you've done the necessary setup, you can run the integration tests by
running:
``` sh
$ go test -v cloud.google.com/go/...
```
#### Replay
Some packages can record the RPCs during integration tests to a file for
subsequent replay. To record, pass the `-record` flag to `go test`. The
recording will be saved to the _package_`.replay` file. To replay integration
tests from a saved recording, the replay file must be present, the `-short`
flag must be passed to `go test`, and the `GCLOUD_TESTS_GOLANG_ENABLE_REPLAY`
environment variable must have a non-empty value.
## Contributor License Agreements
Before we can accept your pull requests you'll need to sign a Contributor
License Agreement (CLA):
- **If you are an individual writing original source code** and **you own the
intellectual property**, then you'll need to sign an [individual CLA][indvcla].
- **If you work for a company that wants to allow you to contribute your
work**, then you'll need to sign a [corporate CLA][corpcla].
You can sign these electronically (just scroll to the bottom). After that,
we'll be able to accept your pull requests.
## Contributor Code of Conduct
As contributors and maintainers of this project,
and in the interest of fostering an open and welcoming community,
we pledge to respect all people who contribute through reporting issues,
posting feature requests, updating documentation,
submitting pull requests or patches, and other activities.
We are committed to making participation in this project
a harassment-free experience for everyone,
regardless of level of experience, gender, gender identity and expression,
sexual orientation, disability, personal appearance,
body size, race, ethnicity, age, religion, or nationality.
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing other's private information,
such as physical or electronic
addresses, without explicit permission
* Other unethical or unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct.
By adopting this Code of Conduct,
project maintainers commit themselves to fairly and consistently
applying these principles to every aspect of managing this project.
Project maintainers who do not follow or enforce the Code of Conduct
may be permanently removed from the project team.
This code of conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior
may be reported by opening an issue
or contacting one or more of the project maintainers.
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,
available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
[gcloudcli]: https://developers.google.com/cloud/sdk/gcloud/
[indvcla]: https://developers.google.com/open-source/cla/individual
[corpcla]: https://developers.google.com/open-source/cla/corporate
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+177
View File
@@ -0,0 +1,177 @@
# Google Cloud Client Libraries for Go
[![GoDoc](https://godoc.org/cloud.google.com/go?status.svg)](https://pkg.go.dev/cloud.google.com/go)
Go packages for [Google Cloud Platform](https://cloud.google.com) services.
``` go
import "cloud.google.com/go"
```
To install the packages on your system, *do not clone the repo*. Instead:
1. Change to your project directory:
```
cd /my/cloud/project
```
1. Get the package you want to use. Some products have their own module, so it's
best to `go get` the package(s) you want to use:
```
$ go get cloud.google.com/go/firestore # Replace with the package you want to use.
```
**NOTE:** Some of these packages are under development, and may occasionally
make backwards-incompatible changes.
**NOTE:** Github repo is a mirror of [https://code.googlesource.com/gocloud](https://code.googlesource.com/gocloud).
## Supported APIs
| Google API | Status | Package |
| ----------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------- |
| [Asset][cloud-asset] | stable | [`cloud.google.com/go/asset/apiv1`](https://pkg.go.dev/cloud.google.com/go/asset/v1beta) |
| [Automl][cloud-automl] | stable | [`cloud.google.com/go/automl/apiv1`](https://pkg.go.dev/cloud.google.com/go/automl/apiv1) |
| [BigQuery][cloud-bigquery] | stable | [`cloud.google.com/go/bigquery`](https://pkg.go.dev/cloud.google.com/go/bigquery) |
| [Bigtable][cloud-bigtable] | stable | [`cloud.google.com/go/bigtable`](https://pkg.go.dev/cloud.google.com/go/bigtable) |
| [Cloudbuild][cloud-build] | stable | [`cloud.google.com/go/cloudbuild/apiv1`](https://pkg.go.dev/cloud.google.com/go/cloudbuild/apiv1) |
| [Cloudtasks][cloud-tasks] | stable | [`cloud.google.com/go/cloudtasks/apiv2`](https://pkg.go.dev/cloud.google.com/go/cloudtasks/apiv2) |
| [Container][cloud-container] | stable | [`cloud.google.com/go/container/apiv1`](https://pkg.go.dev/cloud.google.com/go/container/apiv1) |
| [ContainerAnalysis][cloud-containeranalysis] | beta | [`cloud.google.com/go/containeranalysis/apiv1`](https://pkg.go.dev/cloud.google.com/go/containeranalysis/apiv1) |
| [Dataproc][cloud-dataproc] | stable | [`cloud.google.com/go/dataproc/apiv1`](https://pkg.go.dev/cloud.google.com/go/dataproc/apiv1) |
| [Datastore][cloud-datastore] | stable | [`cloud.google.com/go/datastore`](https://pkg.go.dev/cloud.google.com/go/datastore) |
| [Debugger][cloud-debugger] | stable | [`cloud.google.com/go/debugger/apiv2`](https://pkg.go.dev/cloud.google.com/go/debugger/apiv2) |
| [Dialogflow][cloud-dialogflow] | stable | [`cloud.google.com/go/dialogflow/apiv2`](https://pkg.go.dev/cloud.google.com/go/dialogflow/apiv2) |
| [Data Loss Prevention][cloud-dlp] | stable | [`cloud.google.com/go/dlp/apiv2`](https://pkg.go.dev/cloud.google.com/go/dlp/apiv2) |
| [ErrorReporting][cloud-errors] | alpha | [`cloud.google.com/go/errorreporting`](https://pkg.go.dev/cloud.google.com/go/errorreporting) |
| [Firestore][cloud-firestore] | stable | [`cloud.google.com/go/firestore`](https://pkg.go.dev/cloud.google.com/go/firestore) |
| [IAM][cloud-iam] | stable | [`cloud.google.com/go/iam`](https://pkg.go.dev/cloud.google.com/go/iam) |
| [IoT][cloud-iot] | stable | [`cloud.google.com/go/iot/apiv1`](https://pkg.go.dev/cloud.google.com/go/iot/apiv1) |
| [IRM][cloud-irm] | alpha | [`cloud.google.com/go/irm/apiv1alpha2`](https://pkg.go.dev/cloud.google.com/go/irm/apiv1alpha2) |
| [KMS][cloud-kms] | stable | [`cloud.google.com/go/kms/apiv1`](https://pkg.go.dev/cloud.google.com/go/kms/apiv1) |
| [Natural Language][cloud-natural-language] | stable | [`cloud.google.com/go/language/apiv1`](https://pkg.go.dev/cloud.google.com/go/language/apiv1) |
| [Logging][cloud-logging] | stable | [`cloud.google.com/go/logging`](https://pkg.go.dev/cloud.google.com/go/logging) |
| [Memorystore][cloud-memorystore] | alpha | [`cloud.google.com/go/redis/apiv1`](https://pkg.go.dev/cloud.google.com/go/redis/apiv1) |
| [Monitoring][cloud-monitoring] | stable | [`cloud.google.com/go/monitoring/apiv3`](https://pkg.go.dev/cloud.google.com/go/monitoring/apiv3) |
| [OS Login][cloud-oslogin] | stable | [`cloud.google.com/go/oslogin/apiv1`](https://pkg.go.dev/cloud.google.com/go/oslogin/apiv1) |
| [Pub/Sub][cloud-pubsub] | stable | [`cloud.google.com/go/pubsub`](https://pkg.go.dev/cloud.google.com/go/pubsub) |
| [Phishing Protection][cloud-phishingprotection] | alpha | [`cloud.google.com/go/phishingprotection/apiv1beta1`](https://pkg.go.dev/cloud.google.com/go/phishingprotection/apiv1beta1) |
| [reCAPTCHA Enterprise][cloud-recaptcha] | alpha | [`cloud.google.com/go/recaptchaenterprise/apiv1beta1`](https://pkg.go.dev/cloud.google.com/go/recaptchaenterprise/apiv1beta1) |
| [Recommender][cloud-recommender] | beta | [`cloud.google.com/go/recommender/apiv1beta1`](https://pkg.go.dev/cloud.google.com/go/recommender/apiv1beta1) |
| [Scheduler][cloud-scheduler] | stable | [`cloud.google.com/go/scheduler/apiv1`](https://pkg.go.dev/cloud.google.com/go/scheduler/apiv1) |
| [Securitycenter][cloud-securitycenter] | stable | [`cloud.google.com/go/securitycenter/apiv1`](https://pkg.go.dev/cloud.google.com/go/securitycenter/apiv1) |
| [Spanner][cloud-spanner] | stable | [`cloud.google.com/go/spanner`](https://pkg.go.dev/cloud.google.com/go/spanner) |
| [Speech][cloud-speech] | stable | [`cloud.google.com/go/speech/apiv1`](https://pkg.go.dev/cloud.google.com/go/speech/apiv1) |
| [Storage][cloud-storage] | stable | [`cloud.google.com/go/storage`](https://pkg.go.dev/cloud.google.com/go/storage) |
| [Talent][cloud-talent] | alpha | [`cloud.google.com/go/talent/apiv4beta1`](https://pkg.go.dev/cloud.google.com/go/talent/apiv4beta1) |
| [Text To Speech][cloud-texttospeech] | stable | [`cloud.google.com/go/texttospeech/apiv1`](https://pkg.go.dev/cloud.google.com/go/texttospeech/apiv1) |
| [Trace][cloud-trace] | stable | [`cloud.google.com/go/trace/apiv2`](https://pkg.go.dev/cloud.google.com/go/trace/apiv2) |
| [Translate][cloud-translate] | stable | [`cloud.google.com/go/translate`](https://pkg.go.dev/cloud.google.com/go/translate) |
| [Video Intelligence][cloud-video] | beta | [`cloud.google.com/go/videointelligence/apiv1beta2`](https://pkg.go.dev/cloud.google.com/go/videointelligence/apiv1beta2) |
| [Vision][cloud-vision] | stable | [`cloud.google.com/go/vision/apiv1`](https://pkg.go.dev/cloud.google.com/go/vision/apiv1) |
| [Webrisk][cloud-webrisk] | alpha | [`cloud.google.com/go/webrisk/apiv1beta1`](https://pkg.go.dev/cloud.google.com/go/webrisk/apiv1beta1) |
> **Alpha status**: the API is still being actively developed. As a
> result, it might change in backward-incompatible ways and is not recommended
> for production use.
>
> **Beta status**: the API is largely complete, but still has outstanding
> features and bugs to be addressed. There may be minor backwards-incompatible
> changes where necessary.
>
> **Stable status**: the API is mature and ready for production use. We will
> continue addressing bugs and feature requests.
Documentation and examples are available at [pkg.go.dev/cloud.google.com/go](https://pkg.go.dev/cloud.google.com/go)
## [Go Versions Supported](#supported-versions)
We currently support Go versions 1.11 and newer.
## Authorization
By default, each API will use [Google Application Default Credentials](https://developers.google.com/identity/protocols/application-default-credentials)
for authorization credentials used in calling the API endpoints. This will allow your
application to run in many environments without requiring explicit configuration.
[snip]:# (auth)
```go
client, err := storage.NewClient(ctx)
```
To authorize using a
[JSON key file](https://cloud.google.com/iam/docs/managing-service-account-keys),
pass
[`option.WithCredentialsFile`](https://pkg.go.dev/google.golang.org/api/option#WithCredentialsFile)
to the `NewClient` function of the desired package. For example:
[snip]:# (auth-JSON)
```go
client, err := storage.NewClient(ctx, option.WithCredentialsFile("path/to/keyfile.json"))
```
You can exert more control over authorization by using the
[`golang.org/x/oauth2`](https://pkg.go.dev/golang.org/x/oauth2) package to
create an `oauth2.TokenSource`. Then pass
[`option.WithTokenSource`](https://pkg.go.dev/google.golang.org/api/option#WithTokenSource)
to the `NewClient` function:
[snip]:# (auth-ts)
```go
tokenSource := ...
client, err := storage.NewClient(ctx, option.WithTokenSource(tokenSource))
```
## Contributing
Contributions are welcome. Please, see the
[CONTRIBUTING](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/CONTRIBUTING.md)
document for details.
Please note that this project is released with a Contributor Code of Conduct.
By participating in this project you agree to abide by its terms.
See [Contributor Code of Conduct](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/CONTRIBUTING.md#contributor-code-of-conduct)
for more information.
[cloud-asset]: https://cloud.google.com/security-command-center/docs/how-to-asset-inventory
[cloud-automl]: https://cloud.google.com/automl
[cloud-build]: https://cloud.google.com/cloud-build/
[cloud-bigquery]: https://cloud.google.com/bigquery/
[cloud-bigtable]: https://cloud.google.com/bigtable/
[cloud-container]: https://cloud.google.com/containers/
[cloud-containeranalysis]: https://cloud.google.com/container-registry/docs/container-analysis
[cloud-dataproc]: https://cloud.google.com/dataproc/
[cloud-datastore]: https://cloud.google.com/datastore/
[cloud-dialogflow]: https://cloud.google.com/dialogflow-enterprise/
[cloud-debugger]: https://cloud.google.com/debugger/
[cloud-dlp]: https://cloud.google.com/dlp/
[cloud-errors]: https://cloud.google.com/error-reporting/
[cloud-firestore]: https://cloud.google.com/firestore/
[cloud-iam]: https://cloud.google.com/iam/
[cloud-iot]: https://cloud.google.com/iot-core/
[cloud-irm]: https://cloud.google.com/incident-response/docs/concepts
[cloud-kms]: https://cloud.google.com/kms/
[cloud-pubsub]: https://cloud.google.com/pubsub/
[cloud-storage]: https://cloud.google.com/storage/
[cloud-language]: https://cloud.google.com/natural-language
[cloud-logging]: https://cloud.google.com/logging/
[cloud-natural-language]: https://cloud.google.com/natural-language/
[cloud-memorystore]: https://cloud.google.com/memorystore/
[cloud-monitoring]: https://cloud.google.com/monitoring/
[cloud-oslogin]: https://cloud.google.com/compute/docs/oslogin/rest
[cloud-phishingprotection]: https://cloud.google.com/phishing-protection/
[cloud-securitycenter]: https://cloud.google.com/security-command-center/
[cloud-scheduler]: https://cloud.google.com/scheduler
[cloud-spanner]: https://cloud.google.com/spanner/
[cloud-speech]: https://cloud.google.com/speech
[cloud-talent]: https://cloud.google.com/solutions/talent-solution/
[cloud-tasks]: https://cloud.google.com/tasks/
[cloud-texttospeech]: https://cloud.google.com/texttospeech/
[cloud-talent]: https://cloud.google.com/solutions/talent-solution/
[cloud-trace]: https://cloud.google.com/trace/
[cloud-translate]: https://cloud.google.com/translate
[cloud-recaptcha]: https://cloud.google.com/recaptcha-enterprise/
[cloud-recommender]: https://cloud.google.com/recommendations/
[cloud-video]: https://cloud.google.com/video-intelligence/
[cloud-vision]: https://cloud.google.com/vision
[cloud-webrisk]: https://cloud.google.com/web-risk/
+144
View File
@@ -0,0 +1,144 @@
# Setup from scratch
1. [Install Go](https://golang.org/dl/).
1. Ensure that your `GOBIN` directory (by default `$(go env GOPATH)/bin`)
is in your `PATH`.
1. Check it's working by running `go version`.
* If it doesn't work, check the install location, usually
`/usr/local/go`, is on your `PATH`.
1. Sign one of the
[contributor license agreements](#contributor-license-agreements) below.
1. Clone the repo:
`git clone https://github.com/googleapis/google-cloud-go`
1. Change into the checked out source:
`cd google-cloud-go`
1. Fork the repo and add your fork as a secondary remote (this is necessary in
order to create PRs).
# Which module to release?
The Go client libraries have several modules. Each module does not strictly
correspond to a single library - they correspond to trees of directories. If a
file needs to be released, you must release the closest ancestor module.
To see all modules:
```
$ cat `find . -name go.mod` | grep module
module cloud.google.com/go
module cloud.google.com/go/bigtable
module cloud.google.com/go/firestore
module cloud.google.com/go/bigquery
module cloud.google.com/go/storage
module cloud.google.com/go/datastore
module cloud.google.com/go/pubsub
module cloud.google.com/go/spanner
module cloud.google.com/go/logging
```
The `cloud.google.com/go` is the repository root module. Each other module is
a submodule.
So, if you need to release a change in `bigtable/bttest/inmem.go`, the closest
ancestor module is `cloud.google.com/go/bigtable` - so you should release a new
version of the `cloud.google.com/go/bigtable` submodule.
If you need to release a change in `asset/apiv1/asset_client.go`, the closest
ancestor module is `cloud.google.com/go` - so you should release a new version
of the `cloud.google.com/go` repository root module. Note: releasing
`cloud.google.com/go` has no impact on any of the submodules, and vice-versa.
They are released entirely independently.
# Test failures
If there are any test failures in the Kokoro build, releases are blocked until
the failures have been resolved.
# How to release `cloud.google.com/go`
1. Check for failures in the
[continuous Kokoro build](http://go/google-cloud-go-continuous). If there are any
failures in the most recent build, address them before proceeding with the
release.
1. Navigate to `~/code/gocloud/` and switch to master.
1. `git pull`
1. Run `git tag -l | grep -v beta | grep -v alpha` to see all existing releases.
The current latest tag `$CV` is the largest tag. It should look something
like `vX.Y.Z` (note: ignore all `LIB/vX.Y.Z` tags - these are tags for a
specific library, not the module root). We'll call the current version `$CV`
and the new version `$NV`.
1. On master, run `git log $CV...` to list all the changes since the last
release. NOTE: You must manually visually parse out changes to submodules [1]
(the `git log` is going to show you things in submodules, which are not going
to be part of your release).
1. Edit `CHANGES.md` to include a summary of the changes.
1. `cd internal/version && go generate && cd -`
1. Commit the changes, push to your fork, and create a PR.
1. Wait for the PR to be reviewed and merged. Once it's merged, and without
merging any other PRs in the meantime:
a. Switch to master.
b. `git pull`
c. Tag the repo with the next version: `git tag $NV`.
d. Push the tag to origin:
`git push origin $NV`
1. Update [the releases page](https://github.com/googleapis/google-cloud-go/releases)
with the new release, copying the contents of `CHANGES.md`.
1. Run `go get cloud.google.com/go@vX.Y.Z` then wait for the release
to show up on http://pkg.go.dev/cloud.google.com/go (a few minutes).
1. Go to the [doc publishing job](http://go/google-cloud-go-publish-docs) and
trigger the job with the following environment variables:
`MODULE=cloud.google.com,VERSION=vX.Y.Z`.
Replace the version with the value for the module you're
releasing. See [`publish_docs.sh`](/internal/kokoro/publish_docs.sh).
# How to release a submodule
We have several submodules, including `cloud.google.com/go/logging`,
`cloud.google.com/go/datastore`, and so on.
To release a submodule:
(these instructions assume we're releasing `cloud.google.com/go/datastore` - adjust accordingly)
1. Check for failures in the
[continuous Kokoro build](http://go/google-cloud-go-continuous). If there are any
failures in the most recent build, address them before proceeding with the
release. (This applies even if the failures are in a different submodule from the one
being released.)
1. Navigate to `~/code/gocloud/` and switch to master.
1. `git pull`
1. Run `git tag -l | grep datastore | grep -v beta | grep -v alpha` to see all
existing releases. The current latest tag `$CV` is the largest tag. It
should look something like `datastore/vX.Y.Z`. We'll call the current version
`$CV` and the new version `$NV`.
1. On master, run `git log $CV.. -- datastore/` to list all the changes to the
submodule directory since the last release.
1. Edit `datastore/CHANGES.md` to include a summary of the changes.
1. `cd internal/version && go generate && cd -`
1. Commit the changes, push to your fork, and create a PR.
1. Wait for the PR to be reviewed and merged. Once it's merged, and without
merging any other PRs in the meantime:
a. Switch to master.
b. `git pull`
c. Tag the repo with the next version: `git tag $NV`.
d. Push the tag to origin:
`git push origin $NV`
1. Update [the releases page](https://github.com/googleapis/google-cloud-go/releases)
with the new release, copying the contents of `datastore/CHANGES.md`.
1. Run `go get cloud.google.com/go/datastore@vX.Y.Z` then wait for the release
to show up on http://pkg.go.dev/cloud.google.com/go/datastore (a few
minutes).
1. Go to the [doc publishing job](http://go/google-cloud-go-publish-docs) and
trigger the job with the following environment variables:
`MODULE=cloud.google.com/go/datastore,VERSION=vX.Y.Z`.
Replace the module path and version with the values for the module you're
releasing. You can leave all of the other fields blank.
See [`publish_docs.sh`](/internal/kokoro/publish_docs.sh).
# Appendix
1: This should get better as submodule tooling matures.
+519
View File
@@ -0,0 +1,519 @@
// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package metadata provides access to Google Compute Engine (GCE)
// metadata and API service accounts.
//
// This package is a wrapper around the GCE metadata service,
// as documented at https://developers.google.com/compute/docs/metadata.
package metadata // import "cloud.google.com/go/compute/metadata"
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"runtime"
"strings"
"sync"
"time"
)
const (
// metadataIP is the documented metadata server IP address.
metadataIP = "169.254.169.254"
// metadataHostEnv is the environment variable specifying the
// GCE metadata hostname. If empty, the default value of
// metadataIP ("169.254.169.254") is used instead.
// This is variable name is not defined by any spec, as far as
// I know; it was made up for the Go package.
metadataHostEnv = "GCE_METADATA_HOST"
userAgent = "gcloud-golang/0.1"
)
type cachedValue struct {
k string
trim bool
mu sync.Mutex
v string
}
var (
projID = &cachedValue{k: "project/project-id", trim: true}
projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
instID = &cachedValue{k: "instance/id", trim: true}
)
var defaultClient = &Client{hc: &http.Client{
Transport: &http.Transport{
Dial: (&net.Dialer{
Timeout: 2 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
},
}}
// NotDefinedError is returned when requested metadata is not defined.
//
// The underlying string is the suffix after "/computeMetadata/v1/".
//
// This error is not returned if the value is defined to be the empty
// string.
type NotDefinedError string
func (suffix NotDefinedError) Error() string {
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
}
func (c *cachedValue) get(cl *Client) (v string, err error) {
defer c.mu.Unlock()
c.mu.Lock()
if c.v != "" {
return c.v, nil
}
if c.trim {
v, err = cl.getTrimmed(c.k)
} else {
v, err = cl.Get(c.k)
}
if err == nil {
c.v = v
}
return
}
var (
onGCEOnce sync.Once
onGCE bool
)
// OnGCE reports whether this process is running on Google Compute Engine.
func OnGCE() bool {
onGCEOnce.Do(initOnGCE)
return onGCE
}
func initOnGCE() {
onGCE = testOnGCE()
}
func testOnGCE() bool {
// The user explicitly said they're on GCE, so trust them.
if os.Getenv(metadataHostEnv) != "" {
return true
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
resc := make(chan bool, 2)
// Try two strategies in parallel.
// See https://github.com/googleapis/google-cloud-go/issues/194
go func() {
req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
req.Header.Set("User-Agent", userAgent)
res, err := defaultClient.hc.Do(req.WithContext(ctx))
if err != nil {
resc <- false
return
}
defer res.Body.Close()
resc <- res.Header.Get("Metadata-Flavor") == "Google"
}()
go func() {
addrs, err := net.DefaultResolver.LookupHost(ctx, "metadata.google.internal")
if err != nil || len(addrs) == 0 {
resc <- false
return
}
resc <- strsContains(addrs, metadataIP)
}()
tryHarder := systemInfoSuggestsGCE()
if tryHarder {
res := <-resc
if res {
// The first strategy succeeded, so let's use it.
return true
}
// Wait for either the DNS or metadata server probe to
// contradict the other one and say we are running on
// GCE. Give it a lot of time to do so, since the system
// info already suggests we're running on a GCE BIOS.
timer := time.NewTimer(5 * time.Second)
defer timer.Stop()
select {
case res = <-resc:
return res
case <-timer.C:
// Too slow. Who knows what this system is.
return false
}
}
// There's no hint from the system info that we're running on
// GCE, so use the first probe's result as truth, whether it's
// true or false. The goal here is to optimize for speed for
// users who are NOT running on GCE. We can't assume that
// either a DNS lookup or an HTTP request to a blackholed IP
// address is fast. Worst case this should return when the
// metaClient's Transport.ResponseHeaderTimeout or
// Transport.Dial.Timeout fires (in two seconds).
return <-resc
}
// systemInfoSuggestsGCE reports whether the local system (without
// doing network requests) suggests that we're running on GCE. If this
// returns true, testOnGCE tries a bit harder to reach its metadata
// server.
func systemInfoSuggestsGCE() bool {
if runtime.GOOS != "linux" {
// We don't have any non-Linux clues available, at least yet.
return false
}
slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name")
name := strings.TrimSpace(string(slurp))
return name == "Google" || name == "Google Compute Engine"
}
// Subscribe calls Client.Subscribe on the default client.
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
return defaultClient.Subscribe(suffix, fn)
}
// Get calls Client.Get on the default client.
func Get(suffix string) (string, error) { return defaultClient.Get(suffix) }
// ProjectID returns the current instance's project ID string.
func ProjectID() (string, error) { return defaultClient.ProjectID() }
// NumericProjectID returns the current instance's numeric project ID.
func NumericProjectID() (string, error) { return defaultClient.NumericProjectID() }
// InternalIP returns the instance's primary internal IP address.
func InternalIP() (string, error) { return defaultClient.InternalIP() }
// ExternalIP returns the instance's primary external (public) IP address.
func ExternalIP() (string, error) { return defaultClient.ExternalIP() }
// Email calls Client.Email on the default client.
func Email(serviceAccount string) (string, error) { return defaultClient.Email(serviceAccount) }
// Hostname returns the instance's hostname. This will be of the form
// "<instanceID>.c.<projID>.internal".
func Hostname() (string, error) { return defaultClient.Hostname() }
// InstanceTags returns the list of user-defined instance tags,
// assigned when initially creating a GCE instance.
func InstanceTags() ([]string, error) { return defaultClient.InstanceTags() }
// InstanceID returns the current VM's numeric instance ID.
func InstanceID() (string, error) { return defaultClient.InstanceID() }
// InstanceName returns the current VM's instance ID string.
func InstanceName() (string, error) { return defaultClient.InstanceName() }
// Zone returns the current VM's zone, such as "us-central1-b".
func Zone() (string, error) { return defaultClient.Zone() }
// InstanceAttributes calls Client.InstanceAttributes on the default client.
func InstanceAttributes() ([]string, error) { return defaultClient.InstanceAttributes() }
// ProjectAttributes calls Client.ProjectAttributes on the default client.
func ProjectAttributes() ([]string, error) { return defaultClient.ProjectAttributes() }
// InstanceAttributeValue calls Client.InstanceAttributeValue on the default client.
func InstanceAttributeValue(attr string) (string, error) {
return defaultClient.InstanceAttributeValue(attr)
}
// ProjectAttributeValue calls Client.ProjectAttributeValue on the default client.
func ProjectAttributeValue(attr string) (string, error) {
return defaultClient.ProjectAttributeValue(attr)
}
// Scopes calls Client.Scopes on the default client.
func Scopes(serviceAccount string) ([]string, error) { return defaultClient.Scopes(serviceAccount) }
func strsContains(ss []string, s string) bool {
for _, v := range ss {
if v == s {
return true
}
}
return false
}
// A Client provides metadata.
type Client struct {
hc *http.Client
}
// NewClient returns a Client that can be used to fetch metadata.
// Returns the client that uses the specified http.Client for HTTP requests.
// If nil is specified, returns the default client.
func NewClient(c *http.Client) *Client {
if c == nil {
return defaultClient
}
return &Client{hc: c}
}
// getETag returns a value from the metadata service as well as the associated ETag.
// This func is otherwise equivalent to Get.
func (c *Client) getETag(suffix string) (value, etag string, err error) {
// Using a fixed IP makes it very difficult to spoof the metadata service in
// a container, which is an important use-case for local testing of cloud
// deployments. To enable spoofing of the metadata service, the environment
// variable GCE_METADATA_HOST is first inspected to decide where metadata
// requests shall go.
host := os.Getenv(metadataHostEnv)
if host == "" {
// Using 169.254.169.254 instead of "metadata" here because Go
// binaries built with the "netgo" tag and without cgo won't
// know the search suffix for "metadata" is
// ".google.internal", and this IP address is documented as
// being stable anyway.
host = metadataIP
}
suffix = strings.TrimLeft(suffix, "/")
u := "http://" + host + "/computeMetadata/v1/" + suffix
req, err := http.NewRequest("GET", u, nil)
if err != nil {
return "", "", err
}
req.Header.Set("Metadata-Flavor", "Google")
req.Header.Set("User-Agent", userAgent)
res, err := c.hc.Do(req)
if err != nil {
return "", "", err
}
defer res.Body.Close()
if res.StatusCode == http.StatusNotFound {
return "", "", NotDefinedError(suffix)
}
all, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", "", err
}
if res.StatusCode != 200 {
return "", "", &Error{Code: res.StatusCode, Message: string(all)}
}
return string(all), res.Header.Get("Etag"), nil
}
// Get returns a value from the metadata service.
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
//
// If the GCE_METADATA_HOST environment variable is not defined, a default of
// 169.254.169.254 will be used instead.
//
// If the requested metadata is not defined, the returned error will
// be of type NotDefinedError.
func (c *Client) Get(suffix string) (string, error) {
val, _, err := c.getETag(suffix)
return val, err
}
func (c *Client) getTrimmed(suffix string) (s string, err error) {
s, err = c.Get(suffix)
s = strings.TrimSpace(s)
return
}
func (c *Client) lines(suffix string) ([]string, error) {
j, err := c.Get(suffix)
if err != nil {
return nil, err
}
s := strings.Split(strings.TrimSpace(j), "\n")
for i := range s {
s[i] = strings.TrimSpace(s[i])
}
return s, nil
}
// ProjectID returns the current instance's project ID string.
func (c *Client) ProjectID() (string, error) { return projID.get(c) }
// NumericProjectID returns the current instance's numeric project ID.
func (c *Client) NumericProjectID() (string, error) { return projNum.get(c) }
// InstanceID returns the current VM's numeric instance ID.
func (c *Client) InstanceID() (string, error) { return instID.get(c) }
// InternalIP returns the instance's primary internal IP address.
func (c *Client) InternalIP() (string, error) {
return c.getTrimmed("instance/network-interfaces/0/ip")
}
// Email returns the email address associated with the service account.
// The account may be empty or the string "default" to use the instance's
// main account.
func (c *Client) Email(serviceAccount string) (string, error) {
if serviceAccount == "" {
serviceAccount = "default"
}
return c.getTrimmed("instance/service-accounts/" + serviceAccount + "/email")
}
// ExternalIP returns the instance's primary external (public) IP address.
func (c *Client) ExternalIP() (string, error) {
return c.getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
}
// Hostname returns the instance's hostname. This will be of the form
// "<instanceID>.c.<projID>.internal".
func (c *Client) Hostname() (string, error) {
return c.getTrimmed("instance/hostname")
}
// InstanceTags returns the list of user-defined instance tags,
// assigned when initially creating a GCE instance.
func (c *Client) InstanceTags() ([]string, error) {
var s []string
j, err := c.Get("instance/tags")
if err != nil {
return nil, err
}
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
return nil, err
}
return s, nil
}
// InstanceName returns the current VM's instance ID string.
func (c *Client) InstanceName() (string, error) {
return c.getTrimmed("instance/name")
}
// Zone returns the current VM's zone, such as "us-central1-b".
func (c *Client) Zone() (string, error) {
zone, err := c.getTrimmed("instance/zone")
// zone is of the form "projects/<projNum>/zones/<zoneName>".
if err != nil {
return "", err
}
return zone[strings.LastIndex(zone, "/")+1:], nil
}
// InstanceAttributes returns the list of user-defined attributes,
// assigned when initially creating a GCE VM instance. The value of an
// attribute can be obtained with InstanceAttributeValue.
func (c *Client) InstanceAttributes() ([]string, error) { return c.lines("instance/attributes/") }
// ProjectAttributes returns the list of user-defined attributes
// applying to the project as a whole, not just this VM. The value of
// an attribute can be obtained with ProjectAttributeValue.
func (c *Client) ProjectAttributes() ([]string, error) { return c.lines("project/attributes/") }
// InstanceAttributeValue returns the value of the provided VM
// instance attribute.
//
// If the requested attribute is not defined, the returned error will
// be of type NotDefinedError.
//
// InstanceAttributeValue may return ("", nil) if the attribute was
// defined to be the empty string.
func (c *Client) InstanceAttributeValue(attr string) (string, error) {
return c.Get("instance/attributes/" + attr)
}
// ProjectAttributeValue returns the value of the provided
// project attribute.
//
// If the requested attribute is not defined, the returned error will
// be of type NotDefinedError.
//
// ProjectAttributeValue may return ("", nil) if the attribute was
// defined to be the empty string.
func (c *Client) ProjectAttributeValue(attr string) (string, error) {
return c.Get("project/attributes/" + attr)
}
// Scopes returns the service account scopes for the given account.
// The account may be empty or the string "default" to use the instance's
// main account.
func (c *Client) Scopes(serviceAccount string) ([]string, error) {
if serviceAccount == "" {
serviceAccount = "default"
}
return c.lines("instance/service-accounts/" + serviceAccount + "/scopes")
}
// Subscribe subscribes to a value from the metadata service.
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
// The suffix may contain query parameters.
//
// Subscribe calls fn with the latest metadata value indicated by the provided
// suffix. If the metadata value is deleted, fn is called with the empty string
// and ok false. Subscribe blocks until fn returns a non-nil error or the value
// is deleted. Subscribe returns the error value returned from the last call to
// fn, which may be nil when ok == false.
func (c *Client) Subscribe(suffix string, fn func(v string, ok bool) error) error {
const failedSubscribeSleep = time.Second * 5
// First check to see if the metadata value exists at all.
val, lastETag, err := c.getETag(suffix)
if err != nil {
return err
}
if err := fn(val, true); err != nil {
return err
}
ok := true
if strings.ContainsRune(suffix, '?') {
suffix += "&wait_for_change=true&last_etag="
} else {
suffix += "?wait_for_change=true&last_etag="
}
for {
val, etag, err := c.getETag(suffix + url.QueryEscape(lastETag))
if err != nil {
if _, deleted := err.(NotDefinedError); !deleted {
time.Sleep(failedSubscribeSleep)
continue // Retry on other errors.
}
ok = false
}
lastETag = etag
if err := fn(val, ok); err != nil || !ok {
return err
}
}
}
// Error contains an error response from the server.
type Error struct {
// Code is the HTTP response status code.
Code int
// Message is the server response message.
Message string
}
func (e *Error) Error() string {
return fmt.Sprintf("compute: Received %d `%s`", e.Code, e.Message)
}
+109
View File
@@ -0,0 +1,109 @@
// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
Package cloud is the root of the packages used to access Google Cloud
Services. See https://godoc.org/cloud.google.com/go for a full list
of sub-packages.
Client Options
All clients in sub-packages are configurable via client options. These options are
described here: https://godoc.org/google.golang.org/api/option.
Authentication and Authorization
All the clients in sub-packages support authentication via Google Application Default
Credentials (see https://cloud.google.com/docs/authentication/production), or
by providing a JSON key file for a Service Account. See the authentication examples
in this package for details.
Timeouts and Cancellation
By default, non-streaming methods, like Create or Get, will have a default deadline applied to the
context provided at call time, unless a context deadline is already set. Streaming
methods have no default deadline and will run indefinitely. To set timeouts or
arrange for cancellation, use contexts. See the examples for details. Transient
errors will be retried when correctness allows.
To opt out of default deadlines, set the temporary environment variable
GOOGLE_API_GO_EXPERIMENTAL_DISABLE_DEFAULT_DEADLINE to "true" prior to client
creation. This affects all Google Cloud Go client libraries. This opt-out
mechanism will be removed in a future release. File an issue at
https://github.com/googleapis/google-cloud-go if the default deadlines
cannot work for you.
Do not attempt to control the initial connection (dialing) of a service by setting a
timeout on the context passed to NewClient. Dialing is non-blocking, so timeouts
would be ineffective and would only interfere with credential refreshing, which uses
the same context.
Connection Pooling
Connection pooling differs in clients based on their transport. Cloud
clients either rely on HTTP or gRPC transports to communicate
with Google Cloud.
Cloud clients that use HTTP (bigquery, compute, storage, and translate) rely on the
underlying HTTP transport to cache connections for later re-use. These are cached to
the default http.MaxIdleConns and http.MaxIdleConnsPerHost settings in
http.DefaultTransport.
For gRPC clients (all others in this repo), connection pooling is configurable. Users
of cloud client libraries may specify option.WithGRPCConnectionPool(n) as a client
option to NewClient calls. This configures the underlying gRPC connections to be
pooled and addressed in a round robin fashion.
Using the Libraries with Docker
Minimal docker images like Alpine lack CA certificates. This causes RPCs to appear to
hang, because gRPC retries indefinitely. See https://github.com/googleapis/google-cloud-go/issues/928
for more information.
Debugging
To see gRPC logs, set the environment variable GRPC_GO_LOG_SEVERITY_LEVEL. See
https://godoc.org/google.golang.org/grpc/grpclog for more information.
For HTTP logging, set the GODEBUG environment variable to "http2debug=1" or "http2debug=2".
Client Stability
Clients in this repository are considered alpha or beta unless otherwise
marked as stable in the README.md. Semver is not used to communicate stability
of clients.
Alpha and beta clients may change or go away without notice.
Clients marked stable will maintain compatibility with future versions for as
long as we can reasonably sustain. Incompatible changes might be made in some
situations, including:
- Security bugs may prompt backwards-incompatible changes.
- Situations in which components are no longer feasible to maintain without
making breaking changes, including removal.
- Parts of the client surface may be outright unstable and subject to change.
These parts of the surface will be labeled with the note, "It is EXPERIMENTAL
and subject to change or removal without notice."
*/
package cloud // import "cloud.google.com/go"
+23
View File
@@ -0,0 +1,23 @@
module cloud.google.com/go
go 1.11
require (
cloud.google.com/go/storage v1.10.0
github.com/golang/mock v1.4.4
github.com/golang/protobuf v1.4.2
github.com/google/go-cmp v0.5.2
github.com/google/martian/v3 v3.0.0
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7
github.com/googleapis/gax-go/v2 v2.0.5
github.com/jstemmer/go-junit-report v0.9.1
go.opencensus.io v0.22.4
golang.org/x/lint v0.0.0-20200302205851-738671d3881b
golang.org/x/net v0.0.0-20200927032502-5d4f70055728
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
golang.org/x/text v0.3.3
golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d
google.golang.org/api v0.32.0
google.golang.org/genproto v0.0.0-20201002142447-3860012362da
google.golang.org/grpc v1.32.0
)
+526
View File
@@ -0,0 +1,526 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1 h1:hL+ycaJpVE9M7nLoiXb/Pn10ENE2u+oddxbD8uu0ZVU=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0 h1:sAbMqjY1PEQKZBWfbu6Y6bsupJ9c4QdHnzg/VvYTLcE=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0 h1:xE3CPsOgttP4ACBePh79zTKALtXwn/Edhcr16R5hMWU=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0 h1:a/O/bK/vWrYGOTFtH8di4rBxMZnmkjy+Y5LxpDwo+dA=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0 h1:Kt+gOPPp2LEPWp8CSfxhsM8ik9CcyE/gYu+0r+RnZvM=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1 h1:W9tAK3E57P75u0XLLR82LZyw8VpAnhmyTOxW9qzmyj8=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0 h1:9/vpR43S4aJaROxqQHQ3nH9lfyKKV0dC3vOmnw8ebQQ=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0 h1:Lpy6hKgdcl7a3WGSfJIFmxmcdjSpP6OmBEfcOv1Y680=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0 h1:VV2nUM3wwLLGh9lSABFgZMjInyUbJeaRSE64WuAIQ+4=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0 h1:RPUcBvDeYgQFMfQu1eBMq6piD1SXmLH+vK3qjewZPus=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0 h1:UDpwYIwla4jHGzZJaEJYx1tOejbgSoNqsAfHAUYe2r8=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0 h1:86K1Gel7BQ9/WmNWn7dTKMvTLFzwtBe5FNqYbi9X35g=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/davecgh/go-spew v1.1.0/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/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0 h1:Rd1kQnQu0Hq3qvJppYSG0HtP+f5LPPUiDswTLiEegLg=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
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 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57 h1:eqyIo2HjKhKe/mJzTG8n4VqvLXIOEG+SLdDqX7xGtkY=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f h1:Jnx61latede7zDD3DiiP4gmNz33uK0U5HDUaF0a/HVQ=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99 h1:Ak8CrdlwwXwAZxzS66vgPt4U8yUZX7JwLvVR58FN5jM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7 h1:k+KkMRk8mGOu1xG38StS7dQ+Z6oW1i9n3dgrAVU9Q/E=
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4 h1:hU4mGcQI4DaAYW+IbTun+2qEZVFxK0ySjQLTbS0VQKc=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6 h1:UDMh68UUwekSh5iP2OMhRRZJiiBccgV7axzUG8vi56c=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4 h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522 h1:OeRHuibLsmZkFj773W4LcfAGsSxJgfPONhr8cmO+eLA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979 h1:Agxu5KLo8o7Bb634SVDnhIfpTvxmzUwhbYAzBvXt6h4=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f h1:hX65Cu3JDlGH3uEdK7I99Ii+9kjD6mvnnpfLdEAH0x4=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac h1:8R1esu+8QioDxo4E4mX6bFztO+dMTM49DNAaWfO5OeY=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367 h1:0IiAsCRByjO2QjX7ZPkw5oU9x+n1YqRL802rjC0c3Aw=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 h1:WQ8q63x+f/zpC8Ac1s9wLElVoHhm32p6tudrU72n1QA=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200927032502-5d4f70055728 h1:5wtQIAulKU5AbLQOkjxl32UufnIOqgBX72pS0AV14H0=
golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 h1:ld7aEMNHoBnnDAX15v1T6z31v8HwR2A9FYOuAhWqkwc=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e h1:hq86ru83GdWTlfQFZGO4nZJTU4Bs2wfHl8oFHRaXsfc=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25 h1:OKbAoGs4fGM5cPLlVQLZGYkFC8OnOfgo6tt0Smf9XhM=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 h1:B6caxRw+hozq68X2MY7jEpZh/cr4/aHLv9xU8Kkadrw=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f h1:Fqb3ao1hUmOR3GkUOg/Y+BadLwykBIzs5q8Ez2SbHyc=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138 h1:H3uGjxCR/6Ds0Mjgyp7LMK81+LvmbvWWEnJhzk1Pi9E=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c h1:97SnQk1GYRXJgvwZ8fadnxDOWfKvkNQHH3CtZntPSrM=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0 h1:Dh6fw+p6FyRl5x/FvNswO1ji0lIGzm3KP8Y9VkS9PTE=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff h1:On1qIo75ByTwFJ4/W2bIqHcwJ9XAqtSWUs8GwRrIhtc=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d h1:lzLdP95xJmMpwQ6LUHwrc5V7js93hTiY7gkznu0BgmY=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88 h1:4j84u0sokprDu3IdSYHJMmou+YSLflMz8p7yAx/QI4g=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d h1:szSOL78iTCl0LF1AMjhSWJj8tIM0KixlUUnBtYXsmd8=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d h1:vWQvJ/Z0Lu+9/8oQ/pAYXNzbc7CMnBl+tULGVHOy3oE=
golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0 h1:VGGbLNyPF7dvYHhcUGYBBGCRDDK0RRJAI6KCvo0CL+E=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0 h1:jbyannxz0XFD3zdjgrSUsaJbgpH4eTrkdhRChkHPfO8=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0 h1:yzlyyDW/J0w8yNFJIhiAJy4kq74S+1DOLdawELNxFMA=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0 h1:0q95w+VuFtv4PAx4PZVQdBMmYbaCHbnfKaEiDIcVyag=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0 h1:J1Pl9P2lnmYFSJvgs70DKELqHNh8CNWXPbud4njEE2s=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0 h1:cG03eaksBzhfSIk7JRGctfp3lanklcOM/mTGvow7BbQ=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0 h1:BaiDisFir8O4IJxvAabCGGkQ6yCJegNQqSVoYUNAnbk=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.32.0 h1:Le77IccnTqEa8ryp9wIpX5W3zYm7Gf9LhOp9PHcwFts=
google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19 h1:Lj2SnHtxkRGJDqnGaSjo+CCdIieEnwVazbOXILwQemk=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64 h1:iKtrH9Y8mcbADOP0YFaEMth7OfuHY9xHOwNj4znpM1A=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51 h1:Ex1mq5jaJof+kRnYi3SlYJ8KKa9Ao3NHyIT5XJ1gF6U=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84 h1:pSLkPbrjnPyLDYUO2VM9mDLqo2V6CFBY84lFSZAfoi4=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380 h1:xriR1EgvKfkKxIoU2uUvrMVl+H26359loFFUleSMXFo=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c h1:Lq4llNryJoaVFRmvrIwC/ZHH7tNt4tUYIu8+se2aayY=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201002142447-3860012362da h1:DTQYk4u7nICKkkVZsBv0/0po0ChISxAJ5CTAfUhO0PQ=
google.golang.org/genproto v0.0.0-20201002142447-3860012362da/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1 h1:SfXqXS5hkufcdZ/mHtYCh53P2b+92WQq/DZcKLgsFRs=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
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 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0 h1:cJv5/xdbk1NnMPR1VP9+HU6gupuG9MLBoH1r6RHZ2MY=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc h1:TnonUr8u3himcMY0vSh23jFOXA+cnucl1gB6EQTReBI=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a h1:/8zB6iBfHCl1qAnEAWwGPNrUvapuy6CPla1VM0k8hQw=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a h1:LJwr7TCTghdatWv40WobzlKXc9c4s8oGa7QKJUtHhWA=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
Loaded 100 of 4795 files, more files were not shown because too many files have changed in this diff. Show more