Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02445d2192 | ||
|
|
123284e88f | ||
|
|
6b28ea22a8 | ||
|
|
649b6a54f5 | ||
|
|
93471e3b28 | ||
|
|
452fd1c764 | ||
|
|
cfe7d47286 | ||
|
|
af98aaa9b2 | ||
|
|
10b421cec8 | ||
|
|
98d768f8c8 | ||
|
|
c869dfb367 | ||
|
|
5f4fa20ca8 | ||
|
|
6374cb307b | ||
|
|
9c054c1ac8 | ||
|
|
725f2d8fb9 | ||
|
|
7372487f84 | ||
|
|
f202fbc06d | ||
|
|
8bb1490ebd | ||
|
|
45c1a8c5cc | ||
|
|
a611a2bcbd | ||
|
|
d3cfbee0be | ||
|
|
cf1538c56a | ||
|
|
6cefb8e376 | ||
|
|
77eda49baa | ||
|
|
3f8368e7a6 | ||
|
|
a24f9e15e4 | ||
|
|
99914e6317 | ||
|
|
336f68ba68 | ||
|
|
e1f8352fc4 | ||
|
|
4f0509c55d | ||
|
|
7c73927d17 | ||
|
|
3c5bbd53c6 | ||
|
|
2d2f825035 | ||
|
|
30b38ea89d | ||
|
|
1c1e593eab | ||
|
|
8bfac9f6fa | ||
|
|
3d8121234f | ||
|
|
549a843382 | ||
|
|
38f1cf40e9 | ||
|
|
4f4318d005 | ||
|
|
5af7f8b662 | ||
|
|
3d42195a26 | ||
|
|
9bc08d4c48 | ||
|
|
4de1075067 | ||
|
|
dc46913c63 | ||
|
|
bb85735e02 | ||
|
|
f49ee1d5bc | ||
|
|
b01f482fb2 | ||
|
|
b69e175687 | ||
|
|
3f0d2b2acd | ||
|
|
a2c645304f | ||
|
|
c0aa9bf5b1 | ||
|
|
7403e3648e |
No files matched your search
+12
-3
@@ -1,16 +1,25 @@
|
|||||||
version: 2.1
|
version: 2.1
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
resource_class: large
|
||||||
machine:
|
machine:
|
||||||
image: 'ubuntu-1604:201903-01'
|
image: 'ubuntu-2004:202107-02'
|
||||||
working_directory: ~/terraform-provider-watchtower
|
working_directory: ~/terraform-provider-watchtower
|
||||||
steps:
|
steps:
|
||||||
- add_ssh_keys:
|
- add_ssh_keys:
|
||||||
fingerprints:
|
fingerprints:
|
||||||
# hashicorp-ci github "machine" user, for private modules
|
# hc-github-team-secure-boundary github user,TF_ACC=1 ~/.go/bin/go test ./... -v $(TESTARGS) -timeout 120m to read private modules
|
||||||
- "c6:96:98:82:dc:04:6c:39:dd:ac:83:05:e3:15:1c:98"
|
- "d4:7f:b2:f7:6f:4d:e7:21:cc:d2:b7:6a:8b:68:e6:d4"
|
||||||
- checkout
|
- 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:
|
- run:
|
||||||
name: "Run Acceptance Tests"
|
name: "Run Acceptance Tests"
|
||||||
command: |
|
command: |
|
||||||
|
source ~/.bashrc
|
||||||
make testacc-ci
|
make testacc-ci
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
1.15.2
|
1.17.5
|
||||||
+76
-3
@@ -1,3 +1,76 @@
|
|||||||
|
## 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)
|
## 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.
|
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.
|
||||||
@@ -5,14 +78,14 @@ We are bumping the version of the Boundary Terraform provider to v1.0.0 and will
|
|||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* During `terraform apply`, do not update existing user account passwords when the password field is updated in the tf file.
|
* 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))
|
([Issue](https://github.com/hashicorp/terraform-provider-boundary/issues/71)),
|
||||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/70))
|
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/70)).
|
||||||
|
|
||||||
## 0.1.4 (January 14, 2021)
|
## 0.1.4 (January 14, 2021)
|
||||||
|
|
||||||
### New and Improved
|
### New and Improved
|
||||||
|
|
||||||
Update to the Boundary Go SDK v0.0.3
|
Update provider to handle new domain errors ([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/63)).
|
||||||
|
|
||||||
## 0.1.0 (October 14, 2020)
|
## 0.1.0 (October 14, 2020)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# 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.15
|
FROM golang:1.17
|
||||||
|
|
||||||
WORKDIR /go/src/terraform-provider-boundary
|
WORKDIR /go/src/terraform-provider-boundary
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
default: testacc
|
default: testacc
|
||||||
GOOS=$(shell go env GOOS)
|
GOOS=$(shell go env GOOS)
|
||||||
GOARCH=$(shell go env GOARCH)
|
GOARCH=$(shell go env GOARCH)
|
||||||
INSTALL_PATH=~/.local/share/terraform/plugins/localhost/providers/boundary/0.0.1/linux_$(GOARCH)
|
INSTALL_PATH=~/.local/share/terraform/plugins/localhost/providers/boundary/0.0.1/linux_$(GOARCH)
|
||||||
@@ -11,6 +11,16 @@ ifeq ($(GOOS), "windows")
|
|||||||
INSTALL_PATH=%APPDATA%/HashiCorp/Terraform/plugins/localhost/providers/boundary/0.0.1/windows_$(GOARCH)
|
INSTALL_PATH=%APPDATA%/HashiCorp/Terraform/plugins/localhost/providers/boundary/0.0.1/windows_$(GOARCH)
|
||||||
endif
|
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:
|
tools:
|
||||||
go generate -tags tools tools/tools.go
|
go generate -tags tools tools/tools.go
|
||||||
|
|
||||||
@@ -24,15 +34,17 @@ test:
|
|||||||
testacc:
|
testacc:
|
||||||
TF_ACC=1 go test ./... -v $(TESTARGS) -timeout 120m
|
TF_ACC=1 go test ./... -v $(TESTARGS) -timeout 120m
|
||||||
|
|
||||||
testacc-ci: install-go
|
testacc-ci:
|
||||||
git config --global --add url."git@github.com:".insteadOf "https://github.com/"
|
git config --global --add url."git@github.com:".insteadOf "https://github.com/"
|
||||||
TF_ACC=1 ~/.go/bin/go test ./... -v $(TESTARGS) -timeout 120m
|
TF_ACC=1 ~/.go/bin/go test ./... -v $(TESTARGS) -timeout 120m
|
||||||
|
|
||||||
install-go:
|
install-go: ~/.go/bin/go
|
||||||
|
|
||||||
|
~/.go/bin/go:
|
||||||
./ci/goinstall.sh
|
./ci/goinstall.sh
|
||||||
|
|
||||||
dev:
|
dev:
|
||||||
mkdir -p $(INSTALL_PATH)
|
mkdir -p $(INSTALL_PATH)
|
||||||
go build -o $(INSTALL_PATH)/terraform-provider-boundary main.go
|
go build -o $(INSTALL_PATH)/terraform-provider-boundary main.go
|
||||||
|
|
||||||
all:
|
all:
|
||||||
@@ -41,7 +53,35 @@ all:
|
|||||||
GOOS=windows go build -o $(BUILD_ALL_PATH)/terraform-provider-boundary_windows-amd64 main.go
|
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
|
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:
|
rm-id-flag-from-docs:
|
||||||
find docs/ -name "*.md" -type f | xargs sed -i -e '/- \*\*id\*\*/d'
|
find docs/ -name "*.md" -type f | xargs sed -i -e '/- \*\*id\*\*/d'
|
||||||
|
|
||||||
.PHONY: testacc tools
|
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
|
||||||
@@ -9,7 +9,7 @@ Requirements
|
|||||||
------------
|
------------
|
||||||
|
|
||||||
- [Terraform](https://www.terraform.io/downloads.html) >= 0.12.x
|
- [Terraform](https://www.terraform.io/downloads.html) >= 0.12.x
|
||||||
- [Go](https://golang.org/doc/install) >= 1.15
|
- [Go](https://golang.org/doc/install) >= 1.17
|
||||||
|
|
||||||
Building The Provider
|
Building The Provider
|
||||||
---------------------
|
---------------------
|
||||||
@@ -53,7 +53,15 @@ 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.
|
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, run `make testacc`.
|
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`.
|
||||||
|
|
||||||
*Note:* Acceptance tests create real resources, and often cost money to run.
|
*Note:* Acceptance tests create real resources, and often cost money to run.
|
||||||
|
|
||||||
@@ -61,6 +69,9 @@ In order to run the full suite of Acceptance tests, run `make testacc`.
|
|||||||
$ 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
|
Generating Docs
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
@@ -129,7 +140,7 @@ resource "boundary_user" "users" {
|
|||||||
scope_id = boundary_scope.corp.id
|
scope_id = boundary_scope.corp.id
|
||||||
}
|
}
|
||||||
|
|
||||||
// organiation level group for readonly users
|
// organization level group for readonly users
|
||||||
resource "boundary_group" "readonly" {
|
resource "boundary_group" "readonly" {
|
||||||
name = "readonly"
|
name = "readonly"
|
||||||
description = "Organization group for readonly users"
|
description = "Organization group for readonly users"
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
VERSION="1.15"
|
VERSION="1.17.5"
|
||||||
|
|
||||||
[ -z "$GOROOT" ] && GOROOT="$HOME/.go"
|
[ -z "$GOROOT" ] && GOROOT="$HOME/.go"
|
||||||
[ -z "$GOPATH" ] && GOPATH="$HOME/go"
|
[ -z "$GOPATH" ] && GOPATH="$HOME/go"
|
||||||
|
|||||||
@@ -35,4 +35,5 @@ provider "boundary" {
|
|||||||
- **password_auth_method_login_name** (String) The auth method login name for password-style auth methods
|
- **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
|
- **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
|
- **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.
|
- **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.
|
||||||
@@ -6,7 +6,7 @@ description: |-
|
|||||||
The account resource allows you to configure a Boundary account.
|
The account resource allows you to configure a Boundary account.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Resource `boundary_account`
|
# boundary_account (Resource)
|
||||||
|
|
||||||
The account resource allows you to configure a Boundary account.
|
The account resource allows you to configure a Boundary account.
|
||||||
|
|
||||||
@@ -45,11 +45,11 @@ resource "boundary_account" "jeff" {
|
|||||||
### Optional
|
### Optional
|
||||||
|
|
||||||
- **description** (String) The account description.
|
- **description** (String) The account description.
|
||||||
- **login_name** (String) The login name for this account.
|
- **login_name** (String, Deprecated) The login name for this account.
|
||||||
- **name** (String) The account name. Defaults to the resource name.
|
- **name** (String) The account name. Defaults to the resource name.
|
||||||
- **password** (String) The account password. Only set on create, changes will not be reflected when updating account.
|
- **password** (String, Deprecated) The account password. Only set on create, changes will not be reflected when updating account.
|
||||||
|
|
||||||
### Read-only
|
### Read-Only
|
||||||
|
|
||||||
- **id** (String) The ID of the account.
|
- **id** (String) The ID of the account.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@ description: |-
|
|||||||
The auth method resource allows you to configure a Boundary auth_method.
|
The auth method resource allows you to configure a Boundary auth_method.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Resource `boundary_auth_method`
|
# boundary_auth_method (Resource)
|
||||||
|
|
||||||
The auth method resource allows you to configure a Boundary auth_method.
|
The auth method resource allows you to configure a Boundary auth_method.
|
||||||
|
|
||||||
@@ -38,11 +38,11 @@ resource "boundary_auth_method" "password" {
|
|||||||
### Optional
|
### Optional
|
||||||
|
|
||||||
- **description** (String) The auth method description.
|
- **description** (String) The auth method description.
|
||||||
- **min_login_name_length** (Number) The minimum login name length.
|
- **min_login_name_length** (Number, Deprecated) The minimum login name length.
|
||||||
- **min_password_length** (Number) The minimum password length.
|
- **min_password_length** (Number, Deprecated) The minimum password length.
|
||||||
- **name** (String) The auth method name. Defaults to the resource name.
|
- **name** (String) The auth method name. Defaults to the resource name.
|
||||||
|
|
||||||
### Read-only
|
### Read-Only
|
||||||
|
|
||||||
- **id** (String) The ID of the account.
|
- **id** (String) The ID of the account.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
---
|
||||||
|
# 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>
|
||||||
|
```
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
---
|
||||||
|
# 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>
|
||||||
|
```
|
||||||
@@ -77,7 +77,7 @@ resource "boundary_group" "example" {
|
|||||||
- **member_ids** (Set of String) Resource IDs for group members, these are most likely boundary users.
|
- **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.
|
- **name** (String) The group name. Defaults to the resource name.
|
||||||
|
|
||||||
### Read-only
|
### Read-Only
|
||||||
|
|
||||||
- **id** (String) The ID of the group.
|
- **id** (String) The ID of the group.
|
||||||
|
|
||||||
|
|||||||
+16
-9
@@ -3,12 +3,12 @@
|
|||||||
page_title: "boundary_host Resource - terraform-provider-boundary"
|
page_title: "boundary_host Resource - terraform-provider-boundary"
|
||||||
subcategory: ""
|
subcategory: ""
|
||||||
description: |-
|
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.
|
Deprecated: use resource_host_static instead.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Resource `boundary_host`
|
# boundary_host (Resource)
|
||||||
|
|
||||||
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.
|
Deprecated: use `resource_host_static` instead.
|
||||||
|
|
||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
@@ -28,12 +28,19 @@ resource "boundary_scope" "project" {
|
|||||||
auto_create_admin_role = true
|
auto_create_admin_role = true
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "boundary_host" "example" {
|
resource "boundary_host_catalog" "static" {
|
||||||
name = "example_host"
|
name = "My catalog"
|
||||||
description = "My first host!"
|
description = "My first host catalog!"
|
||||||
address = "10.0.0.1"
|
type = "static"
|
||||||
scope_id = boundary_scope.project.id
|
scope_id = boundary_scope.project.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "boundary_host" "example" {
|
||||||
|
name = "example_host"
|
||||||
|
description = "My first host!"
|
||||||
|
address = "10.0.0.1"
|
||||||
|
host_catalog_id = boundary_host_catalog.static.id
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
<!-- schema generated by tfplugindocs -->
|
<!-- schema generated by tfplugindocs -->
|
||||||
@@ -42,7 +49,7 @@ resource "boundary_host" "example" {
|
|||||||
### Required
|
### Required
|
||||||
|
|
||||||
- **host_catalog_id** (String)
|
- **host_catalog_id** (String)
|
||||||
- **type** (String)
|
- **type** (String) The type of host
|
||||||
|
|
||||||
### Optional
|
### Optional
|
||||||
|
|
||||||
@@ -50,7 +57,7 @@ resource "boundary_host" "example" {
|
|||||||
- **description** (String) The host description.
|
- **description** (String) The host description.
|
||||||
- **name** (String) The host name. Defaults to the resource name.
|
- **name** (String) The host name. Defaults to the resource name.
|
||||||
|
|
||||||
### Read-only
|
### Read-Only
|
||||||
|
|
||||||
- **id** (String) The ID of the host.
|
- **id** (String) The ID of the host.
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
page_title: "boundary_host_catalog Resource - terraform-provider-boundary"
|
page_title: "boundary_host_catalog Resource - terraform-provider-boundary"
|
||||||
subcategory: ""
|
subcategory: ""
|
||||||
description: |-
|
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.
|
Deprecated: use resource_host_catalog_static instead.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Resource `boundary_host_catalog`
|
# boundary_host_catalog (Resource)
|
||||||
|
|
||||||
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.
|
Deprecated: use `resource_host_catalog_static` instead.
|
||||||
|
|
||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
@@ -42,14 +42,14 @@ resource "boundary_host_catalog" "example" {
|
|||||||
### Required
|
### Required
|
||||||
|
|
||||||
- **scope_id** (String) The scope ID in which the resource is created.
|
- **scope_id** (String) The scope ID in which the resource is created.
|
||||||
- **type** (String) The host catalog type. Only `Static` (yes, title case) is supported.
|
- **type** (String) The host catalog type. Only `static` is supported.
|
||||||
|
|
||||||
### Optional
|
### Optional
|
||||||
|
|
||||||
- **description** (String) The host catalog description.
|
- **description** (String) The host catalog description.
|
||||||
- **name** (String) The host catalog name. Defaults to the resource name.
|
- **name** (String) The host catalog name. Defaults to the resource name.
|
||||||
|
|
||||||
### Read-only
|
### Read-Only
|
||||||
|
|
||||||
- **id** (String) The ID of the host catalog.
|
- **id** (String) The ID of the host catalog.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
@@ -3,12 +3,12 @@
|
|||||||
page_title: "boundary_host_set Resource - terraform-provider-boundary"
|
page_title: "boundary_host_set Resource - terraform-provider-boundary"
|
||||||
subcategory: ""
|
subcategory: ""
|
||||||
description: |-
|
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.
|
Deprecated: use resource_host_set_static instead.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Resource `boundary_host_set`
|
# boundary_host_set (Resource)
|
||||||
|
|
||||||
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.
|
Deprecated: use `resource_host_set_static` instead.
|
||||||
|
|
||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
@@ -50,6 +50,7 @@ resource "boundary_host" "2" {
|
|||||||
|
|
||||||
resource "boundary_host_set" "web" {
|
resource "boundary_host_set" "web" {
|
||||||
host_catalog_id = boundary_host_catalog.static.id
|
host_catalog_id = boundary_host_catalog.static.id
|
||||||
|
type = "static"
|
||||||
host_ids = [
|
host_ids = [
|
||||||
boundary_host.1.id,
|
boundary_host.1.id,
|
||||||
boundary_host.2.id,
|
boundary_host.2.id,
|
||||||
@@ -62,18 +63,18 @@ resource "boundary_host_set" "web" {
|
|||||||
|
|
||||||
### Required
|
### Required
|
||||||
|
|
||||||
- **host_catalog_id** (String) The catalog for the hostset.
|
- **host_catalog_id** (String) The catalog for the host set.
|
||||||
- **type** (String)
|
- **type** (String) The type of host set
|
||||||
|
|
||||||
### Optional
|
### Optional
|
||||||
|
|
||||||
- **description** (String) The hostset description.
|
- **description** (String) The host set description.
|
||||||
- **host_ids** (Set of String) The list of host IDs contained in this set.
|
- **host_ids** (Set of String) The list of host IDs contained in this set.
|
||||||
- **name** (String) The hostset name. Defaults to the resource name.
|
- **name** (String) The host set name. Defaults to the resource name.
|
||||||
|
|
||||||
### Read-only
|
### Read-Only
|
||||||
|
|
||||||
- **id** (String) The ID of the hostset.
|
- **id** (String) The ID of the host set.
|
||||||
|
|
||||||
## Import
|
## Import
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
|
||||||
+15
-15
@@ -51,10 +51,10 @@ resource "boundary_user" "bar" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "boundary_role" "example" {
|
resource "boundary_role" "example" {
|
||||||
name = "My role"
|
name = "My role"
|
||||||
description = "My first role!"
|
description = "My first role!"
|
||||||
principals = [boundary_user.foo.id, boundary_user.bar.id]
|
principal_ids = [boundary_user.foo.id, boundary_user.bar.id]
|
||||||
scope_id = boundary_scope.org.id
|
scope_id = boundary_scope.org.id
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -76,11 +76,11 @@ resource "boundary_user" "readonly" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "boundary_role" "readonly" {
|
resource "boundary_role" "readonly" {
|
||||||
name = "readonly"
|
name = "readonly"
|
||||||
description = "A readonly role"
|
description = "A readonly role"
|
||||||
principals = [boundary_user.readonly.id]
|
principal_ids = [boundary_user.readonly.id]
|
||||||
grants = ["id=*;action=read"]
|
grant_strings = ["id=*;type=*;actions=read"]
|
||||||
scope_id = boundary_scope.org.id
|
scope_id = boundary_scope.org.id
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -109,11 +109,11 @@ resource "boundary_user" "readonly" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "boundary_role" "readonly" {
|
resource "boundary_role" "readonly" {
|
||||||
name = "readonly"
|
name = "readonly"
|
||||||
description = "A readonly role"
|
description = "A readonly role"
|
||||||
principals = [boundary_user.readonly.id]
|
principal_ids = [boundary_user.readonly.id]
|
||||||
grants = ["id=*;action=read"]
|
grant_strings = ["id=*;type=*;actions=read"]
|
||||||
scope_id = boundary_scope.project.id
|
scope_id = boundary_scope.project.id
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ resource "boundary_role" "readonly" {
|
|||||||
- **name** (String) The role name. Defaults to the resource name.
|
- **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.
|
- **principal_ids** (Set of String) A list of principal (user or group) IDs to add as principals on the role.
|
||||||
|
|
||||||
### Read-only
|
### Read-Only
|
||||||
|
|
||||||
- **id** (String) The ID of the role.
|
- **id** (String) The ID of the role.
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ resource "boundary_role" "org_admin" {
|
|||||||
- **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.
|
- **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.
|
- **name** (String) The scope name. Defaults to the resource name.
|
||||||
|
|
||||||
### Read-only
|
### Read-Only
|
||||||
|
|
||||||
- **id** (String) The ID of the scope.
|
- **id** (String) The ID of the scope.
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ description: |-
|
|||||||
The target resource allows you to configure a Boundary target.
|
The target resource allows you to configure a Boundary target.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Resource `boundary_target`
|
# boundary_target (Resource)
|
||||||
|
|
||||||
The target resource allows you to configure a Boundary target.
|
The target resource allows you to configure a Boundary target.
|
||||||
|
|
||||||
@@ -33,6 +33,22 @@ resource "boundary_scope" "project" {
|
|||||||
auto_create_admin_role = true
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
resource "boundary_host_catalog" "foo" {
|
resource "boundary_host_catalog" "foo" {
|
||||||
name = "test"
|
name = "test"
|
||||||
description = "test catalog"
|
description = "test catalog"
|
||||||
@@ -67,11 +83,15 @@ resource "boundary_host_set" "foo" {
|
|||||||
resource "boundary_target" "foo" {
|
resource "boundary_target" "foo" {
|
||||||
name = "foo"
|
name = "foo"
|
||||||
description = "Foo target"
|
description = "Foo target"
|
||||||
|
type = "tcp"
|
||||||
default_port = "22"
|
default_port = "22"
|
||||||
scope_id = boundary_scope.project.id
|
scope_id = boundary_scope.project.id
|
||||||
host_set_ids = [
|
host_source_ids = [
|
||||||
boundary_host_set.foo.id
|
boundary_host_set.foo.id
|
||||||
]
|
]
|
||||||
|
application_credential_source_ids = [
|
||||||
|
boundary_credential_library_vault.foo.id
|
||||||
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -85,14 +105,16 @@ resource "boundary_target" "foo" {
|
|||||||
|
|
||||||
### Optional
|
### 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.
|
- **default_port** (Number) The default port for this target.
|
||||||
- **description** (String) The target description.
|
- **description** (String) The target description.
|
||||||
- **host_set_ids** (Set of String) A list of host set ID's.
|
- **host_source_ids** (Set of String) A list of host source ID's.
|
||||||
- **name** (String) The target name. Defaults to the resource name.
|
- **name** (String) The target name. Defaults to the resource name.
|
||||||
- **session_connection_limit** (Number)
|
- **session_connection_limit** (Number)
|
||||||
- **session_max_seconds** (Number)
|
- **session_max_seconds** (Number)
|
||||||
|
- **worker_filter** (String) Boolean expression to filter the workers for this target
|
||||||
|
|
||||||
### Read-only
|
### Read-Only
|
||||||
|
|
||||||
- **id** (String) The ID of the target.
|
- **id** (String) The ID of the target.
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ description: |-
|
|||||||
The user resource allows you to configure a Boundary user.
|
The user resource allows you to configure a Boundary user.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Resource `boundary_user`
|
# boundary_user (Resource)
|
||||||
|
|
||||||
The user resource allows you to configure a Boundary user.
|
The user resource allows you to configure a Boundary user.
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ resource "boundary_user" "jeff" {
|
|||||||
- **description** (String) The user description.
|
- **description** (String) The user description.
|
||||||
- **name** (String) The username. Defaults to the resource name.
|
- **name** (String) The username. Defaults to the resource name.
|
||||||
|
|
||||||
### Read-only
|
### Read-Only
|
||||||
|
|
||||||
- **id** (String) The ID of the user.
|
- **id** (String) The ID of the user.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
terraform import boundary_credential_library_vault.foo <my-id>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
terraform import boundary_credential_store_vault.foo <my-id>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -13,9 +13,16 @@ resource "boundary_scope" "project" {
|
|||||||
auto_create_admin_role = true
|
auto_create_admin_role = true
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "boundary_host" "example" {
|
resource "boundary_host_catalog" "static" {
|
||||||
name = "example_host"
|
name = "My catalog"
|
||||||
description = "My first host!"
|
description = "My first host catalog!"
|
||||||
address = "10.0.0.1"
|
type = "static"
|
||||||
scope_id = boundary_scope.project.id
|
scope_id = boundary_scope.project.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "boundary_host" "example" {
|
||||||
|
name = "example_host"
|
||||||
|
description = "My first host!"
|
||||||
|
address = "10.0.0.1"
|
||||||
|
host_catalog_id = boundary_host_catalog.static.id
|
||||||
|
}
|
||||||
@@ -35,6 +35,7 @@ resource "boundary_host" "2" {
|
|||||||
|
|
||||||
resource "boundary_host_set" "web" {
|
resource "boundary_host_set" "web" {
|
||||||
host_catalog_id = boundary_host_catalog.static.id
|
host_catalog_id = boundary_host_catalog.static.id
|
||||||
|
type = "static"
|
||||||
host_ids = [
|
host_ids = [
|
||||||
boundary_host.1.id,
|
boundary_host.1.id,
|
||||||
boundary_host.2.id,
|
boundary_host.2.id,
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ resource "boundary_user" "readonly" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "boundary_role" "readonly" {
|
resource "boundary_role" "readonly" {
|
||||||
name = "readonly"
|
name = "readonly"
|
||||||
description = "A readonly role"
|
description = "A readonly role"
|
||||||
principals = [boundary_user.readonly.id]
|
principal_ids = [boundary_user.readonly.id]
|
||||||
grants = ["id=*;action=read"]
|
grant_strings = ["id=*;type=*;actions=read"]
|
||||||
scope_id = boundary_scope.project.id
|
scope_id = boundary_scope.project.id
|
||||||
}
|
}
|
||||||
@@ -13,9 +13,9 @@ resource "boundary_user" "readonly" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "boundary_role" "readonly" {
|
resource "boundary_role" "readonly" {
|
||||||
name = "readonly"
|
name = "readonly"
|
||||||
description = "A readonly role"
|
description = "A readonly role"
|
||||||
principals = [boundary_user.readonly.id]
|
principal_ids = [boundary_user.readonly.id]
|
||||||
grants = ["id=*;action=read"]
|
grant_strings = ["id=*;type=*;actions=read"]
|
||||||
scope_id = boundary_scope.org.id
|
scope_id = boundary_scope.org.id
|
||||||
}
|
}
|
||||||
@@ -17,8 +17,8 @@ resource "boundary_user" "bar" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "boundary_role" "example" {
|
resource "boundary_role" "example" {
|
||||||
name = "My role"
|
name = "My role"
|
||||||
description = "My first role!"
|
description = "My first role!"
|
||||||
principals = [boundary_user.foo.id, boundary_user.bar.id]
|
principal_ids = [boundary_user.foo.id, boundary_user.bar.id]
|
||||||
scope_id = boundary_scope.org.id
|
scope_id = boundary_scope.org.id
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,22 @@ resource "boundary_scope" "project" {
|
|||||||
auto_create_admin_role = true
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
resource "boundary_host_catalog" "foo" {
|
resource "boundary_host_catalog" "foo" {
|
||||||
name = "test"
|
name = "test"
|
||||||
description = "test catalog"
|
description = "test catalog"
|
||||||
@@ -52,9 +68,13 @@ resource "boundary_host_set" "foo" {
|
|||||||
resource "boundary_target" "foo" {
|
resource "boundary_target" "foo" {
|
||||||
name = "foo"
|
name = "foo"
|
||||||
description = "Foo target"
|
description = "Foo target"
|
||||||
|
type = "tcp"
|
||||||
default_port = "22"
|
default_port = "22"
|
||||||
scope_id = boundary_scope.project.id
|
scope_id = boundary_scope.project.id
|
||||||
host_set_ids = [
|
host_source_ids = [
|
||||||
boundary_host_set.foo.id
|
boundary_host_set.foo.id
|
||||||
]
|
]
|
||||||
|
application_credential_source_ids = [
|
||||||
|
boundary_credential_library_vault.foo.id
|
||||||
|
]
|
||||||
}
|
}
|
||||||
@@ -1,39 +1,206 @@
|
|||||||
module github.com/hashicorp/terraform-provider-boundary
|
module github.com/hashicorp/terraform-provider-boundary
|
||||||
|
|
||||||
go 1.15
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.68.0 // indirect
|
github.com/hashicorp/boundary v0.7.4
|
||||||
cloud.google.com/go/storage v1.11.0 // indirect
|
github.com/hashicorp/boundary/api v0.0.21
|
||||||
github.com/Azure/azure-sdk-for-go v46.4.0+incompatible // indirect
|
github.com/hashicorp/boundary/sdk v0.0.13
|
||||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.3 // indirect
|
github.com/hashicorp/cap v0.1.1
|
||||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
github.com/hashicorp/go-cty v1.4.1-0.20200723130312-85980079f637
|
||||||
github.com/Azure/go-autorest/autorest/validation v0.3.0 // indirect
|
github.com/hashicorp/go-kms-wrapping v0.6.8
|
||||||
github.com/DataDog/datadog-go v4.0.0+incompatible // indirect
|
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.2
|
||||||
github.com/agext/levenshtein v1.2.3 // indirect
|
github.com/hashicorp/terraform-plugin-docs v0.5.1
|
||||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.545 // indirect
|
github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.1
|
||||||
github.com/aws/aws-sdk-go v1.35.4 // indirect
|
github.com/kr/pretty v0.3.0
|
||||||
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.4
|
|
||||||
github.com/hashicorp/boundary/api v0.0.3
|
|
||||||
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.3.1
|
|
||||||
github.com/hashicorp/terraform-plugin-sdk/v2 v2.0.5-0.20201016174159-3a1c6c51f716
|
|
||||||
github.com/hashicorp/yamux v0.0.0-20200609203250-aecfd211c9ce // indirect
|
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/oklog/run v1.1.0 // indirect
|
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
||||||
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
|
require (
|
||||||
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c // indirect
|
cloud.google.com/go v0.81.0 // indirect
|
||||||
github.com/zclconf/go-cty v1.6.1 // indirect
|
cloud.google.com/go/storage v1.10.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.62.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
|
||||||
|
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/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
|
||||||
|
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,18 +1,44 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Key used for common SDK ID resource attribute
|
// IDKey is used for common SDK ID resource attribute
|
||||||
IDKey = "id"
|
IDKey = "id"
|
||||||
// Key used for common "name" resource attribute
|
// NameKey is used for common "name" resource attribute
|
||||||
NameKey = "name"
|
NameKey = "name"
|
||||||
// Key used for common "description" resource attribute
|
// DescriptionKey is used for common "description" resource attribute
|
||||||
DescriptionKey = "description"
|
DescriptionKey = "description"
|
||||||
// Key used for common "scope_id" resource attribute
|
// ScopeIdKey is used for common "scope_id" resource attribute
|
||||||
ScopeIdKey = "scope_id"
|
ScopeIdKey = "scope_id"
|
||||||
// Key used for common "type" resource attribute
|
// TypeKey is used for common "type" resource attribute
|
||||||
TypeKey = "type"
|
TypeKey = "type"
|
||||||
// Key used for common "host_catalog_id" resource attribute
|
// HostCatalogIdKey is used for common "host_catalog_id" resource attribute
|
||||||
HostCatalogIdKey = "host_catalog_id"
|
HostCatalogIdKey = "host_catalog_id"
|
||||||
// Key used for common "auth_method_id" resource attribute
|
// AuthMethodIdKey is used for common "auth_method_id" resource attribute
|
||||||
AuthMethodIdKey = "auth_method_id"
|
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"
|
||||||
)
|
)
|
||||||
@@ -52,18 +52,35 @@ func New() *schema.Provider {
|
|||||||
Optional: true,
|
Optional: true,
|
||||||
Description: "The auth method password for password-style auth methods",
|
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{
|
ResourcesMap: map[string]*schema.Resource{
|
||||||
"boundary_account": resourceAccount(),
|
"boundary_account": resourceAccount(),
|
||||||
"boundary_auth_method": resourceAuthMethod(),
|
"boundary_account_password": resourceAccountPassword(),
|
||||||
"boundary_group": resourceGroup(),
|
"boundary_account_oidc": resourceAccountOidc(),
|
||||||
"boundary_host": resourceHost(),
|
"boundary_auth_method": resourceAuthMethod(),
|
||||||
"boundary_host_catalog": resourceHostCatalog(),
|
"boundary_auth_method_password": resourceAuthMethodPassword(),
|
||||||
"boundary_host_set": resourceHostset(),
|
"boundary_auth_method_oidc": resourceAuthMethodOidc(),
|
||||||
"boundary_role": resourceRole(),
|
"boundary_credential_library_vault": resourceCredentialLibraryVault(),
|
||||||
"boundary_scope": resourceScope(),
|
"boundary_credential_store_vault": resourceCredentialStoreVault(),
|
||||||
"boundary_target": resourceTarget(),
|
"boundary_managed_group": resourceManagedGroup(),
|
||||||
"boundary_user": resourceUser(),
|
"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(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,11 +150,11 @@ func providerAuthenticate(ctx context.Context, d *schema.ResourceData, md *metaD
|
|||||||
|
|
||||||
am := authmethods.NewClient(md.client)
|
am := authmethods.NewClient(md.client)
|
||||||
|
|
||||||
at, err := am.Authenticate(ctx, authMethodId.(string), credentials)
|
at, err := am.Authenticate(ctx, authMethodId.(string), "login", credentials)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
md.client.SetToken(at.Item.Token)
|
md.client.SetToken(at.Attributes["token"].(string))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return errors.New("no suitable auth method information found")
|
return errors.New("no suitable auth method information found")
|
||||||
@@ -154,12 +171,20 @@ func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if url, ok := d.GetOk("addr"); ok {
|
if url, ok := d.GetOk("addr"); ok {
|
||||||
client.SetAddr(url.(string))
|
if err := client.SetAddr(url.(string)); err != nil {
|
||||||
|
return nil, diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if client.Addr() == "" {
|
if client.Addr() == "" {
|
||||||
return nil, diag.Errorf(`"no valid address could be determined from "addr" or "BOUNDARY_ADDR" env var`)
|
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)
|
client.SetLimiter(5, 5)
|
||||||
|
|
||||||
md := &metaData{
|
md := &metaData{
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
tcLoginName = "user"
|
tcLoginName = "testuser"
|
||||||
tcPassword = "passpass"
|
tcPassword = "passpass"
|
||||||
tcPAUM = "ampw_0000000000"
|
tcPAUM = "ampw_0000000000"
|
||||||
tcConfig = []controller.Option{
|
tcConfig = []controller.Option{
|
||||||
controller.WithDefaultAuthMethodId(tcPAUM),
|
controller.WithDefaultPasswordAuthMethodId(tcPAUM),
|
||||||
controller.WithDefaultLoginName(tcLoginName),
|
controller.WithDefaultLoginName(tcLoginName),
|
||||||
controller.WithDefaultPassword(tcPassword),
|
controller.WithDefaultPassword(tcPassword),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,6 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
accountTypePassword = "password"
|
|
||||||
accountLoginNameKey = "login_name"
|
|
||||||
accountPasswordKey = "password"
|
|
||||||
)
|
|
||||||
|
|
||||||
func resourceAccount() *schema.Resource {
|
func resourceAccount() *schema.Resource {
|
||||||
return &schema.Resource{
|
return &schema.Resource{
|
||||||
Description: "The account resource allows you to configure a Boundary account.",
|
Description: "The account resource allows you to configure a Boundary account.",
|
||||||
@@ -60,11 +54,14 @@ func resourceAccount() *schema.Resource {
|
|||||||
Description: "The login name for this account.",
|
Description: "The login name for this account.",
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
Deprecated: "Will be removed in favor of using attributes parameter",
|
||||||
},
|
},
|
||||||
accountPasswordKey: {
|
accountPasswordKey: {
|
||||||
Description: "The account password. Only set on create, changes will not be reflected when updating account.",
|
Description: "The account password. Only set on create, changes will not be reflected when updating account.",
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
Deprecated: "Will be removed in favor of using attributes parameter",
|
||||||
|
|
||||||
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
|
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
|
||||||
if d.Id() == "" {
|
if d.Id() == "" {
|
||||||
// This is a new resource do not suppress password diff
|
// This is a new resource do not suppress password diff
|
||||||
@@ -77,21 +74,33 @@ func resourceAccount() *schema.Resource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setFromAccountResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
|
func setFromAccountResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
|
||||||
d.Set(NameKey, raw["name"])
|
if err := d.Set(NameKey, raw["name"]); err != nil {
|
||||||
d.Set(DescriptionKey, raw["description"])
|
return err
|
||||||
d.Set(AuthMethodIdKey, raw["auth_method_id"])
|
}
|
||||||
d.Set(TypeKey, raw["type"])
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(malnick) - remove after deprecation cycle in favor of attributes
|
||||||
switch raw["type"].(string) {
|
switch raw["type"].(string) {
|
||||||
case accountTypePassword:
|
case accountTypePassword:
|
||||||
if attrsVal, ok := raw["attributes"]; ok {
|
if attrsVal, ok := raw["attributes"]; ok {
|
||||||
attrs := attrsVal.(map[string]interface{})
|
attrs := attrsVal.(map[string]interface{})
|
||||||
d.Set(accountLoginNameKey, attrs["login_name"])
|
if err := d.Set(accountLoginNameKey, attrs["login_name"]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d.SetId(raw["id"].(string))
|
d.SetId(raw["id"].(string))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||||
@@ -104,49 +113,24 @@ func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, meta int
|
|||||||
return diag.Errorf("no auth method ID provided")
|
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{}
|
opts := []accounts.Option{}
|
||||||
|
|
||||||
var typeStr string
|
if nameVal, ok := d.GetOk(NameKey); ok {
|
||||||
if typeVal, ok := d.GetOk(TypeKey); ok {
|
opts = append(opts, accounts.WithName(nameVal.(string)))
|
||||||
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 descVal, ok := d.GetOk(DescriptionKey); ok {
|
||||||
if ok {
|
opts = append(opts, accounts.WithDescription(descVal.(string)))
|
||||||
nameStr := nameVal.(string)
|
|
||||||
opts = append(opts, accounts.WithName(nameStr))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
descVal, ok := d.GetOk(DescriptionKey)
|
// TODO(malnick) - remove after deprecation cycle
|
||||||
if ok {
|
if name, ok := d.GetOk(accountLoginNameKey); ok {
|
||||||
descStr := descVal.(string)
|
opts = append(opts, accounts.WithPasswordAccountLoginName(name.(string)))
|
||||||
opts = append(opts, accounts.WithDescription(descStr))
|
}
|
||||||
|
|
||||||
|
// TODO(malnick) - remove after deprecation cycle
|
||||||
|
if pass, ok := d.GetOk(accountPasswordKey); ok {
|
||||||
|
opts = append(opts, accounts.WithPasswordAccountPassword(pass.(string)))
|
||||||
}
|
}
|
||||||
|
|
||||||
aClient := accounts.NewClient(md.client)
|
aClient := accounts.NewClient(md.client)
|
||||||
@@ -159,7 +143,9 @@ func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, meta int
|
|||||||
return diag.Errorf("nil account after create")
|
return diag.Errorf("nil account after create")
|
||||||
}
|
}
|
||||||
|
|
||||||
setFromAccountResponseMap(d, acr.GetResponseMap())
|
if err := setFromAccountResponseMap(d, acr.GetResponse().Map); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -170,7 +156,7 @@ func resourceAccountRead(ctx context.Context, d *schema.ResourceData, meta inter
|
|||||||
|
|
||||||
arr, err := aClient.Read(ctx, d.Id())
|
arr, err := aClient.Read(ctx, d.Id())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound {
|
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||||
d.SetId("")
|
d.SetId("")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -180,7 +166,9 @@ func resourceAccountRead(ctx context.Context, d *schema.ResourceData, meta inter
|
|||||||
return diag.Errorf("account nil after read")
|
return diag.Errorf("account nil after read")
|
||||||
}
|
}
|
||||||
|
|
||||||
setFromAccountResponseMap(d, arr.GetResponseMap())
|
if err := setFromAccountResponseMap(d, arr.GetResponse().Map); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -191,60 +179,38 @@ func resourceAccountUpdate(ctx context.Context, d *schema.ResourceData, meta int
|
|||||||
|
|
||||||
opts := []accounts.Option{}
|
opts := []accounts.Option{}
|
||||||
|
|
||||||
var name *string
|
|
||||||
if d.HasChange(NameKey) {
|
if d.HasChange(NameKey) {
|
||||||
opts = append(opts, accounts.DefaultName())
|
opts = append(opts, accounts.DefaultName())
|
||||||
nameVal, ok := d.GetOk(NameKey)
|
nameVal, ok := d.GetOk(NameKey)
|
||||||
if ok {
|
if ok {
|
||||||
nameStr := nameVal.(string)
|
opts = append(opts, accounts.WithName(nameVal.(string)))
|
||||||
name = &nameStr
|
|
||||||
opts = append(opts, accounts.WithName(nameStr))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var desc *string
|
|
||||||
if d.HasChange(DescriptionKey) {
|
if d.HasChange(DescriptionKey) {
|
||||||
opts = append(opts, accounts.DefaultDescription())
|
opts = append(opts, accounts.DefaultDescription())
|
||||||
descVal, ok := d.GetOk(DescriptionKey)
|
descVal, ok := d.GetOk(DescriptionKey)
|
||||||
if ok {
|
if ok {
|
||||||
descStr := descVal.(string)
|
opts = append(opts, accounts.WithDescription(descVal.(string)))
|
||||||
desc = &descStr
|
|
||||||
opts = append(opts, accounts.WithDescription(descStr))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var loginName *string
|
// TODO(malnick) - remove after deprecation cycle
|
||||||
if d.HasChange(accountLoginNameKey) {
|
if d.HasChange(accountLoginNameKey) {
|
||||||
switch d.Get(TypeKey).(string) {
|
opts = append(opts, accounts.DefaultPasswordAccountLoginName())
|
||||||
case accountTypePassword:
|
if keyVal, ok := d.GetOk(accountLoginNameKey); ok {
|
||||||
opts = append(opts, accounts.DefaultPasswordAccountLoginName())
|
opts = append(opts, accounts.WithPasswordAccountLoginName(keyVal.(string)))
|
||||||
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 {
|
if len(opts) > 0 {
|
||||||
opts = append(opts, accounts.WithAutomaticVersioning(true))
|
opts = append(opts, accounts.WithAutomaticVersioning(true))
|
||||||
_, err := aClient.Update(ctx, d.Id(), 0, opts...)
|
aur, err := aClient.Update(ctx, d.Id(), 0, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return diag.Errorf("error updating account: %v", err)
|
return diag.Errorf("error updating account: %v", err)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if d.HasChange(NameKey) {
|
setFromAccountResponseMap(d, aur.GetResponse().Map)
|
||||||
d.Set(NameKey, name)
|
|
||||||
}
|
|
||||||
if d.HasChange(DescriptionKey) {
|
|
||||||
d.Set(DescriptionKey, desc)
|
|
||||||
}
|
|
||||||
if d.HasChange(accountLoginNameKey) {
|
|
||||||
d.Set(accountLoginNameKey, loginName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,266 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
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)
|
}`, fooAccountDescUpdate)
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAccAccount(t *testing.T) {
|
func TestAccAccountBase(t *testing.T) {
|
||||||
tc := controller.NewTestController(t, tcConfig...)
|
tc := controller.NewTestController(t, tcConfig...)
|
||||||
defer tc.Shutdown()
|
defer tc.Shutdown()
|
||||||
url := tc.ApiAddrs()[0]
|
url := tc.ApiAddrs()[0]
|
||||||
@@ -69,7 +69,7 @@ func TestAccAccount(t *testing.T) {
|
|||||||
CheckDestroy: testAccCheckAccountResourceDestroy(t, provider),
|
CheckDestroy: testAccCheckAccountResourceDestroy(t, provider),
|
||||||
Steps: []resource.TestStep{
|
Steps: []resource.TestStep{
|
||||||
{
|
{
|
||||||
//create
|
// create
|
||||||
Config: testConfig(url, fooOrg, fooAccount),
|
Config: testConfig(url, fooOrg, fooAccount),
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
resource.TestCheckResourceAttr("boundary_account.foo", "description", fooAccountDesc),
|
resource.TestCheckResourceAttr("boundary_account.foo", "description", fooAccountDesc),
|
||||||
@@ -137,7 +137,7 @@ func testAccCheckAccountResourceDestroy(t *testing.T, testProvider *schema.Provi
|
|||||||
amClient := accounts.NewClient(md.client)
|
amClient := accounts.NewClient(md.client)
|
||||||
|
|
||||||
_, err := amClient.Read(context.Background(), id)
|
_, err := amClient.Read(context.Background(), id)
|
||||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.ResponseStatus() != http.StatusNotFound {
|
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)
|
return fmt.Errorf("didn't get a 404 when reading destroyed account %q: %v", id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,12 +11,6 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
"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 {
|
func resourceAuthMethod() *schema.Resource {
|
||||||
return &schema.Resource{
|
return &schema.Resource{
|
||||||
Description: "The auth method resource allows you to configure a Boundary auth_method.",
|
Description: "The auth method resource allows you to configure a Boundary auth_method.",
|
||||||
@@ -62,22 +56,32 @@ func resourceAuthMethod() *schema.Resource {
|
|||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
|
Deprecated: "Will be removed in favor of using attributes parameter",
|
||||||
},
|
},
|
||||||
authmethodMinPasswordLengthKey: {
|
authmethodMinPasswordLengthKey: {
|
||||||
Description: "The minimum password length.",
|
Description: "The minimum password length.",
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
|
Deprecated: "Will be removed in favor of using attributes parameter",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setFromAuthMethodResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
|
func setFromAuthMethodResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
|
||||||
d.Set(NameKey, raw["name"])
|
if err := d.Set(NameKey, raw["name"]); err != nil {
|
||||||
d.Set(DescriptionKey, raw["description"])
|
return err
|
||||||
d.Set(ScopeIdKey, raw["scope_id"])
|
}
|
||||||
d.Set(TypeKey, raw["type"])
|
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
|
||||||
|
}
|
||||||
|
|
||||||
switch raw["type"].(string) {
|
switch raw["type"].(string) {
|
||||||
case authmethodTypePassword:
|
case authmethodTypePassword:
|
||||||
@@ -86,15 +90,19 @@ func setFromAuthMethodResponseMap(d *schema.ResourceData, raw map[string]interfa
|
|||||||
|
|
||||||
minLoginNameLength := attrs["min_login_name_length"].(json.Number)
|
minLoginNameLength := attrs["min_login_name_length"].(json.Number)
|
||||||
minLoginNameLengthInt, _ := minLoginNameLength.Int64()
|
minLoginNameLengthInt, _ := minLoginNameLength.Int64()
|
||||||
d.Set(authmethodMinLoginNameLengthKey, int(minLoginNameLengthInt))
|
if err := d.Set(authmethodMinLoginNameLengthKey, int(minLoginNameLengthInt)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
minPasswordLength := attrs["min_password_length"].(json.Number)
|
minPasswordLength := attrs["min_password_length"].(json.Number)
|
||||||
minPasswordLengthInt, _ := minPasswordLength.Int64()
|
minPasswordLengthInt, _ := minPasswordLength.Int64()
|
||||||
d.Set(authmethodMinPasswordLengthKey, int(minPasswordLengthInt))
|
if err := d.Set(authmethodMinPasswordLengthKey, int(minPasswordLengthInt)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d.SetId(raw["id"].(string))
|
d.SetId(raw["id"].(string))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAuthMethodCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
func resourceAuthMethodCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||||
@@ -107,48 +115,31 @@ func resourceAuthMethodCreate(ctx context.Context, d *schema.ResourceData, meta
|
|||||||
return diag.Errorf("no scope ID provided")
|
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
|
var typeStr string
|
||||||
if typeVal, ok := d.GetOk(TypeKey); ok {
|
if typeVal, ok := d.GetOk(TypeKey); ok {
|
||||||
typeStr = typeVal.(string)
|
typeStr = typeVal.(string)
|
||||||
} else {
|
} else {
|
||||||
return diag.Errorf("no type provided")
|
return diag.Errorf("no type provided")
|
||||||
}
|
}
|
||||||
switch typeStr {
|
|
||||||
case authmethodTypePassword:
|
opts := []authmethods.Option{}
|
||||||
if minLoginNameLength != nil {
|
|
||||||
opts = append(opts, authmethods.WithPasswordAuthMethodMinLoginNameLength(uint32(*minLoginNameLength)))
|
if nameVal, ok := d.GetOk(NameKey); ok {
|
||||||
}
|
opts = append(opts, authmethods.WithName(nameVal.(string)))
|
||||||
if minPasswordLength != nil {
|
|
||||||
opts = append(opts, authmethods.WithPasswordAuthMethodMinPasswordLength(uint32(*minPasswordLength)))
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return diag.Errorf("invalid type provided")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nameVal, ok := d.GetOk(NameKey)
|
if descVal, ok := d.GetOk(DescriptionKey); ok {
|
||||||
if ok {
|
opts = append(opts, authmethods.WithDescription(descVal.(string)))
|
||||||
nameStr := nameVal.(string)
|
|
||||||
opts = append(opts, authmethods.WithName(nameStr))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
descVal, ok := d.GetOk(DescriptionKey)
|
// TODO(malnick) - deprecate
|
||||||
if ok {
|
if minLengthVal, ok := d.GetOk(authmethodMinLoginNameLengthKey); ok {
|
||||||
descStr := descVal.(string)
|
opts = append(opts, authmethods.WithPasswordAuthMethodMinLoginNameLength(uint32(minLengthVal.(int))))
|
||||||
opts = append(opts, authmethods.WithDescription(descStr))
|
}
|
||||||
|
|
||||||
|
// TODO(malnick) - deprecate
|
||||||
|
if minLengthVal, ok := d.GetOk(authmethodMinPasswordLengthKey); ok {
|
||||||
|
opts = append(opts, authmethods.WithPasswordAuthMethodMinPasswordLength(uint32(minLengthVal.(int))))
|
||||||
}
|
}
|
||||||
|
|
||||||
amClient := authmethods.NewClient(md.client)
|
amClient := authmethods.NewClient(md.client)
|
||||||
@@ -161,7 +152,9 @@ func resourceAuthMethodCreate(ctx context.Context, d *schema.ResourceData, meta
|
|||||||
return diag.Errorf("nil auth method after create")
|
return diag.Errorf("nil auth method after create")
|
||||||
}
|
}
|
||||||
|
|
||||||
setFromAuthMethodResponseMap(d, amcr.GetResponseMap())
|
if err := setFromAuthMethodResponseMap(d, amcr.GetResponse().Map); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -172,7 +165,7 @@ func resourceAuthMethodRead(ctx context.Context, d *schema.ResourceData, meta in
|
|||||||
|
|
||||||
amrr, err := amClient.Read(ctx, d.Id())
|
amrr, err := amClient.Read(ctx, d.Id())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if apiErr := api.AsServerError(err); apiErr.ResponseStatus() == http.StatusNotFound {
|
if apiErr := api.AsServerError(err); apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||||
d.SetId("")
|
d.SetId("")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -182,7 +175,9 @@ func resourceAuthMethodRead(ctx context.Context, d *schema.ResourceData, meta in
|
|||||||
return diag.Errorf("auth method nil after read")
|
return diag.Errorf("auth method nil after read")
|
||||||
}
|
}
|
||||||
|
|
||||||
setFromAuthMethodResponseMap(d, amrr.GetResponseMap())
|
if err := setFromAuthMethodResponseMap(d, amrr.GetResponse().Map); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -193,81 +188,43 @@ func resourceAuthMethodUpdate(ctx context.Context, d *schema.ResourceData, meta
|
|||||||
|
|
||||||
opts := []authmethods.Option{}
|
opts := []authmethods.Option{}
|
||||||
|
|
||||||
var name *string
|
|
||||||
if d.HasChange(NameKey) {
|
if d.HasChange(NameKey) {
|
||||||
opts = append(opts, authmethods.DefaultName())
|
opts = append(opts, authmethods.DefaultName())
|
||||||
nameVal, ok := d.GetOk(NameKey)
|
if nameVal, ok := d.GetOk(NameKey); ok {
|
||||||
if ok {
|
opts = append(opts, authmethods.WithName(nameVal.(string)))
|
||||||
nameStr := nameVal.(string)
|
|
||||||
name = &nameStr
|
|
||||||
opts = append(opts, authmethods.WithName(nameStr))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var desc *string
|
|
||||||
if d.HasChange(DescriptionKey) {
|
if d.HasChange(DescriptionKey) {
|
||||||
opts = append(opts, authmethods.DefaultDescription())
|
opts = append(opts, authmethods.DefaultDescription())
|
||||||
descVal, ok := d.GetOk(DescriptionKey)
|
if descVal, ok := d.GetOk(DescriptionKey); ok {
|
||||||
if ok {
|
opts = append(opts, authmethods.WithDescription(descVal.(string)))
|
||||||
descStr := descVal.(string)
|
|
||||||
desc = &descStr
|
|
||||||
opts = append(opts, authmethods.WithDescription(descStr))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var minLoginNameLength *int
|
// TODO(malnick) - deprecate
|
||||||
if d.HasChange(authmethodMinLoginNameLengthKey) {
|
|
||||||
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`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var minPasswordLength *int
|
|
||||||
if d.HasChange(authmethodMinPasswordLengthKey) {
|
if d.HasChange(authmethodMinPasswordLengthKey) {
|
||||||
switch d.Get(TypeKey).(string) {
|
opts = append(opts, authmethods.DefaultPasswordAuthMethodMinPasswordLength())
|
||||||
case authmethodTypePassword:
|
if minLengthVal, ok := d.GetOk(authmethodMinPasswordLengthKey); ok {
|
||||||
opts = append(opts, authmethods.DefaultPasswordAuthMethodMinPasswordLength())
|
opts = append(opts, authmethods.WithPasswordAuthMethodMinPasswordLength(uint32(minLengthVal.(int))))
|
||||||
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`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO(malnick) - deprecate
|
||||||
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) {
|
if d.HasChange(authmethodMinLoginNameLengthKey) {
|
||||||
d.Set(authmethodMinLoginNameLengthKey, minLoginNameLength)
|
opts = append(opts, authmethods.DefaultPasswordAuthMethodMinLoginNameLength())
|
||||||
|
if minLengthVal, ok := d.GetOk(authmethodMinLoginNameLengthKey); ok {
|
||||||
|
opts = append(opts, authmethods.WithPasswordAuthMethodMinLoginNameLength(uint32(minLengthVal.(int))))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if d.HasChange(authmethodMinPasswordLengthKey) {
|
|
||||||
d.Set(authmethodMinPasswordLengthKey, minPasswordLength)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setFromAuthMethodResponseMap(d, amu.GetResponse().Map)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,564 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,286 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
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,31 +15,33 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
fooAuthMethodDesc = "test auth method"
|
fooBaseAuthMethodDesc = "test auth method"
|
||||||
fooAuthMethodDescUpdate = "test auth method update"
|
fooBaseAuthMethodDescUpdate = "test auth method update"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO(malnick) - remove these tests after deprecating use of password params in
|
||||||
|
// favor of attributes map
|
||||||
var (
|
var (
|
||||||
fooAuthMethod = fmt.Sprintf(`
|
fooBaseAuthMethod = fmt.Sprintf(`
|
||||||
resource "boundary_auth_method" "foo" {
|
resource "boundary_auth_method" "foo" {
|
||||||
name = "test"
|
name = "test"
|
||||||
description = "%s"
|
description = "%s"
|
||||||
type = "password"
|
type = "password"
|
||||||
scope_id = boundary_scope.org1.id
|
scope_id = boundary_scope.org1.id
|
||||||
depends_on = [boundary_role.org1_admin]
|
depends_on = [boundary_role.org1_admin]
|
||||||
}`, fooAuthMethodDesc)
|
}`, fooBaseAuthMethodDesc)
|
||||||
|
|
||||||
fooAuthMethodUpdate = fmt.Sprintf(`
|
fooBaseAuthMethodUpdate = fmt.Sprintf(`
|
||||||
resource "boundary_auth_method" "foo" {
|
resource "boundary_auth_method" "foo" {
|
||||||
name = "test"
|
name = "test"
|
||||||
description = "%s"
|
description = "%s"
|
||||||
type = "password"
|
type = "password"
|
||||||
scope_id = boundary_scope.org1.id
|
scope_id = boundary_scope.org1.id
|
||||||
depends_on = [boundary_role.org1_admin]
|
depends_on = [boundary_role.org1_admin]
|
||||||
}`, fooAuthMethodDescUpdate)
|
}`, fooBaseAuthMethodDescUpdate)
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAccAuthMethod(t *testing.T) {
|
func TestAccBaseAuthMethodPassword(t *testing.T) {
|
||||||
tc := controller.NewTestController(t, tcConfig...)
|
tc := controller.NewTestController(t, tcConfig...)
|
||||||
defer tc.Shutdown()
|
defer tc.Shutdown()
|
||||||
url := tc.ApiAddrs()[0]
|
url := tc.ApiAddrs()[0]
|
||||||
@@ -50,24 +52,25 @@ func TestAccAuthMethod(t *testing.T) {
|
|||||||
CheckDestroy: testAccCheckAuthMethodResourceDestroy(t, provider),
|
CheckDestroy: testAccCheckAuthMethodResourceDestroy(t, provider),
|
||||||
Steps: []resource.TestStep{
|
Steps: []resource.TestStep{
|
||||||
{
|
{
|
||||||
//create
|
// create
|
||||||
Config: testConfig(url, fooOrg, fooAuthMethod),
|
Config: testConfig(url, fooOrg, fooBaseAuthMethod),
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
resource.TestCheckResourceAttr("boundary_auth_method.foo", "description", fooAuthMethodDesc),
|
resource.TestCheckResourceAttr("boundary_auth_method.foo", "description", fooBaseAuthMethodDesc),
|
||||||
resource.TestCheckResourceAttr("boundary_auth_method.foo", "name", "test"),
|
resource.TestCheckResourceAttr("boundary_auth_method.foo", "name", "test"),
|
||||||
resource.TestCheckResourceAttr("boundary_auth_method.foo", "type", "password"),
|
resource.TestCheckResourceAttr("boundary_auth_method.foo", "type", "password"),
|
||||||
testAccCheckAuthMethodResourceExists(provider, "boundary_auth_method.foo"),
|
testAccCheckBaseAuthMethodResourceExists(provider, "boundary_auth_method.foo"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
importStep("boundary_auth_method.foo"),
|
importStep("boundary_auth_method.foo"),
|
||||||
|
|
||||||
{
|
{
|
||||||
// update
|
// update
|
||||||
Config: testConfig(url, fooOrg, fooAuthMethodUpdate),
|
Config: testConfig(url, fooOrg, fooBaseAuthMethodUpdate),
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
resource.TestCheckResourceAttr("boundary_auth_method.foo", "description", fooAuthMethodDescUpdate),
|
resource.TestCheckResourceAttr("boundary_auth_method.foo", "description", fooBaseAuthMethodDescUpdate),
|
||||||
resource.TestCheckResourceAttr("boundary_auth_method.foo", "name", "test"),
|
resource.TestCheckResourceAttr("boundary_auth_method.foo", "name", "test"),
|
||||||
resource.TestCheckResourceAttr("boundary_auth_method.foo", "type", "password"),
|
resource.TestCheckResourceAttr("boundary_auth_method.foo", "type", "password"),
|
||||||
testAccCheckAuthMethodResourceExists(provider, "boundary_auth_method.foo"),
|
testAccCheckBaseAuthMethodResourceExists(provider, "boundary_auth_method.foo"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
importStep("boundary_auth_method.foo"),
|
importStep("boundary_auth_method.foo"),
|
||||||
@@ -75,7 +78,7 @@ func TestAccAuthMethod(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckAuthMethodResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
|
func testAccCheckBaseAuthMethodResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
|
||||||
return func(s *terraform.State) error {
|
return func(s *terraform.State) error {
|
||||||
rs, ok := s.RootModule().Resources[name]
|
rs, ok := s.RootModule().Resources[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -99,7 +102,7 @@ func testAccCheckAuthMethodResourceExists(testProvider *schema.Provider, name st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckAuthMethodResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
|
func testAccCheckBaseAuthMethodResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
|
||||||
return func(s *terraform.State) error {
|
return func(s *terraform.State) error {
|
||||||
if testProvider.Meta() == nil {
|
if testProvider.Meta() == nil {
|
||||||
t.Fatal("got nil provider metadata")
|
t.Fatal("got nil provider metadata")
|
||||||
@@ -114,7 +117,7 @@ func testAccCheckAuthMethodResourceDestroy(t *testing.T, testProvider *schema.Pr
|
|||||||
amClient := authmethods.NewClient(md.client)
|
amClient := authmethods.NewClient(md.client)
|
||||||
|
|
||||||
_, err := amClient.Read(context.Background(), id)
|
_, err := amClient.Read(context.Background(), id)
|
||||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.ResponseStatus() != http.StatusNotFound {
|
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)
|
return fmt.Errorf("didn't get a 404 when reading destroyed auth method %q: %v", id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,237 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,373 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,12 +58,21 @@ func resourceGroup() *schema.Resource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setFromGroupResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
|
func setFromGroupResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
|
||||||
d.Set(NameKey, raw["name"])
|
if err := d.Set(NameKey, raw["name"]); err != nil {
|
||||||
d.Set(DescriptionKey, raw["description"])
|
return err
|
||||||
d.Set(ScopeIdKey, raw["scope_id"])
|
}
|
||||||
d.Set(groupMemberIdsKey, raw["member_ids"])
|
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
|
||||||
|
}
|
||||||
d.SetId(raw["id"].(string))
|
d.SetId(raw["id"].(string))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
func resourceGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||||
@@ -99,7 +108,7 @@ func resourceGroupCreate(ctx context.Context, d *schema.ResourceData, meta inter
|
|||||||
if gcr == nil {
|
if gcr == nil {
|
||||||
return diag.Errorf("group nil after create")
|
return diag.Errorf("group nil after create")
|
||||||
}
|
}
|
||||||
raw := gcr.GetResponseMap()
|
raw := gcr.GetResponse().Map
|
||||||
|
|
||||||
if val, ok := d.GetOk(groupMemberIdsKey); ok {
|
if val, ok := d.GetOk(groupMemberIdsKey); ok {
|
||||||
list := val.(*schema.Set).List()
|
list := val.(*schema.Set).List()
|
||||||
@@ -114,10 +123,12 @@ func resourceGroupCreate(ctx context.Context, d *schema.ResourceData, meta inter
|
|||||||
if gcsmr == nil {
|
if gcsmr == nil {
|
||||||
return diag.Errorf("group nil after setting members")
|
return diag.Errorf("group nil after setting members")
|
||||||
}
|
}
|
||||||
raw = gcsmr.GetResponseMap()
|
raw = gcsmr.GetResponse().Map
|
||||||
}
|
}
|
||||||
|
|
||||||
setFromGroupResponseMap(d, raw)
|
if err := setFromGroupResponseMap(d, raw); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -128,7 +139,7 @@ func resourceGroupRead(ctx context.Context, d *schema.ResourceData, meta interfa
|
|||||||
|
|
||||||
g, err := grps.Read(ctx, d.Id())
|
g, err := grps.Read(ctx, d.Id())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if apiErr := api.AsServerError(err); apiErr.ResponseStatus() == http.StatusNotFound {
|
if apiErr := api.AsServerError(err); apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||||
d.SetId("")
|
d.SetId("")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -138,7 +149,9 @@ func resourceGroupRead(ctx context.Context, d *schema.ResourceData, meta interfa
|
|||||||
return diag.Errorf("group nil after read")
|
return diag.Errorf("group nil after read")
|
||||||
}
|
}
|
||||||
|
|
||||||
setFromGroupResponseMap(d, g.GetResponseMap())
|
if err := setFromGroupResponseMap(d, g.GetResponse().Map); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -180,10 +193,14 @@ func resourceGroupUpdate(ctx context.Context, d *schema.ResourceData, meta inter
|
|||||||
}
|
}
|
||||||
|
|
||||||
if d.HasChange(NameKey) {
|
if d.HasChange(NameKey) {
|
||||||
d.Set(NameKey, name)
|
if err := d.Set(NameKey, name); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if d.HasChange(DescriptionKey) {
|
if d.HasChange(DescriptionKey) {
|
||||||
d.Set(DescriptionKey, desc)
|
if err := d.Set(DescriptionKey, desc); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The above call may not actually happen, so we use d.Id() and automatic
|
// The above call may not actually happen, so we use d.Id() and automatic
|
||||||
@@ -201,7 +218,10 @@ func resourceGroupUpdate(ctx context.Context, d *schema.ResourceData, meta inter
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return diag.Errorf("error updating members in group: %v", err)
|
return diag.Errorf("error updating members in group: %v", err)
|
||||||
}
|
}
|
||||||
d.Set(groupMemberIdsKey, memberIds)
|
if err := d.Set(groupMemberIdsKey, memberIds); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -365,7 +365,7 @@ func testAccCheckGroupResourceDestroy(t *testing.T, testProvider *schema.Provide
|
|||||||
id := rs.Primary.ID
|
id := rs.Primary.ID
|
||||||
|
|
||||||
_, err := grps.Read(context.Background(), id)
|
_, err := grps.Read(context.Background(), id)
|
||||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.ResponseStatus() != http.StatusNotFound {
|
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)
|
return fmt.Errorf("didn't get a 404 when reading destroyed resource %q: %v", id, apiErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,199 +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{
|
|
||||||
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{
|
|
||||||
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` (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.ResponseStatus() == 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
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,648 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,391 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
+40
-20
@@ -20,30 +20,50 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
projHostCatalog = fmt.Sprintf(`
|
projHostCatalog = `
|
||||||
resource "boundary_host_catalog" "foo" {
|
resource "%s" "foo" {
|
||||||
name = "foo"
|
name = "foo"
|
||||||
description = "%s"
|
description = "bar"
|
||||||
scope_id = boundary_scope.proj1.id
|
scope_id = boundary_scope.proj1.id
|
||||||
type = "static"
|
%s
|
||||||
depends_on = [boundary_role.proj1_admin]
|
depends_on = [boundary_role.proj1_admin]
|
||||||
}`, fooHostCatalogDescription)
|
}`
|
||||||
|
|
||||||
projHostCatalogUpdate = fmt.Sprintf(`
|
projHostCatalogUpdate = `
|
||||||
resource "boundary_host_catalog" "foo" {
|
resource "%s" "foo" {
|
||||||
name = "foo"
|
name = "foo"
|
||||||
description = "%s"
|
description = "foo bar"
|
||||||
scope_id = boundary_scope.proj1.id
|
scope_id = boundary_scope.proj1.id
|
||||||
type = "static"
|
%s
|
||||||
depends_on = [boundary_role.proj1_admin]
|
depends_on = [boundary_role.proj1_admin]
|
||||||
}`, fooHostCatalogDescriptionUpdate)
|
}`
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAccHostCatalogCreate(t *testing.T) {
|
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...)
|
tc := controller.NewTestController(t, tcConfig...)
|
||||||
defer tc.Shutdown()
|
defer tc.Shutdown()
|
||||||
url := tc.ApiAddrs()[0]
|
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
|
var provider *schema.Provider
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
ProviderFactories: providerFactories(&provider),
|
ProviderFactories: providerFactories(&provider),
|
||||||
@@ -51,24 +71,24 @@ func TestAccHostCatalogCreate(t *testing.T) {
|
|||||||
Steps: []resource.TestStep{
|
Steps: []resource.TestStep{
|
||||||
{
|
{
|
||||||
// test create
|
// test create
|
||||||
Config: testConfig(url, fooOrg, firstProjectFoo, projHostCatalog),
|
Config: testConfig(url, fooOrg, firstProjectFoo, hc),
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckScopeResourceExists(provider, "boundary_scope.org1"),
|
testAccCheckScopeResourceExists(provider, "boundary_scope.org1"),
|
||||||
testAccCheckScopeResourceExists(provider, "boundary_scope.proj1"),
|
testAccCheckScopeResourceExists(provider, "boundary_scope.proj1"),
|
||||||
testAccCheckHostCatalogResourceExists(provider, "boundary_host_catalog.foo"),
|
testAccCheckHostCatalogResourceExists(provider, fooName),
|
||||||
resource.TestCheckResourceAttr("boundary_host_catalog.foo", DescriptionKey, fooHostCatalogDescription),
|
resource.TestCheckResourceAttr(fooName, DescriptionKey, fooHostCatalogDescription),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
importStep("boundary_host_catalog.foo"),
|
importStep(fooName),
|
||||||
{
|
{
|
||||||
// test update
|
// test update
|
||||||
Config: testConfig(url, fooOrg, firstProjectFoo, projHostCatalogUpdate),
|
Config: testConfig(url, fooOrg, firstProjectFoo, hcu),
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckHostCatalogResourceExists(provider, "boundary_host_catalog.foo"),
|
testAccCheckHostCatalogResourceExists(provider, fooName),
|
||||||
resource.TestCheckResourceAttr("boundary_host_catalog.foo", DescriptionKey, fooHostCatalogDescriptionUpdate),
|
resource.TestCheckResourceAttr(fooName, DescriptionKey, fooHostCatalogDescriptionUpdate),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
importStep("boundary_host_catalog.foo"),
|
importStep(fooName),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -102,13 +122,13 @@ func testAccCheckHostCatalogResourceDestroy(t *testing.T, testProvider *schema.P
|
|||||||
|
|
||||||
for _, rs := range s.RootModule().Resources {
|
for _, rs := range s.RootModule().Resources {
|
||||||
switch rs.Type {
|
switch rs.Type {
|
||||||
case "boundary_host_catalog":
|
case "boundary_host_catalog", "boundary_host_catalog_static":
|
||||||
|
|
||||||
id := rs.Primary.ID
|
id := rs.Primary.ID
|
||||||
hcClient := hostcatalogs.NewClient(md.client)
|
hcClient := hostcatalogs.NewClient(md.client)
|
||||||
|
|
||||||
_, err := hcClient.Read(context.Background(), id)
|
_, err := hcClient.Read(context.Background(), id)
|
||||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.ResponseStatus() != http.StatusNotFound {
|
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)
|
return fmt.Errorf("didn't get a 404 when reading destroyed host catalog %q: %v", id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,356 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,311 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
+118
-43
@@ -11,51 +11,52 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
hostsetHostIdsKey = "host_ids"
|
hostSetHostIdsKey = "host_ids"
|
||||||
hostsetTypeStatic = "static"
|
hostSetTypeStatic = "static"
|
||||||
)
|
)
|
||||||
|
|
||||||
func resourceHostset() *schema.Resource {
|
func resourceHostSet() *schema.Resource {
|
||||||
return &schema.Resource{
|
return &schema.Resource{
|
||||||
Description: "The host_set resource allows you to configure a Boundary host set. Host sets are " +
|
DeprecationMessage: "Deprecated: use `resource_host_set_static` instead.",
|
||||||
"always part of a host catalog, so a host catalog resource should be used inline or you " +
|
Description: "Deprecated: use `resource_host_set_static` instead.",
|
||||||
"should have the host catalog ID in hand to successfully configure a host set.",
|
|
||||||
|
|
||||||
CreateContext: resourceHostsetCreate,
|
CreateContext: resourceHostSetStaticCreate,
|
||||||
ReadContext: resourceHostsetRead,
|
ReadContext: resourceHostSetStaticRead,
|
||||||
UpdateContext: resourceHostsetUpdate,
|
UpdateContext: resourceHostSetStaticUpdate,
|
||||||
DeleteContext: resourceHostsetDelete,
|
DeleteContext: resourceHostSetStaticDelete,
|
||||||
Importer: &schema.ResourceImporter{
|
Importer: &schema.ResourceImporter{
|
||||||
StateContext: schema.ImportStatePassthroughContext,
|
StateContext: schema.ImportStatePassthroughContext,
|
||||||
},
|
},
|
||||||
|
|
||||||
Schema: map[string]*schema.Schema{
|
Schema: map[string]*schema.Schema{
|
||||||
IDKey: {
|
IDKey: {
|
||||||
Description: "The ID of the hostset.",
|
Description: "The ID of the host set.",
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
},
|
},
|
||||||
NameKey: {
|
NameKey: {
|
||||||
Description: "The hostset name. Defaults to the resource name.",
|
Description: "The host set name. Defaults to the resource name.",
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
DescriptionKey: {
|
DescriptionKey: {
|
||||||
Description: "The hostset description.",
|
Description: "The host set description.",
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
TypeKey: {
|
TypeKey: {
|
||||||
Type: schema.TypeString,
|
Description: "The type of host set",
|
||||||
Required: true,
|
|
||||||
ForceNew: true,
|
|
||||||
},
|
|
||||||
HostCatalogIdKey: {
|
|
||||||
Description: "The catalog for the hostset.",
|
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Required: true,
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
hostsetHostIdsKey: {
|
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.",
|
Description: "The list of host IDs contained in this set.",
|
||||||
Type: schema.TypeSet,
|
Type: schema.TypeSet,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
@@ -65,16 +66,80 @@ func resourceHostset() *schema.Resource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setFromHostSetResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
|
func resourceHostSetStatic() *schema.Resource {
|
||||||
d.Set(NameKey, raw["name"])
|
return &schema.Resource{
|
||||||
d.Set(DescriptionKey, raw["description"])
|
Description: "The host_set_static resource allows you to configure a Boundary host set. Host sets are " +
|
||||||
d.Set(HostCatalogIdKey, raw["host_catalog_id"])
|
"always part of a host catalog, so a host catalog resource should be used inline or you " +
|
||||||
d.Set(TypeKey, raw["type"])
|
"should have the host catalog ID in hand to successfully configure a host set.",
|
||||||
d.Set(hostsetHostIdsKey, raw["host_ids"])
|
|
||||||
d.SetId(raw["id"].(string))
|
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,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Default: hostSetTypeStatic,
|
||||||
|
},
|
||||||
|
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 resourceHostsetCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
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
|
||||||
|
}
|
||||||
|
d.SetId(raw["id"].(string))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceHostSetStaticCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||||
md := meta.(*metaData)
|
md := meta.(*metaData)
|
||||||
|
|
||||||
var hostsetHostCatalogId string
|
var hostsetHostCatalogId string
|
||||||
@@ -85,7 +150,7 @@ func resourceHostsetCreate(ctx context.Context, d *schema.ResourceData, meta int
|
|||||||
}
|
}
|
||||||
|
|
||||||
var hostIds []string
|
var hostIds []string
|
||||||
if hostIdsVal, ok := d.GetOk(hostsetHostIdsKey); ok {
|
if hostIdsVal, ok := d.GetOk(hostSetHostIdsKey); ok {
|
||||||
list := hostIdsVal.(*schema.Set).List()
|
list := hostIdsVal.(*schema.Set).List()
|
||||||
hostIds = make([]string, 0, len(list))
|
hostIds = make([]string, 0, len(list))
|
||||||
for _, i := range list {
|
for _, i := range list {
|
||||||
@@ -104,7 +169,7 @@ func resourceHostsetCreate(ctx context.Context, d *schema.ResourceData, meta int
|
|||||||
switch typeStr {
|
switch typeStr {
|
||||||
// NOTE: When other types are added, ensure they don't accept hostSetIds if
|
// NOTE: When other types are added, ensure they don't accept hostSetIds if
|
||||||
// it's not allowed
|
// it's not allowed
|
||||||
case hostsetTypeStatic:
|
case hostSetTypeStatic:
|
||||||
default:
|
default:
|
||||||
return diag.Errorf("invalid type provided")
|
return diag.Errorf("invalid type provided")
|
||||||
}
|
}
|
||||||
@@ -130,7 +195,7 @@ func resourceHostsetCreate(ctx context.Context, d *schema.ResourceData, meta int
|
|||||||
if hscr == nil {
|
if hscr == nil {
|
||||||
return diag.Errorf("nil host set after create")
|
return diag.Errorf("nil host set after create")
|
||||||
}
|
}
|
||||||
raw := hscr.GetResponseMap()
|
raw := hscr.GetResponse().Map
|
||||||
|
|
||||||
if hostIds != nil {
|
if hostIds != nil {
|
||||||
hsshr, err := hsClient.SetHosts(ctx, hscr.Item.Id, hscr.Item.Version, hostIds)
|
hsshr, err := hsClient.SetHosts(ctx, hscr.Item.Id, hscr.Item.Version, hostIds)
|
||||||
@@ -140,21 +205,23 @@ func resourceHostsetCreate(ctx context.Context, d *schema.ResourceData, meta int
|
|||||||
if hsshr == nil {
|
if hsshr == nil {
|
||||||
return diag.Errorf("nil host set after setting hosts")
|
return diag.Errorf("nil host set after setting hosts")
|
||||||
}
|
}
|
||||||
raw = hsshr.GetResponseMap()
|
raw = hsshr.GetResponse().Map
|
||||||
}
|
}
|
||||||
|
|
||||||
setFromHostSetResponseMap(d, raw)
|
if err := setFromHostSetStaticResponseMap(d, raw); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceHostsetRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
func resourceHostSetStaticRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||||
md := meta.(*metaData)
|
md := meta.(*metaData)
|
||||||
hsClient := hostsets.NewClient(md.client)
|
hsClient := hostsets.NewClient(md.client)
|
||||||
|
|
||||||
hsrr, err := hsClient.Read(ctx, d.Id())
|
hsrr, err := hsClient.Read(ctx, d.Id())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound {
|
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||||
d.SetId("")
|
d.SetId("")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -164,12 +231,14 @@ func resourceHostsetRead(ctx context.Context, d *schema.ResourceData, meta inter
|
|||||||
return diag.Errorf("host set nil after read")
|
return diag.Errorf("host set nil after read")
|
||||||
}
|
}
|
||||||
|
|
||||||
setFromHostSetResponseMap(d, hsrr.GetResponseMap())
|
if err := setFromHostSetStaticResponseMap(d, hsrr.GetResponse().Map); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceHostsetUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
func resourceHostSetStaticUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||||
md := meta.(*metaData)
|
md := meta.(*metaData)
|
||||||
hsClient := hostsets.NewClient(md.client)
|
hsClient := hostsets.NewClient(md.client)
|
||||||
|
|
||||||
@@ -206,17 +275,21 @@ func resourceHostsetUpdate(ctx context.Context, d *schema.ResourceData, meta int
|
|||||||
}
|
}
|
||||||
|
|
||||||
if d.HasChange(NameKey) {
|
if d.HasChange(NameKey) {
|
||||||
d.Set(NameKey, name)
|
if err := d.Set(NameKey, name); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if d.HasChange(DescriptionKey) {
|
if d.HasChange(DescriptionKey) {
|
||||||
d.Set(DescriptionKey, desc)
|
if err := d.Set(DescriptionKey, desc); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The above call may not actually happen, so we use d.Id() and automatic
|
// The above call may not actually happen, so we use d.Id() and automatic
|
||||||
// versioning here
|
// versioning here
|
||||||
if d.HasChange(hostsetHostIdsKey) {
|
if d.HasChange(hostSetHostIdsKey) {
|
||||||
var hostIds []string
|
var hostIds []string
|
||||||
if hostIdsVal, ok := d.GetOk(hostsetHostIdsKey); ok {
|
if hostIdsVal, ok := d.GetOk(hostSetHostIdsKey); ok {
|
||||||
hosts := hostIdsVal.(*schema.Set).List()
|
hosts := hostIdsVal.(*schema.Set).List()
|
||||||
for _, host := range hosts {
|
for _, host := range hosts {
|
||||||
hostIds = append(hostIds, host.(string))
|
hostIds = append(hostIds, host.(string))
|
||||||
@@ -226,13 +299,15 @@ func resourceHostsetUpdate(ctx context.Context, d *schema.ResourceData, meta int
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return diag.Errorf("error updating hosts in host set: %v", err)
|
return diag.Errorf("error updating hosts in host set: %v", err)
|
||||||
}
|
}
|
||||||
d.Set(hostsetHostIdsKey, hostIds)
|
if err := d.Set(hostSetHostIdsKey, hostIds); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceHostsetDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
func resourceHostSetStaticDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||||
md := meta.(*metaData)
|
md := meta.(*metaData)
|
||||||
hsClient := hostsets.NewClient(md.client)
|
hsClient := hostsets.NewClient(md.client)
|
||||||
|
|
||||||
+72
-65
@@ -14,97 +14,104 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func TestAccHostSetStatic(t *testing.T) {
|
||||||
fooHostset = `
|
t.Run("non-static", func(t *testing.T) {
|
||||||
resource "boundary_host_catalog" "foo" {
|
t.Parallel()
|
||||||
scope_id = boundary_scope.proj1.id
|
testAccHostSetStatic(t, false)
|
||||||
type = "static"
|
})
|
||||||
depends_on = [boundary_role.proj1_admin]
|
t.Run("static", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testAccHostSetStatic(t, true)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "boundary_host" "foo" {
|
func testAccHostSetStatic(t *testing.T, static bool) {
|
||||||
type = "static"
|
catalogBlock := `
|
||||||
host_catalog_id = boundary_host_catalog.foo.id
|
resource "%s" "foo" {
|
||||||
address = "10.0.0.1"
|
%s
|
||||||
}
|
scope_id = boundary_scope.proj1.id
|
||||||
|
depends_on = [boundary_role.proj1_admin]
|
||||||
|
}`
|
||||||
|
|
||||||
resource "boundary_host_set" "foo" {
|
hostBlock := `
|
||||||
type = "static"
|
resource "%s" "foo" {
|
||||||
name = "test"
|
host_catalog_id = %s.foo.id
|
||||||
description = "test hostset"
|
%s
|
||||||
host_catalog_id = boundary_host_catalog.foo.id
|
address = "10.0.0.1"
|
||||||
host_ids = [boundary_host.foo.id]
|
}`
|
||||||
}`
|
|
||||||
|
|
||||||
fooHostsetUpdate = `
|
hostSetBlock := `
|
||||||
resource "boundary_host_catalog" "foo" {
|
resource "%s" "foo" {
|
||||||
scope_id = boundary_scope.proj1.id
|
host_catalog_id = %s.foo.id
|
||||||
type = "static"
|
name = "test"
|
||||||
depends_on = [boundary_role.proj1_admin]
|
description = "test hostset"
|
||||||
}
|
%s
|
||||||
|
host_ids = [%s.foo.id%s]
|
||||||
|
}`
|
||||||
|
|
||||||
resource "boundary_host" "foo" {
|
host2Block := `
|
||||||
type = "static"
|
resource "%s" "bar" {
|
||||||
host_catalog_id = boundary_host_catalog.foo.id
|
host_catalog_id = %s.foo.id
|
||||||
address = "10.0.0.1"
|
%s
|
||||||
}
|
address = "10.0.0.2"
|
||||||
|
}`
|
||||||
|
|
||||||
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...)
|
tc := controller.NewTestController(t, tcConfig...)
|
||||||
defer tc.Shutdown()
|
defer tc.Shutdown()
|
||||||
// org := iam.TestOrg(t, tc.IamRepo())
|
// org := iam.TestOrg(t, tc.IamRepo())
|
||||||
url := tc.ApiAddrs()[0]
|
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
|
var provider *schema.Provider
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
ProviderFactories: providerFactories(&provider),
|
ProviderFactories: providerFactories(&provider),
|
||||||
CheckDestroy: testAccCheckHostsetResourceDestroy(t, provider),
|
CheckDestroy: testAccCheckHostSetStaticResourceDestroy(t, provider),
|
||||||
Steps: []resource.TestStep{
|
Steps: []resource.TestStep{
|
||||||
{
|
{
|
||||||
// test project hostset create
|
// test project hostset create
|
||||||
Config: testConfig(url, fooOrg, firstProjectFoo, fooHostset),
|
Config: testConfig(url, fooOrg, firstProjectFoo, hcBlock, hBlock, hsBlock),
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckHostsetResourceExists(provider, "boundary_host_set.foo"),
|
testAccCheckHostSetStaticResourceExists(provider, fooSetName),
|
||||||
testAccCheckHostsetHostIDsSet(provider, "boundary_host_set.foo", []string{"boundary_host.foo"}),
|
testAccCheckHostSetStaticHostIDsSet(provider, fooSetName, []string{fooHostName}),
|
||||||
resource.TestCheckResourceAttr("boundary_host_set.foo", "name", "test"),
|
resource.TestCheckResourceAttr(fooSetName, "name", "test"),
|
||||||
resource.TestCheckResourceAttr("boundary_host_set.foo", "description", "test hostset"),
|
resource.TestCheckResourceAttr(fooSetName, "description", "test hostset"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
importStep("boundary_host_set.foo"),
|
importStep(fooSetName),
|
||||||
{
|
{
|
||||||
// test project hostset update
|
// test project hostset update
|
||||||
Config: testConfig(url, fooOrg, firstProjectFoo, fooHostsetUpdate),
|
Config: testConfig(url, fooOrg, firstProjectFoo, hcBlock, hBlock, h2Block, hsUpdateBlock),
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckHostsetResourceExists(provider, "boundary_host_set.foo"),
|
testAccCheckHostSetStaticResourceExists(provider, fooSetName),
|
||||||
testAccCheckHostsetHostIDsSet(provider, "boundary_host_set.foo", []string{"boundary_host.foo", "boundary_host.bar"}),
|
testAccCheckHostSetStaticHostIDsSet(provider, fooSetName, []string{fooHostName, barHostName}),
|
||||||
resource.TestCheckResourceAttr("boundary_host_set.foo", "name", "test"),
|
resource.TestCheckResourceAttr(fooSetName, "name", "test"),
|
||||||
resource.TestCheckResourceAttr("boundary_host_set.foo", "description", "test hostset"),
|
resource.TestCheckResourceAttr(fooSetName, "description", "test hostset"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
importStep("boundary_host_set.foo"),
|
importStep(fooSetName),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckHostsetResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
|
func testAccCheckHostSetStaticResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
|
||||||
return func(s *terraform.State) error {
|
return func(s *terraform.State) error {
|
||||||
rs, ok := s.RootModule().Resources[name]
|
rs, ok := s.RootModule().Resources[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -127,7 +134,7 @@ func testAccCheckHostsetResourceExists(testProvider *schema.Provider, name strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckHostsetHostIDsSet(testProvider *schema.Provider, name string, wantHostIDs []string) resource.TestCheckFunc {
|
func testAccCheckHostSetStaticHostIDsSet(testProvider *schema.Provider, name string, wantHostIDs []string) resource.TestCheckFunc {
|
||||||
return func(s *terraform.State) error {
|
return func(s *terraform.State) error {
|
||||||
rs, ok := s.RootModule().Resources[name]
|
rs, ok := s.RootModule().Resources[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -185,7 +192,7 @@ func testAccCheckHostsetHostIDsSet(testProvider *schema.Provider, name string, w
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckHostsetResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
|
func testAccCheckHostSetStaticResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
|
||||||
return func(s *terraform.State) error {
|
return func(s *terraform.State) error {
|
||||||
if testProvider.Meta() == nil {
|
if testProvider.Meta() == nil {
|
||||||
t.Fatal("got nil provider metadata")
|
t.Fatal("got nil provider metadata")
|
||||||
@@ -201,7 +208,7 @@ func testAccCheckHostsetResourceDestroy(t *testing.T, testProvider *schema.Provi
|
|||||||
hostsetsClient := hostsets.NewClient(md.client)
|
hostsetsClient := hostsets.NewClient(md.client)
|
||||||
|
|
||||||
_, err := hostsetsClient.Read(context.Background(), id)
|
_, err := hostsetsClient.Read(context.Background(), id)
|
||||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.ResponseStatus() != http.StatusNotFound {
|
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)
|
return fmt.Errorf("didn't get a 404 when reading destroyed host set %q: %v", id, apiErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,14 +17,13 @@ const (
|
|||||||
|
|
||||||
func resourceHost() *schema.Resource {
|
func resourceHost() *schema.Resource {
|
||||||
return &schema.Resource{
|
return &schema.Resource{
|
||||||
Description: "The host resource allows you to configure a Boundary static host. Hosts are " +
|
DeprecationMessage: "Deprecated: use `resource_host_static` instead.",
|
||||||
"always part of a project, so a project resource should be used inline or you should have " +
|
Description: "Deprecated: use `resource_host_static` instead.",
|
||||||
"the project ID in hand to successfully configure a host.",
|
|
||||||
|
|
||||||
CreateContext: resourceHostCreate,
|
CreateContext: resourceHostStaticCreate,
|
||||||
ReadContext: resourceHostRead,
|
ReadContext: resourceHostStaticRead,
|
||||||
UpdateContext: resourceHostUpdate,
|
UpdateContext: resourceHostStaticUpdate,
|
||||||
DeleteContext: resourceHostDelete,
|
DeleteContext: resourceHostStaticDelete,
|
||||||
Importer: &schema.ResourceImporter{
|
Importer: &schema.ResourceImporter{
|
||||||
StateContext: schema.ImportStatePassthroughContext,
|
StateContext: schema.ImportStatePassthroughContext,
|
||||||
},
|
},
|
||||||
@@ -46,9 +45,10 @@ func resourceHost() *schema.Resource {
|
|||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
TypeKey: {
|
TypeKey: {
|
||||||
Type: schema.TypeString,
|
Description: "The type of host",
|
||||||
Required: true,
|
Type: schema.TypeString,
|
||||||
ForceNew: true,
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
HostCatalogIdKey: {
|
HostCatalogIdKey: {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
@@ -63,24 +63,85 @@ func resourceHost() *schema.Resource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setFromHostResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
|
func resourceHostStatic() *schema.Resource {
|
||||||
d.Set(NameKey, raw["name"])
|
return &schema.Resource{
|
||||||
d.Set(DescriptionKey, raw["description"])
|
Description: "The static host resource allows you to configure a Boundary static host. Hosts are " +
|
||||||
d.Set(HostCatalogIdKey, raw["host_catalog_id"])
|
"always part of a project, so a project resource should be used inline or you should have " +
|
||||||
d.Set(TypeKey, raw["type"])
|
"the project ID in hand to successfully configure a host.",
|
||||||
|
|
||||||
|
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,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Default: "static",
|
||||||
|
},
|
||||||
|
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 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
|
||||||
|
}
|
||||||
|
|
||||||
switch raw["type"].(string) {
|
switch raw["type"].(string) {
|
||||||
case hostTypeStatic:
|
case hostTypeStatic:
|
||||||
if attrsVal, ok := raw["attributes"]; ok {
|
if attrsVal, ok := raw["attributes"]; ok {
|
||||||
attrs := attrsVal.(map[string]interface{})
|
attrs := attrsVal.(map[string]interface{})
|
||||||
d.Set(hostAddressKey, attrs["address"])
|
if err := d.Set(hostAddressKey, attrs["address"]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d.SetId(raw["id"].(string))
|
d.SetId(raw["id"].(string))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceHostCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
func resourceHostStaticCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||||
md := meta.(*metaData)
|
md := meta.(*metaData)
|
||||||
|
|
||||||
var hostCatalogId string
|
var hostCatalogId string
|
||||||
@@ -140,18 +201,20 @@ func resourceHostCreate(ctx context.Context, d *schema.ResourceData, meta interf
|
|||||||
return diag.Errorf("host nil after create")
|
return diag.Errorf("host nil after create")
|
||||||
}
|
}
|
||||||
|
|
||||||
setFromHostResponseMap(d, hcr.GetResponseMap())
|
if err := setFromHostResponseMap(d, hcr.GetResponse().Map); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceHostRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
func resourceHostStaticRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||||
md := meta.(*metaData)
|
md := meta.(*metaData)
|
||||||
hClient := hosts.NewClient(md.client)
|
hClient := hosts.NewClient(md.client)
|
||||||
|
|
||||||
hrr, err := hClient.Read(ctx, d.Id())
|
hrr, err := hClient.Read(ctx, d.Id())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound {
|
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||||
d.SetId("")
|
d.SetId("")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -161,12 +224,14 @@ func resourceHostRead(ctx context.Context, d *schema.ResourceData, meta interfac
|
|||||||
return diag.Errorf("host nil after read")
|
return diag.Errorf("host nil after read")
|
||||||
}
|
}
|
||||||
|
|
||||||
setFromHostResponseMap(d, hrr.GetResponseMap())
|
if err := setFromHostResponseMap(d, hrr.GetResponse().Map); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceHostUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
func resourceHostStaticUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||||
md := meta.(*metaData)
|
md := meta.(*metaData)
|
||||||
hClient := hosts.NewClient(md.client)
|
hClient := hosts.NewClient(md.client)
|
||||||
|
|
||||||
@@ -219,19 +284,25 @@ func resourceHostUpdate(ctx context.Context, d *schema.ResourceData, meta interf
|
|||||||
}
|
}
|
||||||
|
|
||||||
if d.HasChange(NameKey) {
|
if d.HasChange(NameKey) {
|
||||||
d.Set(NameKey, name)
|
if err := d.Set(NameKey, name); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if d.HasChange(DescriptionKey) {
|
if d.HasChange(DescriptionKey) {
|
||||||
d.Set(DescriptionKey, desc)
|
if err := d.Set(DescriptionKey, desc); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if d.HasChange(hostAddressKey) {
|
if d.HasChange(hostAddressKey) {
|
||||||
d.Set(hostAddressKey, *address)
|
if err := d.Set(hostAddressKey, *address); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceHostDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
func resourceHostStaticDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||||
md := meta.(*metaData)
|
md := meta.(*metaData)
|
||||||
hClient := hosts.NewClient(md.client)
|
hClient := hosts.NewClient(md.client)
|
||||||
|
|
||||||
+62
-56
@@ -15,53 +15,59 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
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]
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "boundary_host" "foo" {
|
|
||||||
type = "static"
|
|
||||||
host_catalog_id = boundary_host_catalog.foo.id
|
|
||||||
name = "test"
|
|
||||||
description = "test host"
|
|
||||||
address = "%s"
|
|
||||||
}`, fooHostAddress)
|
|
||||||
|
|
||||||
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]
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccHost(t *testing.T, static bool) {
|
||||||
|
const (
|
||||||
|
fooHostAddress = "10.0.0.1"
|
||||||
|
fooHostAddressUpdate = "10.10.0.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
projCatalog := `
|
||||||
|
resource "%s" "foo" {
|
||||||
|
name = "test"
|
||||||
|
description = "test catalog"
|
||||||
|
%s
|
||||||
|
scope_id = boundary_scope.proj1.id
|
||||||
|
depends_on = [boundary_role.proj1_admin]
|
||||||
|
}`
|
||||||
|
|
||||||
|
projHost := `
|
||||||
|
resource "%s" "foo" {
|
||||||
|
host_catalog_id = %s.foo.id
|
||||||
|
name = "test"
|
||||||
|
description = "test host"
|
||||||
|
%s
|
||||||
|
address = "%s"
|
||||||
|
}`
|
||||||
|
|
||||||
tc := controller.NewTestController(t, tcConfig...)
|
tc := controller.NewTestController(t, tcConfig...)
|
||||||
defer tc.Shutdown()
|
defer tc.Shutdown()
|
||||||
// org := iam.TestOrg(t, tc.IamRepo())
|
// org := iam.TestOrg(t, tc.IamRepo())
|
||||||
url := tc.ApiAddrs()[0]
|
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
|
var provider *schema.Provider
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
ProviderFactories: providerFactories(&provider),
|
ProviderFactories: providerFactories(&provider),
|
||||||
@@ -69,28 +75,28 @@ func TestAccHost(t *testing.T) {
|
|||||||
Steps: []resource.TestStep{
|
Steps: []resource.TestStep{
|
||||||
{
|
{
|
||||||
// test project host create
|
// test project host create
|
||||||
Config: testConfig(url, fooOrg, firstProjectFoo, projHost),
|
Config: testConfig(url, fooOrg, firstProjectFoo, hcBlock, hostBlock),
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckHostAttributeSet(provider, "boundary_host.foo", "address", fooHostAddress),
|
testAccCheckHostAttributeSet(provider, fooHostName, "address", fooHostAddress),
|
||||||
testAccCheckHostResourceExists(provider, "boundary_host.foo"),
|
testAccCheckHostResourceExists(provider, fooHostName),
|
||||||
resource.TestCheckResourceAttr("boundary_host.foo", "name", "test"),
|
resource.TestCheckResourceAttr(fooHostName, "name", "test"),
|
||||||
resource.TestCheckResourceAttr("boundary_host.foo", "description", "test host"),
|
resource.TestCheckResourceAttr(fooHostName, "description", "test host"),
|
||||||
resource.TestCheckResourceAttr("boundary_host.foo", "address", fooHostAddress),
|
resource.TestCheckResourceAttr(fooHostName, "address", fooHostAddress),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
importStep("boundary_host.foo"),
|
importStep(fooHostName),
|
||||||
{
|
{
|
||||||
// test project host update
|
// test project host update
|
||||||
Config: testConfig(url, fooOrg, firstProjectFoo, projHostUpdate),
|
Config: testConfig(url, fooOrg, firstProjectFoo, hcBlock, hostUpdateBlock),
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckHostAttributeSet(provider, "boundary_host.foo", "address", fooHostAddressUpdate),
|
testAccCheckHostAttributeSet(provider, fooHostName, "address", fooHostAddressUpdate),
|
||||||
testAccCheckHostResourceExists(provider, "boundary_host.foo"),
|
testAccCheckHostResourceExists(provider, fooHostName),
|
||||||
resource.TestCheckResourceAttr("boundary_host.foo", "name", "test"),
|
resource.TestCheckResourceAttr(fooHostName, "name", "test"),
|
||||||
resource.TestCheckResourceAttr("boundary_host.foo", "description", "test host"),
|
resource.TestCheckResourceAttr(fooHostName, "description", "test host"),
|
||||||
resource.TestCheckResourceAttr("boundary_host.foo", "address", fooHostAddressUpdate),
|
resource.TestCheckResourceAttr(fooHostName, "address", fooHostAddressUpdate),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
importStep("boundary_host.foo"),
|
importStep(fooHostName),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -171,7 +177,7 @@ func testAccCheckHostResourceDestroy(t *testing.T, testProvider *schema.Provider
|
|||||||
hostsClient := hosts.NewClient(md.client)
|
hostsClient := hosts.NewClient(md.client)
|
||||||
|
|
||||||
_, err := hostsClient.Read(context.Background(), id)
|
_, err := hostsClient.Read(context.Background(), id)
|
||||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.ResponseStatus() != http.StatusNotFound {
|
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)
|
return fmt.Errorf("didn't get a 404 when reading destroyed host %q: %v", id, apiErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,14 +77,27 @@ func resourceRole() *schema.Resource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setFromRoleResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
|
func setFromRoleResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
|
||||||
d.Set(NameKey, raw["name"])
|
if err := d.Set(NameKey, raw["name"]); err != nil {
|
||||||
d.Set(DescriptionKey, raw["description"])
|
return err
|
||||||
d.Set(ScopeIdKey, raw["scope_id"])
|
}
|
||||||
d.Set(rolePrincipalIdsKey, raw["principal_ids"])
|
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
|
||||||
d.Set(roleGrantStringsKey, raw["grant_strings"])
|
return err
|
||||||
d.Set(roleGrantScopeIdKey, raw["grant_scope_id"])
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
d.SetId(raw["id"].(string))
|
d.SetId(raw["id"].(string))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceRoleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
func resourceRoleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||||
@@ -149,7 +162,7 @@ func resourceRoleCreate(ctx context.Context, d *schema.ResourceData, meta interf
|
|||||||
if tcr == nil {
|
if tcr == nil {
|
||||||
return diag.Errorf("nil role after create")
|
return diag.Errorf("nil role after create")
|
||||||
}
|
}
|
||||||
raw := tcr.GetResponseMap()
|
raw := tcr.GetResponse().Map
|
||||||
|
|
||||||
if principalIds != nil {
|
if principalIds != nil {
|
||||||
tspr, err := rc.SetPrincipals(ctx, tcr.Item.Id, 0, principalIds, roles.WithAutomaticVersioning(true))
|
tspr, err := rc.SetPrincipals(ctx, tcr.Item.Id, 0, principalIds, roles.WithAutomaticVersioning(true))
|
||||||
@@ -159,7 +172,7 @@ func resourceRoleCreate(ctx context.Context, d *schema.ResourceData, meta interf
|
|||||||
if tspr == nil {
|
if tspr == nil {
|
||||||
return diag.Errorf("nil role after setting principal IDs")
|
return diag.Errorf("nil role after setting principal IDs")
|
||||||
}
|
}
|
||||||
raw = tspr.GetResponseMap()
|
raw = tspr.GetResponse().Map
|
||||||
}
|
}
|
||||||
|
|
||||||
if grantStrings != nil {
|
if grantStrings != nil {
|
||||||
@@ -170,10 +183,12 @@ func resourceRoleCreate(ctx context.Context, d *schema.ResourceData, meta interf
|
|||||||
if tsgr == nil {
|
if tsgr == nil {
|
||||||
return diag.Errorf("nil role after setting grant strings")
|
return diag.Errorf("nil role after setting grant strings")
|
||||||
}
|
}
|
||||||
raw = tsgr.GetResponseMap()
|
raw = tsgr.GetResponse().Map
|
||||||
}
|
}
|
||||||
|
|
||||||
setFromRoleResponseMap(d, raw)
|
if err := setFromRoleResponseMap(d, raw); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -184,7 +199,7 @@ func resourceRoleRead(ctx context.Context, d *schema.ResourceData, meta interfac
|
|||||||
|
|
||||||
trr, err := rc.Read(ctx, d.Id())
|
trr, err := rc.Read(ctx, d.Id())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound {
|
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||||
d.SetId("")
|
d.SetId("")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -194,7 +209,9 @@ func resourceRoleRead(ctx context.Context, d *schema.ResourceData, meta interfac
|
|||||||
return diag.Errorf("role nil after read")
|
return diag.Errorf("role nil after read")
|
||||||
}
|
}
|
||||||
|
|
||||||
setFromRoleResponseMap(d, trr.GetResponseMap())
|
if err := setFromRoleResponseMap(d, trr.GetResponse().Map); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -247,13 +264,19 @@ func resourceRoleUpdate(ctx context.Context, d *schema.ResourceData, meta interf
|
|||||||
}
|
}
|
||||||
|
|
||||||
if d.HasChange(NameKey) {
|
if d.HasChange(NameKey) {
|
||||||
d.Set(NameKey, name)
|
if err := d.Set(NameKey, name); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if d.HasChange(DescriptionKey) {
|
if d.HasChange(DescriptionKey) {
|
||||||
d.Set(DescriptionKey, desc)
|
if err := d.Set(DescriptionKey, desc); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if d.HasChange(roleGrantScopeIdKey) {
|
if d.HasChange(roleGrantScopeIdKey) {
|
||||||
d.Set(roleGrantScopeIdKey, grantScopeId)
|
if err := d.Set(roleGrantScopeIdKey, grantScopeId); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.HasChange(roleGrantStringsKey) {
|
if d.HasChange(roleGrantStringsKey) {
|
||||||
@@ -268,7 +291,9 @@ func resourceRoleUpdate(ctx context.Context, d *schema.ResourceData, meta interf
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return diag.Errorf("error updating grant strings on role: %v", err)
|
return diag.Errorf("error updating grant strings on role: %v", err)
|
||||||
}
|
}
|
||||||
d.Set(roleGrantStringsKey, grantStrings)
|
if err := d.Set(roleGrantStringsKey, grantStrings); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.HasChange(rolePrincipalIdsKey) {
|
if d.HasChange(rolePrincipalIdsKey) {
|
||||||
@@ -283,7 +308,9 @@ func resourceRoleUpdate(ctx context.Context, d *schema.ResourceData, meta interf
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return diag.Errorf("error updating grant strings on role: %v", err)
|
return diag.Errorf("error updating grant strings on role: %v", err)
|
||||||
}
|
}
|
||||||
d.Set(rolePrincipalIdsKey, principalIds)
|
if err := d.Set(rolePrincipalIdsKey, principalIds); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ func testAccCheckRoleDestroyed(testProvider *schema.Provider, name string) resou
|
|||||||
rolesClient := roles.NewClient(md.client)
|
rolesClient := roles.NewClient(md.client)
|
||||||
|
|
||||||
_, err := rolesClient.Read(context.Background(), id)
|
_, err := rolesClient.Read(context.Background(), id)
|
||||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.ResponseStatus() != http.StatusNotFound {
|
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||||
errs = append(errs, fmt.Sprintf("Role not destroyed %q: %v", id, apiErr))
|
errs = append(errs, fmt.Sprintf("Role not destroyed %q: %v", id, apiErr))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,7 +546,7 @@ func testAccCheckRoleResourceDestroy(t *testing.T, testProvider *schema.Provider
|
|||||||
rolesClient := roles.NewClient(md.client)
|
rolesClient := roles.NewClient(md.client)
|
||||||
|
|
||||||
_, err := rolesClient.Read(context.Background(), id)
|
_, err := rolesClient.Read(context.Background(), id)
|
||||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.ResponseStatus() != http.StatusNotFound {
|
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)
|
return fmt.Errorf("didn't get a 404 when reading destroyed role %q: %v", id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,15 +69,25 @@ func resourceScope() *schema.Resource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setFromScopeResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
|
func setFromScopeResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
|
||||||
d.Set(NameKey, raw["name"])
|
if err := d.Set(NameKey, raw["name"]); err != nil {
|
||||||
d.Set(DescriptionKey, raw["description"])
|
return err
|
||||||
if d.Id() == "global" {
|
|
||||||
d.Set(ScopeIdKey, "global")
|
|
||||||
} else {
|
|
||||||
d.Set(ScopeIdKey, raw["scope_id"])
|
|
||||||
}
|
}
|
||||||
|
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if d.Id() == "global" {
|
||||||
|
if err := d.Set(ScopeIdKey, "global"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := d.Set(ScopeIdKey, raw["scope_id"]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
d.SetId(raw["id"].(string))
|
d.SetId(raw["id"].(string))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceScopeCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
func resourceScopeCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||||
@@ -136,7 +146,9 @@ func resourceScopeCreate(ctx context.Context, d *schema.ResourceData, meta inter
|
|||||||
return diag.Errorf("scope nil after create")
|
return diag.Errorf("scope nil after create")
|
||||||
}
|
}
|
||||||
|
|
||||||
setFromScopeResponseMap(d, scr.GetResponseMap())
|
if err := setFromScopeResponseMap(d, scr.GetResponse().Map); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -147,7 +159,7 @@ func resourceScopeRead(ctx context.Context, d *schema.ResourceData, meta interfa
|
|||||||
|
|
||||||
srr, err := scp.Read(ctx, d.Id())
|
srr, err := scp.Read(ctx, d.Id())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound {
|
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||||
d.SetId("")
|
d.SetId("")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -157,7 +169,9 @@ func resourceScopeRead(ctx context.Context, d *schema.ResourceData, meta interfa
|
|||||||
return diag.Errorf("scope nil after read")
|
return diag.Errorf("scope nil after read")
|
||||||
}
|
}
|
||||||
|
|
||||||
setFromScopeResponseMap(d, srr.GetResponseMap())
|
if err := setFromScopeResponseMap(d, srr.GetResponse().Map); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -199,10 +213,14 @@ func resourceScopeUpdate(ctx context.Context, d *schema.ResourceData, meta inter
|
|||||||
}
|
}
|
||||||
|
|
||||||
if d.HasChange(NameKey) {
|
if d.HasChange(NameKey) {
|
||||||
d.Set(NameKey, name)
|
if err := d.Set(NameKey, name); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if d.HasChange(DescriptionKey) {
|
if d.HasChange(DescriptionKey) {
|
||||||
d.Set(DescriptionKey, desc)
|
if err := d.Set(DescriptionKey, desc); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ func testAccCheckScopeResourceDestroy(t *testing.T, testProvider *schema.Provide
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_, err := scp.Read(context.Background(), id)
|
_, err := scp.Read(context.Background(), id)
|
||||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.ResponseStatus() != http.StatusNotFound {
|
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)
|
return fmt.Errorf("didn't get a 404 when reading destroyed resource %q: %w", id, err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -12,10 +12,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
targetHostSetIdsKey = "host_set_ids"
|
targetHostSourceIdsKey = "host_source_ids"
|
||||||
targetDefaultPortKey = "default_port"
|
targetApplicationCredentialSourceIdsKey = "application_credential_source_ids"
|
||||||
targetSessionMaxSecondsKey = "session_max_seconds"
|
targetDefaultPortKey = "default_port"
|
||||||
targetSessionConnectionLimitKey = "session_connection_limit"
|
targetSessionMaxSecondsKey = "session_max_seconds"
|
||||||
|
targetSessionConnectionLimitKey = "session_connection_limit"
|
||||||
|
targetWorkerFilterKey = "worker_filter"
|
||||||
|
|
||||||
targetTypeTcp = "tcp"
|
targetTypeTcp = "tcp"
|
||||||
)
|
)
|
||||||
@@ -65,8 +67,14 @@ func resourceTarget() *schema.Resource {
|
|||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
targetHostSetIdsKey: {
|
targetHostSourceIdsKey: {
|
||||||
Description: "A list of host set ID's.",
|
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.",
|
||||||
Type: schema.TypeSet,
|
Type: schema.TypeSet,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Elem: &schema.Schema{Type: schema.TypeString},
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
@@ -81,18 +89,43 @@ func resourceTarget() *schema.Resource {
|
|||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: 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{}) {
|
func setFromTargetResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
|
||||||
d.Set(NameKey, raw["name"])
|
if err := d.Set(NameKey, raw["name"]); err != nil {
|
||||||
d.Set(DescriptionKey, raw["description"])
|
return err
|
||||||
d.Set(ScopeIdKey, raw["scope_id"])
|
}
|
||||||
d.Set(TypeKey, raw["type"])
|
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
|
||||||
d.Set(targetHostSetIdsKey, raw["host_set_ids"])
|
return err
|
||||||
d.Set(targetSessionMaxSecondsKey, raw["session_max_seconds"])
|
}
|
||||||
d.Set(targetSessionConnectionLimitKey, raw["session_connection_limit"])
|
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
|
||||||
|
}
|
||||||
|
|
||||||
switch raw["type"].(string) {
|
switch raw["type"].(string) {
|
||||||
case targetTypeTcp:
|
case targetTypeTcp:
|
||||||
@@ -100,12 +133,15 @@ func setFromTargetResponseMap(d *schema.ResourceData, raw map[string]interface{}
|
|||||||
attrs := attrsVal.(map[string]interface{})
|
attrs := attrsVal.(map[string]interface{})
|
||||||
if defPort, ok := attrs["default_port"].(json.Number); ok {
|
if defPort, ok := attrs["default_port"].(json.Number); ok {
|
||||||
defPortInt, _ := defPort.Int64()
|
defPortInt, _ := defPort.Int64()
|
||||||
d.Set(targetDefaultPortKey, int(defPortInt))
|
if err := d.Set(targetDefaultPortKey, int(defPortInt)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d.SetId(raw["id"].(string))
|
d.SetId(raw["id"].(string))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceTargetCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
func resourceTargetCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||||
@@ -130,8 +166,7 @@ func resourceTargetCreate(ctx context.Context, d *schema.ResourceData, meta inte
|
|||||||
return diag.Errorf("invalid type provided")
|
return diag.Errorf("invalid type provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := []targets.Option{}
|
var opts []targets.Option
|
||||||
|
|
||||||
nameVal, ok := d.GetOk(NameKey)
|
nameVal, ok := d.GetOk(NameKey)
|
||||||
if ok {
|
if ok {
|
||||||
nameStr := nameVal.(string)
|
nameStr := nameVal.(string)
|
||||||
@@ -171,15 +206,30 @@ func resourceTargetCreate(ctx context.Context, d *schema.ResourceData, meta inte
|
|||||||
opts = append(opts, targets.WithSessionConnectionLimit(int32(sessionConnectionLimitInt)))
|
opts = append(opts, targets.WithSessionConnectionLimit(int32(sessionConnectionLimitInt)))
|
||||||
}
|
}
|
||||||
|
|
||||||
var hostSetIds []string
|
var hostSourceIds []string
|
||||||
if hostSetIdsVal, ok := d.GetOk(targetHostSetIdsKey); ok {
|
if hostSourceIdsVal, ok := d.GetOk(targetHostSourceIdsKey); ok {
|
||||||
list := hostSetIdsVal.(*schema.Set).List()
|
list := hostSourceIdsVal.(*schema.Set).List()
|
||||||
hostSetIds = make([]string, 0, len(list))
|
hostSourceIds = make([]string, 0, len(list))
|
||||||
for _, i := range list {
|
for _, i := range list {
|
||||||
hostSetIds = append(hostSetIds, i.(string))
|
hostSourceIds = append(hostSourceIds, 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)
|
tc := targets.NewClient(md.client)
|
||||||
|
|
||||||
tcr, err := tc.Create(ctx, typeStr, scopeId, opts...)
|
tcr, err := tc.Create(ctx, typeStr, scopeId, opts...)
|
||||||
@@ -189,17 +239,29 @@ func resourceTargetCreate(ctx context.Context, d *schema.ResourceData, meta inte
|
|||||||
if tcr == nil {
|
if tcr == nil {
|
||||||
return diag.Errorf("target nil after create")
|
return diag.Errorf("target nil after create")
|
||||||
}
|
}
|
||||||
raw := tcr.GetResponseMap()
|
raw := tcr.GetResponse().Map
|
||||||
|
|
||||||
if hostSetIds != nil {
|
version := tcr.Item.Version
|
||||||
tur, err := tc.SetHostSets(ctx, tcr.Item.Id, tcr.Item.Version, hostSetIds)
|
if hostSourceIds != nil {
|
||||||
|
tur, err := tc.SetHostSources(ctx, tcr.Item.Id, version, hostSourceIds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return diag.Errorf("error setting host sets on target: %v", err)
|
return diag.Errorf("error setting host sources on target: %v", err)
|
||||||
}
|
}
|
||||||
raw = tur.GetResponseMap()
|
raw = tur.GetResponse().Map
|
||||||
|
version = tur.Item.Version
|
||||||
}
|
}
|
||||||
|
|
||||||
setFromTargetResponseMap(d, raw)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -210,7 +272,7 @@ func resourceTargetRead(ctx context.Context, d *schema.ResourceData, meta interf
|
|||||||
|
|
||||||
trr, err := tc.Read(ctx, d.Id())
|
trr, err := tc.Read(ctx, d.Id())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound {
|
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||||
d.SetId("")
|
d.SetId("")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -220,7 +282,9 @@ func resourceTargetRead(ctx context.Context, d *schema.ResourceData, meta interf
|
|||||||
return diag.Errorf("target nil after read")
|
return diag.Errorf("target nil after read")
|
||||||
}
|
}
|
||||||
|
|
||||||
setFromTargetResponseMap(d, trr.GetResponseMap())
|
if err := setFromTargetResponseMap(d, trr.GetResponse().Map); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -229,7 +293,7 @@ func resourceTargetUpdate(ctx context.Context, d *schema.ResourceData, meta inte
|
|||||||
md := meta.(*metaData)
|
md := meta.(*metaData)
|
||||||
tc := targets.NewClient(md.client)
|
tc := targets.NewClient(md.client)
|
||||||
|
|
||||||
opts := []targets.Option{}
|
var opts []targets.Option
|
||||||
|
|
||||||
var name *string
|
var name *string
|
||||||
if d.HasChange(NameKey) {
|
if d.HasChange(NameKey) {
|
||||||
@@ -295,6 +359,17 @@ 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 {
|
if len(opts) > 0 {
|
||||||
opts = append(opts, targets.WithAutomaticVersioning(true))
|
opts = append(opts, targets.WithAutomaticVersioning(true))
|
||||||
_, err := tc.Update(ctx, d.Id(), 0, opts...)
|
_, err := tc.Update(ctx, d.Id(), 0, opts...)
|
||||||
@@ -304,36 +379,81 @@ func resourceTargetUpdate(ctx context.Context, d *schema.ResourceData, meta inte
|
|||||||
}
|
}
|
||||||
|
|
||||||
if d.HasChange(NameKey) {
|
if d.HasChange(NameKey) {
|
||||||
d.Set(NameKey, name)
|
if err := d.Set(NameKey, name); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if d.HasChange(DescriptionKey) {
|
if d.HasChange(DescriptionKey) {
|
||||||
d.Set(DescriptionKey, desc)
|
if err := d.Set(DescriptionKey, desc); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if d.HasChange(targetDefaultPortKey) {
|
if d.HasChange(targetDefaultPortKey) {
|
||||||
d.Set(targetDefaultPortKey, defaultPort)
|
if err := d.Set(targetDefaultPortKey, defaultPort); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if d.HasChange(targetSessionMaxSecondsKey) {
|
if d.HasChange(targetSessionMaxSecondsKey) {
|
||||||
d.Set(targetSessionMaxSecondsKey, sessionMaxSeconds)
|
if err := d.Set(targetSessionMaxSecondsKey, sessionMaxSeconds); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if d.HasChange(targetSessionConnectionLimitKey) {
|
if d.HasChange(targetSessionConnectionLimitKey) {
|
||||||
d.Set(targetSessionConnectionLimitKey, sessionConnectionLimit)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The above call may not actually happen, so we use d.Id() and automatic
|
// The above call may not actually happen, so we use d.Id() and automatic
|
||||||
// versioning here
|
// versioning here
|
||||||
if d.HasChange(targetHostSetIdsKey) {
|
if d.HasChange(targetHostSourceIdsKey) {
|
||||||
var hostSetIds []string
|
var hostSourceIds []string
|
||||||
if hostSetIdsVal, ok := d.GetOk(targetHostSetIdsKey); ok {
|
if hostSourceIdsVal, ok := d.GetOk(targetHostSourceIdsKey); ok {
|
||||||
hostSets := hostSetIdsVal.(*schema.Set).List()
|
hostSources := hostSourceIdsVal.(*schema.Set).List()
|
||||||
for _, hostSet := range hostSets {
|
for _, hostSource := range hostSources {
|
||||||
hostSetIds = append(hostSetIds, hostSet.(string))
|
hostSourceIds = append(hostSourceIds, hostSource.(string))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err := tc.SetHostSets(ctx, d.Id(), 0, hostSetIds, targets.WithAutomaticVersioning(true))
|
_, err := tc.SetHostSources(ctx, d.Id(), 0, hostSourceIds, targets.WithAutomaticVersioning(true))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return diag.Errorf("error updating host sets in target: %v", err)
|
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)
|
||||||
}
|
}
|
||||||
d.Set(targetHostSetIdsKey, hostSetIds)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/hashicorp/boundary/api"
|
"github.com/hashicorp/boundary/api"
|
||||||
"github.com/hashicorp/boundary/api/targets"
|
"github.com/hashicorp/boundary/api/targets"
|
||||||
"github.com/hashicorp/boundary/testing/controller"
|
"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/resource"
|
||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
||||||
@@ -20,7 +21,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
fooHostSet = `
|
fooBarHostSet = `
|
||||||
resource "boundary_host_catalog" "foo" {
|
resource "boundary_host_catalog" "foo" {
|
||||||
type = "static"
|
type = "static"
|
||||||
name = "test"
|
name = "test"
|
||||||
@@ -49,23 +50,53 @@ resource "boundary_host_set" "foo" {
|
|||||||
host_catalog_id = boundary_host_catalog.foo.id
|
host_catalog_id = boundary_host_catalog.foo.id
|
||||||
host_ids = [
|
host_ids = [
|
||||||
boundary_host.foo.id,
|
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,
|
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(`
|
fooTarget = fmt.Sprintf(`
|
||||||
resource "boundary_target" "foo" {
|
resource "boundary_target" "foo" {
|
||||||
name = "test"
|
name = "test"
|
||||||
description = "%s"
|
description = "%s"
|
||||||
type = "tcp"
|
type = "tcp"
|
||||||
scope_id = boundary_scope.proj1.id
|
scope_id = boundary_scope.proj1.id
|
||||||
host_set_ids = [
|
host_source_ids = [
|
||||||
boundary_host_set.foo.id
|
boundary_host_set.foo.id
|
||||||
]
|
]
|
||||||
|
application_credential_source_ids = [
|
||||||
|
boundary_credential_library_vault.foo.id
|
||||||
|
]
|
||||||
default_port = 22
|
default_port = 22
|
||||||
depends_on = [boundary_role.proj1_admin]
|
depends_on = [boundary_role.proj1_admin]
|
||||||
session_max_seconds = 6000
|
session_max_seconds = 6000
|
||||||
session_connection_limit = 6
|
session_connection_limit = 6
|
||||||
|
worker_filter = "type == \"foo\""
|
||||||
}`, fooTargetDescription)
|
}`, fooTargetDescription)
|
||||||
|
|
||||||
fooTargetUpdate = fmt.Sprintf(`
|
fooTargetUpdate = fmt.Sprintf(`
|
||||||
@@ -74,13 +105,30 @@ resource "boundary_target" "foo" {
|
|||||||
description = "%s"
|
description = "%s"
|
||||||
type = "tcp"
|
type = "tcp"
|
||||||
scope_id = boundary_scope.proj1.id
|
scope_id = boundary_scope.proj1.id
|
||||||
host_set_ids = [
|
host_source_ids = [
|
||||||
boundary_host_set.foo.id
|
boundary_host_set.bar.id
|
||||||
|
]
|
||||||
|
application_credential_source_ids = [
|
||||||
|
boundary_credential_library_vault.bar.id
|
||||||
]
|
]
|
||||||
default_port = 80
|
default_port = 80
|
||||||
depends_on = [boundary_role.proj1_admin]
|
depends_on = [boundary_role.proj1_admin]
|
||||||
session_max_seconds = 7000
|
session_max_seconds = 7000
|
||||||
session_connection_limit = 7
|
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)
|
}`, fooTargetDescriptionUpdate)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -90,14 +138,25 @@ func TestAccTarget(t *testing.T) {
|
|||||||
defer tc.Shutdown()
|
defer tc.Shutdown()
|
||||||
url := tc.ApiAddrs()[0]
|
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
|
var provider *schema.Provider
|
||||||
resource.Test(t, resource.TestCase{
|
resource.Test(t, resource.TestCase{
|
||||||
|
IsUnitTest: true,
|
||||||
ProviderFactories: providerFactories(&provider),
|
ProviderFactories: providerFactories(&provider),
|
||||||
CheckDestroy: testAccCheckTargetResourceDestroy(t, provider),
|
CheckDestroy: testAccCheckTargetResourceDestroy(t, provider),
|
||||||
Steps: []resource.TestStep{
|
Steps: []resource.TestStep{
|
||||||
{
|
{
|
||||||
// test create
|
// test create
|
||||||
Config: testConfig(url, fooOrg, firstProjectFoo, fooHostSet, fooTarget),
|
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, fooBarCredLibs, fooBarHostSet, fooTarget),
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckTargetResourceExists(provider, "boundary_target.foo"),
|
testAccCheckTargetResourceExists(provider, "boundary_target.foo"),
|
||||||
resource.TestCheckResourceAttr("boundary_target.foo", DescriptionKey, fooTargetDescription),
|
resource.TestCheckResourceAttr("boundary_target.foo", DescriptionKey, fooTargetDescription),
|
||||||
@@ -105,18 +164,39 @@ func TestAccTarget(t *testing.T) {
|
|||||||
resource.TestCheckResourceAttr("boundary_target.foo", targetDefaultPortKey, "22"),
|
resource.TestCheckResourceAttr("boundary_target.foo", targetDefaultPortKey, "22"),
|
||||||
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionMaxSecondsKey, "6000"),
|
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionMaxSecondsKey, "6000"),
|
||||||
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionConnectionLimitKey, "6"),
|
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"),
|
importStep("boundary_target.foo"),
|
||||||
{
|
{
|
||||||
// test update
|
// test update
|
||||||
Config: testConfig(url, fooOrg, firstProjectFoo, fooHostSet, fooTargetUpdate),
|
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, fooBarCredLibs, fooBarHostSet, fooTargetUpdate),
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
testAccCheckTargetResourceExists(provider, "boundary_target.foo"),
|
testAccCheckTargetResourceExists(provider, "boundary_target.foo"),
|
||||||
resource.TestCheckResourceAttr("boundary_target.foo", DescriptionKey, fooTargetDescriptionUpdate),
|
resource.TestCheckResourceAttr("boundary_target.foo", DescriptionKey, fooTargetDescriptionUpdate),
|
||||||
resource.TestCheckResourceAttr("boundary_target.foo", targetDefaultPortKey, "80"),
|
resource.TestCheckResourceAttr("boundary_target.foo", targetDefaultPortKey, "80"),
|
||||||
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionMaxSecondsKey, "7000"),
|
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionMaxSecondsKey, "7000"),
|
||||||
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionConnectionLimitKey, "7"),
|
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"),
|
importStep("boundary_target.foo"),
|
||||||
@@ -124,7 +204,7 @@ func TestAccTarget(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAccCheckTargetResourceMembersSet(testProvider *schema.Provider, name string, hostSets []string) resource.TestCheckFunc {
|
func testAccCheckTargetResourceHostSource(testProvider *schema.Provider, name string, hostSources []string) resource.TestCheckFunc {
|
||||||
return func(s *terraform.State) error {
|
return func(s *terraform.State) error {
|
||||||
rs, ok := s.RootModule().Resources[name]
|
rs, ok := s.RootModule().Resources[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -136,20 +216,20 @@ func testAccCheckTargetResourceMembersSet(testProvider *schema.Provider, name st
|
|||||||
return fmt.Errorf("target resource ID is not set")
|
return fmt.Errorf("target resource ID is not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure host sets are declared in state
|
// ensure host sources are declared in state
|
||||||
hostSetIDs := []string{}
|
var hostSourceIDs []string
|
||||||
for _, hostSetResourceID := range hostSets {
|
for _, hostSourceResourceID := range hostSources {
|
||||||
hs, ok := s.RootModule().Resources[hostSetResourceID]
|
hs, ok := s.RootModule().Resources[hostSourceResourceID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("host set resource not found: %s", hostSetResourceID)
|
return fmt.Errorf("host source resource not found: %s", hostSourceResourceID)
|
||||||
}
|
}
|
||||||
|
|
||||||
hostSetID := hs.Primary.ID
|
hostSourceID := hs.Primary.ID
|
||||||
if id == "" {
|
if id == "" {
|
||||||
return fmt.Errorf("host set resource ID not set")
|
return fmt.Errorf("host source resource ID not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
hostSetIDs = append(hostSetIDs, hostSetID)
|
hostSourceIDs = append(hostSourceIDs, hostSourceID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check boundary to ensure it matches
|
// check boundary to ensure it matches
|
||||||
@@ -161,19 +241,76 @@ func testAccCheckTargetResourceMembersSet(testProvider *schema.Provider, name st
|
|||||||
return fmt.Errorf("Got an error when reading target %q: %v", id, err)
|
return fmt.Errorf("Got an error when reading target %q: %v", id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(t.Item.HostSetIds) == 0 {
|
if len(t.Item.HostSourceIds) != len(hostSourceIDs) {
|
||||||
return fmt.Errorf("no hostSets found on target")
|
return fmt.Errorf("tf state and boundary have different number of host sources")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, stateHostSet := range t.Item.HostSetIds {
|
for _, stateHostSourceId := range t.Item.HostSourceIds {
|
||||||
ok := false
|
ok := false
|
||||||
for _, gotHostSetID := range hostSetIDs {
|
for _, gotHostSourceID := range hostSourceIDs {
|
||||||
if gotHostSetID == stateHostSet {
|
if gotHostSourceID == stateHostSourceId {
|
||||||
ok = true
|
ok = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("host set in state not set in boundary: %s", stateHostSet)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,7 +357,7 @@ func testAccCheckTargetResourceDestroy(t *testing.T, testProvider *schema.Provid
|
|||||||
id := rs.Primary.ID
|
id := rs.Primary.ID
|
||||||
|
|
||||||
_, err := tgts.Read(context.Background(), id)
|
_, err := tgts.Read(context.Background(), id)
|
||||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.ResponseStatus() != http.StatusNotFound {
|
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)
|
return fmt.Errorf("didn't get a 404 when reading destroyed target %q: %v", id, apiErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,12 +56,21 @@ func resourceUser() *schema.Resource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setFromUserResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
|
func setFromUserResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
|
||||||
d.Set(NameKey, raw["name"])
|
if err := d.Set(NameKey, raw["name"]); err != nil {
|
||||||
d.Set(DescriptionKey, raw["description"])
|
return err
|
||||||
d.Set(ScopeIdKey, raw["scope_id"])
|
}
|
||||||
d.Set(userAccountIDsKey, raw["account_ids"])
|
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
|
||||||
|
}
|
||||||
d.SetId(raw["id"].(string))
|
d.SetId(raw["id"].(string))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||||
@@ -97,7 +106,7 @@ func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interf
|
|||||||
if ucr == nil {
|
if ucr == nil {
|
||||||
return diag.Errorf("user nil after create")
|
return diag.Errorf("user nil after create")
|
||||||
}
|
}
|
||||||
raw := ucr.GetResponseMap()
|
raw := ucr.GetResponse().Map
|
||||||
|
|
||||||
if val, ok := d.GetOk(userAccountIDsKey); ok {
|
if val, ok := d.GetOk(userAccountIDsKey); ok {
|
||||||
list := val.(*schema.Set).List()
|
list := val.(*schema.Set).List()
|
||||||
@@ -112,10 +121,12 @@ func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interf
|
|||||||
if usrac == nil {
|
if usrac == nil {
|
||||||
return diag.Errorf("user nil after setting accounts")
|
return diag.Errorf("user nil after setting accounts")
|
||||||
}
|
}
|
||||||
raw = usrac.GetResponseMap()
|
raw = usrac.GetResponse().Map
|
||||||
}
|
}
|
||||||
|
|
||||||
setFromUserResponseMap(d, raw)
|
if err := setFromUserResponseMap(d, raw); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -126,7 +137,7 @@ func resourceUserRead(ctx context.Context, d *schema.ResourceData, meta interfac
|
|||||||
|
|
||||||
urr, err := usrs.Read(ctx, d.Id())
|
urr, err := usrs.Read(ctx, d.Id())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound{
|
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||||
d.SetId("")
|
d.SetId("")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -136,7 +147,9 @@ func resourceUserRead(ctx context.Context, d *schema.ResourceData, meta interfac
|
|||||||
return diag.Errorf("user nil after read")
|
return diag.Errorf("user nil after read")
|
||||||
}
|
}
|
||||||
|
|
||||||
setFromUserResponseMap(d, urr.GetResponseMap())
|
if err := setFromUserResponseMap(d, urr.GetResponse().Map); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -178,10 +191,14 @@ func resourceUserUpdate(ctx context.Context, d *schema.ResourceData, meta interf
|
|||||||
}
|
}
|
||||||
|
|
||||||
if d.HasChange(NameKey) {
|
if d.HasChange(NameKey) {
|
||||||
d.Set(NameKey, name)
|
if err := d.Set(NameKey, name); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if d.HasChange(DescriptionKey) {
|
if d.HasChange(DescriptionKey) {
|
||||||
d.Set(DescriptionKey, desc)
|
if err := d.Set(DescriptionKey, desc); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.HasChange(userAccountIDsKey) {
|
if d.HasChange(userAccountIDsKey) {
|
||||||
@@ -197,7 +214,9 @@ func resourceUserUpdate(ctx context.Context, d *schema.ResourceData, meta interf
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return diag.Errorf("error updating accounts on user: %v", err)
|
return diag.Errorf("error updating accounts on user: %v", err)
|
||||||
}
|
}
|
||||||
d.Set(userAccountIDsKey, accountIds)
|
if err := d.Set(userAccountIDsKey, accountIds); err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ func testAccCheckUserResourceDestroy(t *testing.T, testProvider *schema.Provider
|
|||||||
usrs := users.NewClient(md.client)
|
usrs := users.NewClient(md.client)
|
||||||
|
|
||||||
_, err := usrs.Read(context.Background(), id)
|
_, err := usrs.Read(context.Background(), id)
|
||||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.ResponseStatus() != http.StatusNotFound {
|
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)
|
return fmt.Errorf("didn't get a 404 when reading destroyed user %q: %v", id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user