Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42cd1f8c4b | ||
|
|
1c02ed9115 | ||
|
|
dd8e532d18 | ||
|
|
505f302c96 | ||
|
|
f9dc3e5abf | ||
|
|
b58719b697 | ||
|
|
a3f0e90300 | ||
|
|
fb5b42171d |
No files matched your search
+3
-12
@@ -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
|
||||
@@ -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"
|
||||
@@ -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
@@ -1 +1 @@
|
||||
1.17.5
|
||||
1.15.2
|
||||
@@ -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!
|
||||
@@ -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
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.17
|
||||
FROM golang:1.15
|
||||
|
||||
WORKDIR /go/src/terraform-provider-boundary
|
||||
COPY . .
|
||||
|
||||
@@ -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,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
@@ -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
@@ -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.
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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>
|
||||
```
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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" {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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"
|
||||
)
|
||||
@@ -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{
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+24
-42
@@ -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
|
||||
+37
-117
@@ -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
|
||||
}
|
||||
}
|
||||
+68
-73
@@ -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:
|
||||
+55
-59
@@ -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:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
Generated
+11
@@ -0,0 +1,11 @@
|
||||
# Editors
|
||||
.idea
|
||||
.vscode
|
||||
*.swp
|
||||
|
||||
# Test files
|
||||
*.test
|
||||
coverage.txt
|
||||
|
||||
# Other
|
||||
.DS_Store
|
||||
+1695
File diff suppressed because it is too large.
Load diff
+44
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,177 @@
|
||||
# Google Cloud Client Libraries for Go
|
||||
|
||||
[](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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
Reference in new issue
Block a user