Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
622fca1c0e | ||
|
|
8aa93ccb69 | ||
|
|
1b96f89728 | ||
|
|
e90db9895c | ||
|
|
7ae01cc6e0 | ||
|
|
a134747539 | ||
|
|
893b150ec9 | ||
|
|
a61a13193c | ||
|
|
292204caf0 | ||
|
|
a2e04949be | ||
|
|
8fad44cc94 | ||
|
|
e0fb0a44e0 | ||
|
|
767a9ce1e6 | ||
|
|
f1d8cbf6e6 | ||
|
|
072c046b67 | ||
|
|
82ae1e3cd7 | ||
|
|
14bc6a4453 | ||
|
|
dc3aa09688 | ||
|
|
d6fb6d4c16 | ||
|
|
7164883b1b | ||
|
|
7f600f1cd5 | ||
|
|
145c91305c | ||
|
|
deb02086e3 | ||
|
|
445398286f | ||
|
|
66aad543f6 | ||
|
|
b491b4d123 | ||
|
|
bf34f12c56 | ||
|
|
0f4126d7d0 | ||
|
|
41a58f4db5 | ||
|
|
56f858006e | ||
|
|
48672fd050 | ||
|
|
5859c55f43 | ||
|
|
fe704abbfc | ||
|
|
e734090217 | ||
|
|
f9b5452b84 | ||
|
|
531dfa486a | ||
|
|
0bbc96fc11 | ||
|
|
f88fe43c12 | ||
|
|
48694a6bab | ||
|
|
d9d4941e0c | ||
|
|
6a704b2505 | ||
|
|
55089ad19b | ||
|
|
d8ab0733e8 | ||
|
|
9acb1b6f7f | ||
|
|
e6c8309c17 | ||
|
|
84bea9c333 | ||
|
|
8422393315 | ||
|
|
b8c92ee913 | ||
|
|
23c8315fcc | ||
|
|
b17b46d3ab | ||
|
|
414cdff86e | ||
|
|
dfaea8e259 | ||
|
|
1d345d18ac | ||
|
|
577b9eb017 | ||
|
|
e92a34b624 | ||
|
|
93d925fcfd | ||
|
|
214bc5224f | ||
|
|
98a2d617f2 | ||
|
|
cbcbdc9955 | ||
|
|
02445d2192 | ||
|
|
123284e88f | ||
|
|
6b28ea22a8 | ||
|
|
649b6a54f5 | ||
|
|
93471e3b28 | ||
|
|
452fd1c764 | ||
|
|
cfe7d47286 | ||
|
|
af98aaa9b2 | ||
|
|
10b421cec8 | ||
|
|
98d768f8c8 | ||
|
|
c869dfb367 | ||
|
|
5f4fa20ca8 | ||
|
|
6374cb307b | ||
|
|
9c054c1ac8 | ||
|
|
725f2d8fb9 | ||
|
|
7372487f84 | ||
|
|
f202fbc06d | ||
|
|
8bb1490ebd | ||
|
|
45c1a8c5cc |
No files matched your search
+10
-1
@@ -1,8 +1,9 @@
|
||||
version: 2.1
|
||||
jobs:
|
||||
build:
|
||||
resource_class: large
|
||||
machine:
|
||||
image: 'ubuntu-1604:201903-01'
|
||||
image: 'ubuntu-2004:202107-02'
|
||||
working_directory: ~/terraform-provider-watchtower
|
||||
steps:
|
||||
- add_ssh_keys:
|
||||
@@ -10,7 +11,15 @@ jobs:
|
||||
# hc-github-team-secure-boundary github user,TF_ACC=1 ~/.go/bin/go test ./... -v $(TESTARGS) -timeout 120m to read private modules
|
||||
- "d4:7f:b2:f7:6f:4d:e7:21:cc:d2:b7:6a:8b:68:e6:d4"
|
||||
- checkout
|
||||
- run:
|
||||
name: "Install go"
|
||||
command: |
|
||||
unset GOPATH # ci image has done strange things with GOPATH, unset it so install-go can fix it
|
||||
make install-go
|
||||
source ~/.bashrc
|
||||
go mod download
|
||||
- run:
|
||||
name: "Run Acceptance Tests"
|
||||
command: |
|
||||
source ~/.bashrc
|
||||
make testacc-ci
|
||||
@@ -0,0 +1,51 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
go-version:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.go-version.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- id: go-version
|
||||
run: echo "::set-output name=version::$(cat ./.go-version)"
|
||||
release-notes:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Generate Release Notes
|
||||
run: sed -n -e "1{/# /d;}" -e "2{/^$/d;}" -e "/# $(git describe --abbrev=0 --exclude="$(git describe --abbrev=0 --match='v*.*.*' --tags)" --match='v*.*.*' --tags | tr -d v)/q;p" CHANGELOG.md > release-notes.txt
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: release-notes
|
||||
path: release-notes.txt
|
||||
retention-days: 1
|
||||
terraform-provider-release:
|
||||
name: 'Terraform Provider Release'
|
||||
needs: [go-version, release-notes]
|
||||
uses: hashicorp/ghaction-terraform-provider-release/.github/workflows/hashicorp.yml@v2
|
||||
secrets:
|
||||
hc-releases-github-token: '${{ secrets.HASHI_RELEASES_GITHUB_TOKEN }}'
|
||||
hc-releases-host-staging: '${{ secrets.HC_RELEASES_HOST_STAGING }}'
|
||||
hc-releases-host-prod: '${{ secrets.HC_RELEASES_HOST_PROD }}'
|
||||
hc-releases-key-prod: '${{ secrets.HC_RELEASES_KEY_PROD }}'
|
||||
hc-releases-key-staging: '${{ secrets.HC_RELEASES_KEY_STAGING }}'
|
||||
hc-releases-terraform-registry-sync-token: '${{ secrets.TF_PROVIDER_RELEASE_TERRAFORM_REGISTRY_SYNC_TOKEN }}'
|
||||
setup-signore-github-token: '${{ secrets.HASHI_SIGNORE_GITHUB_TOKEN }}'
|
||||
signore-client-id: '${{ secrets.SIGNORE_CLIENT_ID }}'
|
||||
signore-client-secret: '${{ secrets.SIGNORE_CLIENT_SECRET }}'
|
||||
with:
|
||||
release-notes: true
|
||||
setup-go-version: '${{ needs.go-version.outputs.version }}'
|
||||
# Product Version (e.g. v1.2.3 or github.ref_name)
|
||||
product-version: '${{ github.ref_name }}'
|
||||
@@ -33,3 +33,6 @@ website/vendor
|
||||
|
||||
# Keep windows files with windows line endings
|
||||
*.winfile eol=crlf
|
||||
|
||||
# Compilation outputs
|
||||
/plugins/kms/assets/*/*/boundary-plugin*
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.16.3
|
||||
1.19.1
|
||||
@@ -0,0 +1,75 @@
|
||||
archives:
|
||||
- files:
|
||||
# Ensure only built binary is archived
|
||||
- 'none*'
|
||||
format: zip
|
||||
name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}'
|
||||
before:
|
||||
hooks:
|
||||
- 'go mod download'
|
||||
- './scripts/plugins-all.sh'
|
||||
builds:
|
||||
- # Binary naming only required for Terraform CLI 0.12
|
||||
binary: '{{ .ProjectName }}_v{{ .Version }}_x5'
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
flags:
|
||||
- -trimpath
|
||||
goos:
|
||||
- darwin
|
||||
- freebsd
|
||||
- linux
|
||||
- windows
|
||||
goarch:
|
||||
- '386'
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
ignore:
|
||||
- goarch: arm
|
||||
goos: windows
|
||||
- goarch: arm64
|
||||
goos: freebsd
|
||||
- goarch: arm64
|
||||
goos: windows
|
||||
ldflags:
|
||||
- -s -w -X main.Version={{.Version}}
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
checksum:
|
||||
algorithm: sha256
|
||||
extra_files:
|
||||
- glob: 'terraform-registry-manifest.json'
|
||||
name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json'
|
||||
name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS'
|
||||
publishers:
|
||||
- checksum: true
|
||||
# Terraform CLI 0.10 - 0.11 perform discovery via HTTP headers on releases.hashicorp.com
|
||||
# For providers which have existed since those CLI versions, exclude
|
||||
# discovery by setting the protocol version headers to 5.
|
||||
cmd: hc-releases upload -product {{ .ProjectName }} -version {{ .Version }} -file={{ .ArtifactPath }}={{ .ArtifactName }} -header=x-terraform-protocol-version=5 -header=x-terraform-protocol-versions=5.0
|
||||
env:
|
||||
- HC_RELEASES_HOST={{ .Env.HC_RELEASES_HOST }}
|
||||
- HC_RELEASES_KEY={{ .Env.HC_RELEASES_KEY }}
|
||||
extra_files:
|
||||
- glob: 'terraform-registry-manifest.json'
|
||||
name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json'
|
||||
name: upload
|
||||
signature: true
|
||||
release:
|
||||
extra_files:
|
||||
- glob: 'terraform-registry-manifest.json'
|
||||
name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json'
|
||||
ids:
|
||||
- none
|
||||
signs:
|
||||
- args: ["sign", "--dearmor", "--file", "${artifact}", "--out", "${signature}"]
|
||||
artifacts: checksum
|
||||
cmd: signore
|
||||
signature: ${artifact}.sig
|
||||
- args: ["sign", "--dearmor", "--file", "${artifact}", "--out", "${signature}"]
|
||||
artifacts: checksum
|
||||
cmd: signore
|
||||
id: key-id
|
||||
signature: ${artifact}.72D7468F.sig
|
||||
snapshot:
|
||||
name_template: "{{ .Tag }}-next"
|
||||
@@ -0,0 +1,2 @@
|
||||
url_source_repository = "https://github.com/hashicorp/terraform-provider-boundary"
|
||||
url_license = "https://github.com/hashicorp/terraform-provider-boundary/blob/main/LICENSE"
|
||||
+112
@@ -1,3 +1,115 @@
|
||||
# Boundary Terraform Provider CHANGELOG
|
||||
|
||||
Canonical reference for changes, improvements, and bugfixes for the Boundary Terraform provider.
|
||||
|
||||
## 1.1.0 (October 4, 2022)
|
||||
|
||||
### New and Improved
|
||||
|
||||
* Add support for JSON credentials
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/271)).
|
||||
* Add support for setting the plugin execution directory from the config
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/280)).
|
||||
|
||||
### Deprecations/Changes
|
||||
|
||||
* Fix panic resulting from expired Vault credential store tokens
|
||||
([Issue](https://github.com/hashicorp/terraform-provider-boundary/issues/279),
|
||||
[PR](https://github.com/hashicorp/terraform-provider-boundary/pull/277)).
|
||||
* Remove `application_credential_source_ids` of the `target` resource which was deprecated
|
||||
in 1.0.12 ([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/273)).
|
||||
* Remove `default_role` from the `role` resource, this schema was never supported and was
|
||||
included mistakenly ([Issue](https://github.com/hashicorp/terraform-provider-boundary/issues/130),
|
||||
[PR](https://github.com/hashicorp/terraform-provider-boundary/pull/269)).
|
||||
|
||||
## 1.0.12 (September 13, 2022)
|
||||
|
||||
### New and Improved
|
||||
|
||||
* Add support for SSH targets
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/264)).
|
||||
|
||||
### Deprecations/Changes
|
||||
|
||||
* Deprecate `application_credential_source_ids` of the `target` resource
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/260)).
|
||||
|
||||
## 1.0.11 (August 26, 2022)
|
||||
|
||||
### New and Improved
|
||||
|
||||
* Add support for SSH private key credentials
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/257)).
|
||||
* Add support for credential type in Vault libraries
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/257)).
|
||||
|
||||
## 1.0.10 (August 10, 2022)
|
||||
|
||||
### New and Improved
|
||||
|
||||
* Adds support for static credential stores
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/236)).
|
||||
* Adds support for username password credentials
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/242)).
|
||||
|
||||
## 1.0.9 (June 6, 2022)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* The bug fix released in 1.0.8 to resolve the `plugin is nil` error only worked for
|
||||
Linux AMD64. This was due to a build issue where the plugin binaries were only built for
|
||||
Linux AMD64. Other platforms would receive an error similar to:
|
||||
|
||||
Error: error reading wrappers from "recovery_kms_hcl":
|
||||
Error configuring kms: error fetching kms plugin rpc client:
|
||||
fork/exec boundary-plugin-kms-awskms.gz: exec format error
|
||||
([Issue](https://github.com/hashicorp/terraform-provider-boundary/issues/209)),
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/216)).
|
||||
|
||||
## 1.0.8 (June 1, 2022)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* After moving to go-kms-wrapping V2, the Boundary Terraform Provider
|
||||
did not load all KMS plugins resulting in an error when trying to
|
||||
create a wrapper for any type other than 'aead':
|
||||
|
||||
Error: error reading wrappers from "recovery_kms_hcl":
|
||||
Error configuring kms: plugin is nil
|
||||
([Issue](https://github.com/hashicorp/terraform-provider-boundary/issues/209)),
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/213)).
|
||||
|
||||
## 1.0.7 (May 16, 2022)
|
||||
|
||||
### Deprecations/Changes
|
||||
|
||||
* Undoes an erroneous deprecation of the `login_name` and `password` fields in `resource_account_password` and `resource_account`.
|
||||
Deprecates `resource_account` that was replaced with `resource_account_password`
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/201)).
|
||||
|
||||
## 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
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.16
|
||||
FROM golang:1.19
|
||||
|
||||
WORKDIR /go/src/terraform-provider-boundary
|
||||
COPY . .
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
default: testacc
|
||||
default: testacc
|
||||
GOOS=$(shell go env GOOS)
|
||||
GOARCH=$(shell go env GOARCH)
|
||||
INSTALL_PATH=~/.local/share/terraform/plugins/localhost/providers/boundary/0.0.1/linux_$(GOARCH)
|
||||
@@ -11,6 +11,16 @@ ifeq ($(GOOS), "windows")
|
||||
INSTALL_PATH=%APPDATA%/HashiCorp/Terraform/plugins/localhost/providers/boundary/0.0.1/windows_$(GOARCH)
|
||||
endif
|
||||
|
||||
REGISTRY_NAME?=docker.io/hashicorpboundary
|
||||
IMAGE_NAME=postgres
|
||||
IMAGE_TAG ?= $(REGISTRY_NAME)/$(IMAGE_NAME):11-alpine
|
||||
DOCKER_ARGS ?= -d
|
||||
PG_OPTS ?=
|
||||
TEST_DB_PORT ?= 5432
|
||||
BOUNDARY_VERSION = $(shell go mod edit -json | jq -r '.["Require"][] | select(.Path=="github.com/hashicorp/boundary") | .["Version"]')
|
||||
GOPATH ?= ~/go
|
||||
GOMODCACHE ?= $(GOPATH)/pkg/mod
|
||||
|
||||
tools:
|
||||
go generate -tags tools tools/tools.go
|
||||
|
||||
@@ -24,15 +34,22 @@ test:
|
||||
testacc:
|
||||
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/"
|
||||
TF_ACC=1 ~/.go/bin/go test ./... -v $(TESTARGS) -timeout 120m
|
||||
|
||||
install-go:
|
||||
install-go: ~/.go/bin/go
|
||||
|
||||
~/.go/bin/go:
|
||||
./ci/goinstall.sh
|
||||
|
||||
dev:
|
||||
mkdir -p $(INSTALL_PATH)
|
||||
GOOS=${GOOS} GOARCH=${GOARCH} ./scripts/plugins.sh
|
||||
mkdir -p $(INSTALL_PATH)
|
||||
go build -o $(INSTALL_PATH)/terraform-provider-boundary main.go
|
||||
|
||||
dev-no-plugins:
|
||||
mkdir -p $(INSTALL_PATH)
|
||||
go build -o $(INSTALL_PATH)/terraform-provider-boundary main.go
|
||||
|
||||
all:
|
||||
@@ -41,10 +58,35 @@ all:
|
||||
GOOS=windows go build -o $(BUILD_ALL_PATH)/terraform-provider-boundary_windows-amd64 main.go
|
||||
GOOS=linux go build -o $(BUILD_ALL_PATH)/terraform-provider-boundary_linux-amd64 main.go
|
||||
|
||||
docs:
|
||||
docs:
|
||||
go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs
|
||||
|
||||
rm-id-flag-from-docs:
|
||||
find docs/ -name "*.md" -type f | xargs sed -i -e '/- \*\*id\*\*/d'
|
||||
|
||||
.PHONY: testacc tools docs
|
||||
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
|
||||
- [Go](https://golang.org/doc/install) >= 1.16
|
||||
- [Go](https://golang.org/doc/install) >= 1.19
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -61,6 +69,9 @@ In order to run the full suite of Acceptance tests, run `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
|
||||
----------------------
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
VERSION="1.16"
|
||||
VERSION="1.19.1"
|
||||
|
||||
[ -z "$GOROOT" ] && GOROOT="$HOME/.go"
|
||||
[ -z "$GOPATH" ] && GOPATH="$HOME/go"
|
||||
|
||||
+8
-6
@@ -27,12 +27,14 @@ provider "boundary" {
|
||||
|
||||
### Required
|
||||
|
||||
- **addr** (String) The base url of the Boundary API, e.g. "http://127.0.0.1:9200". If not set, it will be read from the "BOUNDARY_ADDR" env var.
|
||||
- `addr` (String) The base url of the Boundary API, e.g. "http://127.0.0.1:9200". If not set, it will be read from the "BOUNDARY_ADDR" env var.
|
||||
|
||||
### Optional
|
||||
|
||||
- **auth_method_id** (String) The auth method ID e.g. ampw_1234567890
|
||||
- **password_auth_method_login_name** (String) The auth method login name for password-style auth methods
|
||||
- **password_auth_method_password** (String) The auth method password for password-style auth methods
|
||||
- **recovery_kms_hcl** (String) Can be a heredoc string or a path on disk. If set, the string/file will be parsed as HCL and used with the recovery KMS mechanism. While this is set, it will override any other authentication information; the KMS mechanism will always be used. See Boundary's KMS docs for examples: https://boundaryproject.io/docs/configuration/kms
|
||||
- **token** (String) The Boundary token to use, as a string or path on disk containing just the string. If set, the token read here will be used in place of authenticating with the auth method specified in "auth_method_id", although the recovery KMS mechanism will still override this. Can also be set with the BOUNDARY_TOKEN environment variable.
|
||||
- `auth_method_id` (String) The auth method ID e.g. ampw_1234567890
|
||||
- `password_auth_method_login_name` (String) The auth method login name for password-style auth methods
|
||||
- `password_auth_method_password` (String) The auth method password for password-style auth methods
|
||||
- `plugin_execution_dir` (String) Specifies a directory that the Boundary provider can use to write and execute its built-in plugins.
|
||||
- `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.
|
||||
@@ -3,60 +3,32 @@
|
||||
page_title: "boundary_account Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The account resource allows you to configure a Boundary account.
|
||||
Deprecated: use boundary_account_password instead.
|
||||
---
|
||||
|
||||
# boundary_account (Resource)
|
||||
|
||||
The account resource allows you to configure a Boundary account.
|
||||
Deprecated: use `boundary_account_password` instead.
|
||||
|
||||
## 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_auth_method" "password" {
|
||||
scope_id = boundary_scope.org.id
|
||||
type = "password"
|
||||
}
|
||||
|
||||
resource "boundary_account" "jeff" {
|
||||
auth_method_id = boundary_auth_method.password.id
|
||||
type = "password"
|
||||
login_name = "jeff"
|
||||
password = "$uper$ecure"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- **auth_method_id** (String) The resource ID for the auth method.
|
||||
- **type** (String) The resource type.
|
||||
- `auth_method_id` (String) 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.
|
||||
- `description` (String) The account description.
|
||||
- `login_name` (String) The login name for this account.
|
||||
- `name` (String) The account name. Defaults to the resource name.
|
||||
- `password` (String, Sensitive) The account password. Only set on create, changes will not be reflected when updating account.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the account.
|
||||
- `id` (String) The ID of the account.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_account.foo <my-id>
|
||||
```
|
||||
@@ -17,17 +17,17 @@ The account resource allows you to configure a Boundary account.
|
||||
|
||||
### Required
|
||||
|
||||
- **auth_method_id** (String) The resource ID for the auth method.
|
||||
- `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.
|
||||
- `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.
|
||||
- `id` (String) The ID of the account.
|
||||
|
||||
|
||||
@@ -10,25 +10,53 @@ description: |-
|
||||
|
||||
The account resource allows you to configure a Boundary account.
|
||||
|
||||
## 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_auth_method" "password" {
|
||||
scope_id = boundary_scope.org.id
|
||||
type = "password"
|
||||
}
|
||||
|
||||
resource "boundary_account_password" "jeff" {
|
||||
auth_method_id = boundary_auth_method.password.id
|
||||
type = "password"
|
||||
login_name = "jeff"
|
||||
password = "$uper$ecure"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- **auth_method_id** (String) The resource ID for the auth method.
|
||||
- **type** (String) The resource type.
|
||||
- `auth_method_id` (String) 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.
|
||||
- `description` (String) The account description.
|
||||
- `login_name` (String) The login name for this account.
|
||||
- `name` (String) The account name. Defaults to the resource name.
|
||||
- `password` (String, Sensitive) The account password. Only set on create, changes will not be reflected when updating account.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the account.
|
||||
- `id` (String) The ID of the account.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_account_password.foo <my-id>
|
||||
```
|
||||
@@ -32,19 +32,19 @@ resource "boundary_auth_method" "password" {
|
||||
|
||||
### Required
|
||||
|
||||
- **scope_id** (String) The scope ID.
|
||||
- **type** (String) The resource type.
|
||||
- `scope_id` (String) The scope ID.
|
||||
- `type` (String) The resource type.
|
||||
|
||||
### Optional
|
||||
|
||||
- **description** (String) The auth method description.
|
||||
- **min_login_name_length** (Number, Deprecated) The minimum login name length.
|
||||
- **min_password_length** (Number, Deprecated) The minimum password length.
|
||||
- **name** (String) The auth method name. Defaults to the resource name.
|
||||
- `description` (String) The auth method description.
|
||||
- `min_login_name_length` (Number, Deprecated) The minimum login name length.
|
||||
- `min_password_length` (Number, Deprecated) The minimum password length.
|
||||
- `name` (String) The auth method name. Defaults to the resource name.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the account.
|
||||
- `id` (String) The ID of the account.
|
||||
|
||||
## Import
|
||||
|
||||
|
||||
@@ -17,31 +17,31 @@ The OIDC auth method resource allows you to configure a Boundary auth_method_oid
|
||||
|
||||
### Required
|
||||
|
||||
- **scope_id** (String) The scope ID.
|
||||
- `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.
|
||||
- `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, Sensitive) 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.
|
||||
- `id` (String) The ID of the auth method.
|
||||
|
||||
|
||||
@@ -17,18 +17,18 @@ The auth method resource allows you to configure a Boundary auth_method_password
|
||||
|
||||
### Required
|
||||
|
||||
- **scope_id** (String) The scope ID.
|
||||
- `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
|
||||
- `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.
|
||||
- `id` (String) The ID of the account.
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_credential_json Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The json credential resource allows you to congiure a credential using a json object.
|
||||
---
|
||||
|
||||
# boundary_credential_json (Resource)
|
||||
|
||||
The json credential resource allows you to congiure a credential using a json object.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "global 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_static" "example" {
|
||||
name = "example_static_credential_store"
|
||||
description = "My first static credential store!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_credential_json" "example" {
|
||||
name = "example_json"
|
||||
description = "My first json credential!"
|
||||
credential_store_id = boundary_credential_store_static.example.id
|
||||
object = file("~/object.json") # change to valid json file
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `credential_store_id` (String) The credential store in which to save this json credential.
|
||||
- `object` (String, Sensitive) The object for the this json credential. Either values encoded with the "jsonencode" function, pre-escaped JSON string, or a file
|
||||
|
||||
### Optional
|
||||
|
||||
- `description` (String) The description of this json credential.
|
||||
- `name` (String) The name of this json credential. Defaults to the resource name.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `id` (String) The ID of this json credential.
|
||||
- `object_hmac` (String) The object hmac.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_credential_json.example_json <my-id>
|
||||
```
|
||||
@@ -50,7 +50,7 @@ resource "boundary_credential_library_vault" "bar" {
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "my/secret/bar" # change to Vault backend path
|
||||
http_method = "POST"
|
||||
request_body = <<EOT
|
||||
http_request_body = <<EOT
|
||||
{
|
||||
"key": "Value",
|
||||
}
|
||||
@@ -63,19 +63,20 @@ EOT
|
||||
|
||||
### 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.
|
||||
- `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.
|
||||
- `credential_type` (String) The type of credential the library generates.
|
||||
- `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.
|
||||
- `id` (String) The ID of the Vault credential library.
|
||||
|
||||
## Import
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_credential_ssh_private_key Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The SSH private key credential resource allows you to configure a credential using a username, private key and optional passphrase.
|
||||
---
|
||||
|
||||
# boundary_credential_ssh_private_key (Resource)
|
||||
|
||||
The SSH private key credential resource allows you to configure a credential using a username, private key and optional passphrase.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "global 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_static" "example" {
|
||||
name = "example_static_credential_store"
|
||||
description = "My first static credential store!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_credential_ssh_private_key" "example" {
|
||||
name = "example_ssh_private_key"
|
||||
description = "My first ssh private key credential!"
|
||||
credential_store_id = boundary_credential_store_static.example.id
|
||||
username = "my-username"
|
||||
private_key = file("~/.ssh/id_rsa") # change to valid SSH Private Key
|
||||
private_key_passphrase = "optional-passphrase" # change to the passphrase of the Private Key if required
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `credential_store_id` (String) ID of the credential store this credential belongs to.
|
||||
- `private_key` (String, Sensitive) The private key associated with the credential.
|
||||
- `username` (String) The username associated with the credential.
|
||||
|
||||
### Optional
|
||||
|
||||
- `description` (String) The description of the credential.
|
||||
- `name` (String) The name of the credential. Defaults to the resource name.
|
||||
- `private_key_passphrase` (String, Sensitive) The passphrase of the private key associated with the credential.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `id` (String) The ID of the credential.
|
||||
- `private_key_hmac` (String) The private key hmac.
|
||||
- `private_key_passphrase_hmac` (String) The private key passphrase hmac.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_credential_ssh_private_key.example_ssh_private_key <my-id>
|
||||
```
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_credential_store_static Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The static credential store resource allows you to configure a Boundary static credential store.
|
||||
---
|
||||
|
||||
# boundary_credential_store_static (Resource)
|
||||
|
||||
The static credential store resource allows you to configure a Boundary static credential store.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "global 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_static" "example" {
|
||||
name = "example_static_credential_store"
|
||||
description = "My first static credential store!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `scope_id` (String) The scope for this credential store.
|
||||
|
||||
### Optional
|
||||
|
||||
- `description` (String) The static credential store description.
|
||||
- `name` (String) The static credential store name. Defaults to the resource name.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `id` (String) The ID of the static credential store.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_credential_store_static.example_static_credential_store <my-id>
|
||||
```
|
||||
@@ -42,26 +42,26 @@ resource "boundary_credential_store_vault" "example" {
|
||||
|
||||
### 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.
|
||||
- `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.
|
||||
- `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.
|
||||
- `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
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_credential_username_password Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The username/password credential resource allows you to configure a credential using a username and password pair.
|
||||
---
|
||||
|
||||
# boundary_credential_username_password (Resource)
|
||||
|
||||
The username/password credential resource allows you to configure a credential using a username and password pair.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "global 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_static" "example" {
|
||||
name = "example_static_credential_store"
|
||||
description = "My first static credential store!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_credential_username_password" "example" {
|
||||
name = "example_username_password"
|
||||
description = "My first username password credential!"
|
||||
credential_store_id = boundary_credential_store_static.example.id
|
||||
username = "my-username"
|
||||
password = "my-password"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `credential_store_id` (String) The credential store in which to save this username/password credential.
|
||||
- `password` (String, Sensitive) The password of this username/password credential.
|
||||
- `username` (String) The username of this username/password credential.
|
||||
|
||||
### Optional
|
||||
|
||||
- `description` (String) The description of this username/password credential.
|
||||
- `name` (String) The name of this username/password credential. Defaults to the resource name.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `id` (String) The ID of this username/password credential.
|
||||
- `password_hmac` (String) The password hmac.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_credential_username_password.example_username_password <my-id>
|
||||
```
|
||||
@@ -69,17 +69,17 @@ resource "boundary_group" "example" {
|
||||
|
||||
### Required
|
||||
|
||||
- **scope_id** (String) The scope ID in which the resource is created. Defaults to the provider's `default_scope` if unset.
|
||||
- `scope_id` (String) The scope ID in which the resource is created. Defaults to the provider's `default_scope` if unset.
|
||||
|
||||
### Optional
|
||||
|
||||
- **description** (String) The group description.
|
||||
- **member_ids** (Set of String) Resource IDs for group members, these are most likely boundary users.
|
||||
- **name** (String) The group name. Defaults to the resource name.
|
||||
- `description` (String) The group description.
|
||||
- `member_ids` (Set of String) Resource IDs for group members, these are most likely boundary users.
|
||||
- `name` (String) The group name. Defaults to the resource name.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the group.
|
||||
- `id` (String) The ID of the group.
|
||||
|
||||
## Import
|
||||
|
||||
|
||||
+20
-12
@@ -3,12 +3,12 @@
|
||||
page_title: "boundary_host Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
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 boundary_host_static instead.
|
||||
---
|
||||
|
||||
# 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 `boundary_host_static` instead.
|
||||
|
||||
## Example Usage
|
||||
|
||||
@@ -28,12 +28,20 @@ resource "boundary_scope" "project" {
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_host" "example" {
|
||||
name = "example_host"
|
||||
description = "My first host!"
|
||||
address = "10.0.0.1"
|
||||
resource "boundary_host_catalog" "static" {
|
||||
name = "My catalog"
|
||||
description = "My first host catalog!"
|
||||
type = "static"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_host" "example" {
|
||||
type = "static"
|
||||
name = "example_host"
|
||||
description = "My first host!"
|
||||
address = "10.0.0.1"
|
||||
host_catalog_id = boundary_host_catalog.static.id
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
@@ -41,18 +49,18 @@ resource "boundary_host" "example" {
|
||||
|
||||
### Required
|
||||
|
||||
- **host_catalog_id** (String)
|
||||
- **type** (String)
|
||||
- `host_catalog_id` (String)
|
||||
- `type` (String) The type of host
|
||||
|
||||
### 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.
|
||||
- `address` (String) The static address of the host resource as `<IP>` (note: port assignment occurs in the target resource definition, do not add :port here) or a domain name.
|
||||
- `description` (String) The host description.
|
||||
- `name` (String) The host name. Defaults to the resource name.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the host.
|
||||
- `id` (String) The ID of the host.
|
||||
|
||||
## Import
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
page_title: "boundary_host_catalog Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
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 boundary_host_catalog_static instead.
|
||||
---
|
||||
|
||||
# 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 `boundary_host_catalog_static` instead.
|
||||
|
||||
## Example Usage
|
||||
|
||||
@@ -41,17 +41,17 @@ resource "boundary_host_catalog" "example" {
|
||||
|
||||
### Required
|
||||
|
||||
- **scope_id** (String) The scope ID in which the resource is created.
|
||||
- **type** (String) The host catalog type. Only `static` is supported.
|
||||
- `scope_id` (String) The scope ID in which the resource is created.
|
||||
- `type` (String) The host catalog type. Only `static` is supported.
|
||||
|
||||
### Optional
|
||||
|
||||
- **description** (String) The host catalog description.
|
||||
- **name** (String) The host catalog name. Defaults to the resource name.
|
||||
- `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.
|
||||
- `id` (String) The ID of the host catalog.
|
||||
|
||||
## Import
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
---
|
||||
# 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.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.global.id
|
||||
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
|
||||
}
|
||||
|
||||
# For more information about the aws plugin, please visit here:
|
||||
# https://github.com/hashicorp/boundary-plugin-host-aws
|
||||
#
|
||||
# For more information about aws users, please visit here:
|
||||
# https://learn.hashicorp.com/tutorials/boundary/aws-host-catalogs?in=boundary/oss-access-management#configure-terraform-and-iam-user-privileges
|
||||
resource "boundary_host_catalog_plugin" "aws_example" {
|
||||
name = "My aws catalog"
|
||||
description = "My first host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
plugin_name = "aws"
|
||||
attributes_json = jsonencode({ "region" = "us-east-1" })
|
||||
|
||||
# recommended to pass in aws secrets using a file() or using environment variables
|
||||
# the secrets below must be generated in aws by creating a aws iam user with programmatic access
|
||||
secrets_json = jsonencode({
|
||||
"access_key_id" = "aws_access_key_id_value",
|
||||
"secret_access_key" = "aws_secret_access_key_value"
|
||||
})
|
||||
}
|
||||
|
||||
# For more information about the azure plugin, please visit here:
|
||||
# https://github.com/hashicorp/boundary-plugin-host-azure
|
||||
#
|
||||
# For more information about azure ad applications, please visit here:
|
||||
# https://learn.hashicorp.com/tutorials/boundary/azure-host-catalogs#register-a-new-azure-ad-application-1
|
||||
resource "boundary_host_catalog_plugin" "azure_example" {
|
||||
name = "My azure catalog"
|
||||
description = "My second host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
plugin_name = "azure"
|
||||
|
||||
# the attributes below must be generated in azure by creating an ad application
|
||||
attributes_json = jsonencode({
|
||||
"disable_credential_rotation" = "true",
|
||||
"tenant_id" = "ARM_TENANT_ID",
|
||||
"subscription_id" = "ARM_SUBSCRIPTION_ID",
|
||||
"client_id" = "ARM_CLIENT_ID"
|
||||
})
|
||||
|
||||
# recommended to pass in aws secrets using a file() or using environment variables
|
||||
# the secrets below must be generated in azure by creating an ad application
|
||||
secrets_json = jsonencode({
|
||||
"secret_value" = "ARM_CLIENT_SECRET"
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
<!-- 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.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_host_catalog_plugin.foo <my-id>
|
||||
```
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
# 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.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.global.id
|
||||
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_host_catalog_static" "example" {
|
||||
name = "My catalog"
|
||||
description = "My first host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
```
|
||||
|
||||
<!-- 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.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_host_catalog_static.foo <my-id>
|
||||
```
|
||||
+16
-12
@@ -3,12 +3,12 @@
|
||||
page_title: "boundary_host_set Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
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 boundary_host_set_static instead.
|
||||
---
|
||||
|
||||
# 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 `boundary_host_set_static` instead.
|
||||
|
||||
## Example Usage
|
||||
|
||||
@@ -29,10 +29,12 @@ resource "boundary_scope" "project" {
|
||||
}
|
||||
|
||||
resource "boundary_host_catalog" "static" {
|
||||
type = "static"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_host" "1" {
|
||||
resource "boundary_host" "first" {
|
||||
type = "static"
|
||||
name = "host_1"
|
||||
description = "My first host!"
|
||||
address = "10.0.0.1"
|
||||
@@ -40,7 +42,8 @@ resource "boundary_host" "1" {
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_host" "2" {
|
||||
resource "boundary_host" "second" {
|
||||
type = "static"
|
||||
name = "host_2"
|
||||
description = "My second host!"
|
||||
address = "10.0.0.2"
|
||||
@@ -50,9 +53,10 @@ resource "boundary_host" "2" {
|
||||
|
||||
resource "boundary_host_set" "web" {
|
||||
host_catalog_id = boundary_host_catalog.static.id
|
||||
type = "static"
|
||||
host_ids = [
|
||||
boundary_host.1.id,
|
||||
boundary_host.2.id,
|
||||
boundary_host.first.id,
|
||||
boundary_host.second.id,
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -62,18 +66,18 @@ resource "boundary_host_set" "web" {
|
||||
|
||||
### Required
|
||||
|
||||
- **host_catalog_id** (String) The catalog for the hostset.
|
||||
- **type** (String)
|
||||
- `host_catalog_id` (String) The catalog for the host set.
|
||||
- `type` (String) The type of host set
|
||||
|
||||
### Optional
|
||||
|
||||
- **description** (String) The hostset description.
|
||||
- **host_ids** (Set of String) The list of host IDs contained in this set.
|
||||
- **name** (String) The hostset name. Defaults to the resource name.
|
||||
- `description` (String) The host set description.
|
||||
- `host_ids` (Set of String) The list of host IDs contained in this set.
|
||||
- `name` (String) The host set name. Defaults to the resource name.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the hostset.
|
||||
- `id` (String) The ID of the host set.
|
||||
|
||||
## Import
|
||||
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
---
|
||||
# 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.
|
||||
|
||||
## 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
|
||||
}
|
||||
|
||||
# For more information about the aws plugin, please visit here:
|
||||
# https://github.com/hashicorp/boundary-plugin-host-aws
|
||||
#
|
||||
# For more information about aws users, please visit here:
|
||||
# https://learn.hashicorp.com/tutorials/boundary/aws-host-catalogs?in=boundary/oss-access-management#configure-terraform-and-iam-user-privileges
|
||||
resource "boundary_host_catalog_plugin" "aws_example" {
|
||||
name = "My aws catalog"
|
||||
description = "My first host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
plugin_name = "aws"
|
||||
attributes_json = jsonencode({ "region" = "us-east-1" })
|
||||
|
||||
# recommended to pass in aws secrets using a file() or using environment variables
|
||||
# the secrets below must be generated in aws by creating a aws iam user with programmatic access
|
||||
secrets_json = jsonencode({
|
||||
"access_key_id" = "aws_access_key_id_value",
|
||||
"secret_access_key" = "aws_secret_access_key_value"
|
||||
})
|
||||
}
|
||||
|
||||
resource "boundary_host_set_plugin" "web" {
|
||||
name = "My web host set plugin"
|
||||
host_catalog_id = boundary_host_catalog_plugin.aws_exmaple.id
|
||||
attributes_json = jsonencode({ "filters" = "tag:service-type=web" })
|
||||
}
|
||||
|
||||
resource "boundary_host_set_plugin" "foobar" {
|
||||
name = "My foobar host set plugin"
|
||||
host_catalog_id = boundary_host_catalog_plugin.aws_exmaple.id
|
||||
preferred_endpoints = ["cidr:54.0.0.0/8"]
|
||||
attributes_json = jsonencode({
|
||||
"filters" = "tag-key=foo",
|
||||
"filters" = "tag-key=bar"
|
||||
})
|
||||
}
|
||||
|
||||
resource "boundary_host_set_plugin" "launch" {
|
||||
name = "My launch host set plugin"
|
||||
host_catalog_id = boundary_host_catalog_plugin.aws_exmaple.id
|
||||
sync_interval_seconds = 60
|
||||
attributes_json = jsonencode({
|
||||
"filters" = "tag:development=prod,dev",
|
||||
"filters" = "launch-time=2022-01-04T*"
|
||||
})
|
||||
}
|
||||
|
||||
# For more information about the azure plugin, please visit here:
|
||||
# https://github.com/hashicorp/boundary-plugin-host-azure
|
||||
#
|
||||
# For more information about azure ad applications, please visit here:
|
||||
# https://learn.hashicorp.com/tutorials/boundary/azure-host-catalogs#register-a-new-azure-ad-application-1
|
||||
resource "boundary_host_catalog_plugin" "azure_example" {
|
||||
name = "My azure catalog"
|
||||
description = "My second host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
plugin_name = "azure"
|
||||
|
||||
# the attributes below must be generated in azure by creating an ad application
|
||||
attributes_json = jsonencode({
|
||||
"disable_credential_rotation" = "true",
|
||||
"tenant_id" = "ARM_TENANT_ID",
|
||||
"subscription_id" = "ARM_SUBSCRIPTION_ID",
|
||||
"client_id" = "ARM_CLIENT_ID"
|
||||
})
|
||||
|
||||
# recommended to pass in aws secrets using a file() or using environment variables
|
||||
# the secrets below must be generated in azure by creating an ad application
|
||||
secrets_json = jsonencode({
|
||||
"secret_value" = "ARM_CLIENT_SECRET"
|
||||
})
|
||||
}
|
||||
|
||||
resource "boundary_host_set_plugin" "database" {
|
||||
name = "My database host set plugin"
|
||||
host_catalog_id = boundary_host_catalog_plugin.azure_exmaple.id
|
||||
attributes_json = jsonencode({ "filter" = "tagName eq 'service-type' and tagValue eq 'database'" })
|
||||
}
|
||||
|
||||
resource "boundary_host_set_plugin" "foodev" {
|
||||
name = "My foodev host set plugin"
|
||||
host_catalog_id = boundary_host_catalog_plugin.azure_exmaple.id
|
||||
preferred_endpoints = ["cidr:54.0.0.0/8"]
|
||||
sync_interval_seconds = 60
|
||||
attributes_json = jsonencode({
|
||||
"filter" = "tagName eq 'tag-key' and tagValue eq 'foo'",
|
||||
"filter" = "tagName eq 'application' and tagValue eq 'dev'",
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
<!-- 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.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_host_set_plugin.foo <my-id>
|
||||
```
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
# 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.
|
||||
|
||||
## 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_host_catalog_static" "example" {
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_host_static" "first" {
|
||||
name = "host_1"
|
||||
description = "My first host!"
|
||||
address = "10.0.0.1"
|
||||
host_catalog_id = boundary_host_catalog_static.example.id
|
||||
}
|
||||
|
||||
resource "boundary_host_static" "second" {
|
||||
name = "host_2"
|
||||
description = "My second host!"
|
||||
address = "10.0.0.2"
|
||||
host_catalog_id = boundary_host_catalog_static.example.id
|
||||
}
|
||||
|
||||
resource "boundary_host_set_static" "web" {
|
||||
host_catalog_id = boundary_host_catalog_static.example.id
|
||||
host_ids = [
|
||||
boundary_host_static.first.id,
|
||||
boundary_host_static.second.id,
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
<!-- 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.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_host_set_static.foo <my-id>
|
||||
```
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
# 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.
|
||||
|
||||
## 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_host_catalog_static" "example" {
|
||||
name = "My catalog"
|
||||
description = "My first host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_host_static" "example" {
|
||||
name = "example_host"
|
||||
description = "My first host!"
|
||||
address = "10.0.0.1"
|
||||
host_catalog_id = boundary_host_catalog_static.example.id
|
||||
}
|
||||
```
|
||||
|
||||
<!-- 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.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_host_static.foo <my-id>
|
||||
```
|
||||
@@ -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.
|
||||
|
||||
|
||||
+21
-22
@@ -51,10 +51,10 @@ resource "boundary_user" "bar" {
|
||||
}
|
||||
|
||||
resource "boundary_role" "example" {
|
||||
name = "My role"
|
||||
description = "My first role!"
|
||||
principals = [boundary_user.foo.id, boundary_user.bar.id]
|
||||
scope_id = boundary_scope.org.id
|
||||
name = "My role"
|
||||
description = "My first role!"
|
||||
principal_ids = [boundary_user.foo.id, boundary_user.bar.id]
|
||||
scope_id = boundary_scope.org.id
|
||||
}
|
||||
```
|
||||
|
||||
@@ -76,11 +76,11 @@ resource "boundary_user" "readonly" {
|
||||
}
|
||||
|
||||
resource "boundary_role" "readonly" {
|
||||
name = "readonly"
|
||||
description = "A readonly role"
|
||||
principals = [boundary_user.readonly.id]
|
||||
grants = ["id=*;action=read"]
|
||||
scope_id = boundary_scope.org.id
|
||||
name = "readonly"
|
||||
description = "A readonly role"
|
||||
principal_ids = [boundary_user.readonly.id]
|
||||
grant_strings = ["id=*;type=*;actions=read"]
|
||||
scope_id = boundary_scope.org.id
|
||||
}
|
||||
```
|
||||
|
||||
@@ -109,11 +109,11 @@ resource "boundary_user" "readonly" {
|
||||
}
|
||||
|
||||
resource "boundary_role" "readonly" {
|
||||
name = "readonly"
|
||||
description = "A readonly role"
|
||||
principals = [boundary_user.readonly.id]
|
||||
grants = ["id=*;action=read"]
|
||||
scope_id = boundary_scope.project.id
|
||||
name = "readonly"
|
||||
description = "A readonly role"
|
||||
principal_ids = [boundary_user.readonly.id]
|
||||
grant_strings = ["id=*;type=*;actions=read"]
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
```
|
||||
|
||||
@@ -122,20 +122,19 @@ resource "boundary_role" "readonly" {
|
||||
|
||||
### Required
|
||||
|
||||
- **scope_id** (String) The scope ID in which the resource is created. Defaults to the provider's `default_scope` if unset.
|
||||
- `scope_id` (String) The scope ID in which the resource is created. Defaults to the provider's `default_scope` if unset.
|
||||
|
||||
### Optional
|
||||
|
||||
- **default_role** (Boolean) Indicates that the role containing this value is the default role (that is, has the id 'r_default'), which triggers some specialized behavior to allow it to be imported and managed.
|
||||
- **description** (String) The role description.
|
||||
- **grant_scope_id** (String)
|
||||
- **grant_strings** (Set of String) A list of stringified grants for the role.
|
||||
- **name** (String) The role name. Defaults to the resource name.
|
||||
- **principal_ids** (Set of String) A list of principal (user or group) IDs to add as principals on the role.
|
||||
- `description` (String) The role description.
|
||||
- `grant_scope_id` (String)
|
||||
- `grant_strings` (Set of String) A list of stringified grants for the role.
|
||||
- `name` (String) The role name. Defaults to the resource name.
|
||||
- `principal_ids` (Set of String) A list of principal (user or group) IDs to add as principals on the role.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the role.
|
||||
- `id` (String) The ID of the role.
|
||||
|
||||
## Import
|
||||
|
||||
|
||||
@@ -65,19 +65,19 @@ resource "boundary_role" "org_admin" {
|
||||
|
||||
### Required
|
||||
|
||||
- **scope_id** (String) The scope ID containing the sub scope resource.
|
||||
- `scope_id` (String) The scope ID containing the sub scope resource.
|
||||
|
||||
### Optional
|
||||
|
||||
- **auto_create_admin_role** (Boolean) If set, when a new scope is created, the provider will not disable the functionality that automatically creates a role in the new scope and gives permissions to manage the scope to the provider's user. Marking this true makes for simpler HCL but results in role resources that are unmanaged by Terraform.
|
||||
- **auto_create_default_role** (Boolean) Only relevant when creating an org scope. If set, when a new scope is created, the provider will not disable the functionality that automatically creates a role in the new scope and gives listing of scopes and auth methods and the ability to authenticate to the anonymous user. Marking this true makes for simpler HCL but results in role resources that are unmanaged by Terraform.
|
||||
- **description** (String) The scope description.
|
||||
- **global_scope** (Boolean) Indicates that the scope containing this value is the global scope, which triggers some specialized behavior to allow it to be imported and managed.
|
||||
- **name** (String) The scope name. Defaults to the resource name.
|
||||
- `auto_create_admin_role` (Boolean) If set, when a new scope is created, the provider will not disable the functionality that automatically creates a role in the new scope and gives permissions to manage the scope to the provider's user. Marking this true makes for simpler HCL but results in role resources that are unmanaged by Terraform.
|
||||
- `auto_create_default_role` (Boolean) Only relevant when creating an org scope. If set, when a new scope is created, the provider will not disable the functionality that automatically creates a role in the new scope and gives listing of scopes and auth methods and the ability to authenticate to the anonymous user. Marking this true makes for simpler HCL but results in role resources that are unmanaged by Terraform.
|
||||
- `description` (String) The scope description.
|
||||
- `global_scope` (Boolean) Indicates that the scope containing this value is the global scope, which triggers some specialized behavior to allow it to be imported and managed.
|
||||
- `name` (String) The scope name. Defaults to the resource name.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the scope.
|
||||
- `id` (String) The ID of the scope.
|
||||
|
||||
## Import
|
||||
|
||||
|
||||
+33
-13
@@ -47,6 +47,7 @@ resource "boundary_credential_library_vault" "foo" {
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "my/secret/foo" # change to Vault backend path
|
||||
http_method = "GET"
|
||||
credential_type = "username_password"
|
||||
}
|
||||
|
||||
resource "boundary_host_catalog" "foo" {
|
||||
@@ -57,6 +58,7 @@ resource "boundary_host_catalog" "foo" {
|
||||
}
|
||||
|
||||
resource "boundary_host" "foo" {
|
||||
type = "static"
|
||||
name = "foo"
|
||||
host_catalog_id = boundary_host_catalog.foo.id
|
||||
scope_id = boundary_scope.project.id
|
||||
@@ -64,6 +66,7 @@ resource "boundary_host" "foo" {
|
||||
}
|
||||
|
||||
resource "boundary_host" "bar" {
|
||||
type = "static"
|
||||
name = "bar"
|
||||
host_catalog_id = boundary_host_catalog.foo.id
|
||||
scope_id = boundary_scope.project.id
|
||||
@@ -71,6 +74,7 @@ resource "boundary_host" "bar" {
|
||||
}
|
||||
|
||||
resource "boundary_host_set" "foo" {
|
||||
type = "static"
|
||||
name = "foo"
|
||||
host_catalog_id = boundary_host_catalog.foo.id
|
||||
|
||||
@@ -83,12 +87,27 @@ resource "boundary_host_set" "foo" {
|
||||
resource "boundary_target" "foo" {
|
||||
name = "foo"
|
||||
description = "Foo target"
|
||||
type = "tcp"
|
||||
default_port = "22"
|
||||
scope_id = boundary_scope.project.id
|
||||
host_set_ids = [
|
||||
host_source_ids = [
|
||||
boundary_host_set.foo.id
|
||||
]
|
||||
application_credential_library_ids = [
|
||||
brokered_credential_source_ids = [
|
||||
boundary_credential_library_vault.foo.id
|
||||
]
|
||||
}
|
||||
|
||||
resource "boundary_target" "ssh_foo" {
|
||||
name = "ssh_foo"
|
||||
description = "Ssh target"
|
||||
type = "ssh"
|
||||
default_port = "22"
|
||||
scope_id = boundary_scope.project.id
|
||||
host_source_ids = [
|
||||
boundary_host_set.foo.id
|
||||
]
|
||||
injected_application_credential_source_ids = [
|
||||
boundary_credential_library_vault.foo.id
|
||||
]
|
||||
}
|
||||
@@ -99,23 +118,24 @@ resource "boundary_target" "foo" {
|
||||
|
||||
### Required
|
||||
|
||||
- **scope_id** (String) The scope ID in which the resource is created. Defaults to the provider's `default_scope` if unset.
|
||||
- **type** (String) The target resource type.
|
||||
- `scope_id` (String) The scope ID in which the resource is created. Defaults to the provider's `default_scope` if unset.
|
||||
- `type` (String) The target resource type.
|
||||
|
||||
### Optional
|
||||
|
||||
- **application_credential_library_ids** (Set of String) A list of application credential library ID's.
|
||||
- **default_port** (Number) The default port for this target.
|
||||
- **description** (String) The target description.
|
||||
- **host_set_ids** (Set of String) A list of host set ID's.
|
||||
- **name** (String) The target name. Defaults to the resource name.
|
||||
- **session_connection_limit** (Number)
|
||||
- **session_max_seconds** (Number)
|
||||
- **worker_filter** (String) Boolean expression to filter the workers for this target
|
||||
- `brokered_credential_source_ids` (Set of String) A list of brokered credential source ID's.
|
||||
- `default_port` (Number) The default port for this target.
|
||||
- `description` (String) The target description.
|
||||
- `host_source_ids` (Set of String) A list of host source ID's.
|
||||
- `injected_application_credential_source_ids` (Set of String) A list of injected application credential source ID's.
|
||||
- `name` (String) The target name. Defaults to the resource name.
|
||||
- `session_connection_limit` (Number)
|
||||
- `session_max_seconds` (Number)
|
||||
- `worker_filter` (String) Boolean expression to filter the workers for this target
|
||||
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the target.
|
||||
- `id` (String) The ID of the target.
|
||||
|
||||
## Import
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ resource "boundary_auth_method" "password" {
|
||||
type = "password"
|
||||
}
|
||||
|
||||
resource "boundary_account" "jeff" {
|
||||
resource "boundary_account_password" "jeff" {
|
||||
auth_method_id = boundary_auth_method.password.id
|
||||
type = "password"
|
||||
login_name = "jeff"
|
||||
@@ -36,7 +36,7 @@ resource "boundary_account" "jeff" {
|
||||
resource "boundary_user" "jeff" {
|
||||
name = "jeff"
|
||||
description = "Jeff's user resource"
|
||||
account_ids = [boundary_account.jeff.id]
|
||||
account_ids = [boundary_account_password.jeff.id]
|
||||
scope_id = boundary_scope.org.id
|
||||
}
|
||||
```
|
||||
@@ -46,17 +46,17 @@ resource "boundary_user" "jeff" {
|
||||
|
||||
### Required
|
||||
|
||||
- **scope_id** (String) The scope ID in which the resource is created. Defaults to the provider's `default_scope` if unset.
|
||||
- `scope_id` (String) The scope ID in which the resource is created. Defaults to the provider's `default_scope` if unset.
|
||||
|
||||
### Optional
|
||||
|
||||
- **account_ids** (Set of String) Account ID's to associate with this user resource.
|
||||
- **description** (String) The user description.
|
||||
- **name** (String) The username. Defaults to the resource name.
|
||||
- `account_ids` (Set of String) Account ID's to associate with this user resource.
|
||||
- `description` (String) The user description.
|
||||
- `name` (String) The username. Defaults to the resource name.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the user.
|
||||
- `id` (String) The ID of the user.
|
||||
|
||||
## Import
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
terraform import boundary_account.foo <my-id>
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_account_password.foo <my-id>
|
||||
+1
-1
@@ -11,7 +11,7 @@ resource "boundary_auth_method" "password" {
|
||||
type = "password"
|
||||
}
|
||||
|
||||
resource "boundary_account" "jeff" {
|
||||
resource "boundary_account_password" "jeff" {
|
||||
auth_method_id = boundary_auth_method.password.id
|
||||
type = "password"
|
||||
login_name = "jeff"
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_credential_json.example_json <my-id>
|
||||
@@ -0,0 +1,27 @@
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "global 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_static" "example" {
|
||||
name = "example_static_credential_store"
|
||||
description = "My first static credential store!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_credential_json" "example" {
|
||||
name = "example_json"
|
||||
description = "My first json credential!"
|
||||
credential_store_id = boundary_credential_store_static.example.id
|
||||
object = file("~/object.json") # change to valid json file
|
||||
}
|
||||
@@ -35,7 +35,7 @@ resource "boundary_credential_library_vault" "bar" {
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "my/secret/bar" # change to Vault backend path
|
||||
http_method = "POST"
|
||||
request_body = <<EOT
|
||||
http_request_body = <<EOT
|
||||
{
|
||||
"key": "Value",
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_credential_ssh_private_key.example_ssh_private_key <my-id>
|
||||
@@ -0,0 +1,29 @@
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "global 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_static" "example" {
|
||||
name = "example_static_credential_store"
|
||||
description = "My first static credential store!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_credential_ssh_private_key" "example" {
|
||||
name = "example_ssh_private_key"
|
||||
description = "My first ssh private key credential!"
|
||||
credential_store_id = boundary_credential_store_static.example.id
|
||||
username = "my-username"
|
||||
private_key = file("~/.ssh/id_rsa") # change to valid SSH Private Key
|
||||
private_key_passphrase = "optional-passphrase" # change to the passphrase of the Private Key if required
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_credential_store_static.example_static_credential_store <my-id>
|
||||
@@ -0,0 +1,20 @@
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "global 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_static" "example" {
|
||||
name = "example_static_credential_store"
|
||||
description = "My first static credential store!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_credential_username_password.example_username_password <my-id>
|
||||
@@ -0,0 +1,28 @@
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "global 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_static" "example" {
|
||||
name = "example_static_credential_store"
|
||||
description = "My first static credential store!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_credential_username_password" "example" {
|
||||
name = "example_username_password"
|
||||
description = "My first username password credential!"
|
||||
credential_store_id = boundary_credential_store_static.example.id
|
||||
username = "my-username"
|
||||
password = "my-password"
|
||||
}
|
||||
@@ -13,9 +13,17 @@ resource "boundary_scope" "project" {
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_host" "example" {
|
||||
name = "example_host"
|
||||
description = "My first host!"
|
||||
address = "10.0.0.1"
|
||||
resource "boundary_host_catalog" "static" {
|
||||
name = "My catalog"
|
||||
description = "My first host catalog!"
|
||||
type = "static"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_host" "example" {
|
||||
type = "static"
|
||||
name = "example_host"
|
||||
description = "My first host!"
|
||||
address = "10.0.0.1"
|
||||
host_catalog_id = boundary_host_catalog.static.id
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_host_catalog_plugin.foo <my-id>
|
||||
@@ -0,0 +1,60 @@
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.global.id
|
||||
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
|
||||
}
|
||||
|
||||
# For more information about the aws plugin, please visit here:
|
||||
# https://github.com/hashicorp/boundary-plugin-host-aws
|
||||
#
|
||||
# For more information about aws users, please visit here:
|
||||
# https://learn.hashicorp.com/tutorials/boundary/aws-host-catalogs?in=boundary/oss-access-management#configure-terraform-and-iam-user-privileges
|
||||
resource "boundary_host_catalog_plugin" "aws_example" {
|
||||
name = "My aws catalog"
|
||||
description = "My first host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
plugin_name = "aws"
|
||||
attributes_json = jsonencode({ "region" = "us-east-1" })
|
||||
|
||||
# recommended to pass in aws secrets using a file() or using environment variables
|
||||
# the secrets below must be generated in aws by creating a aws iam user with programmatic access
|
||||
secrets_json = jsonencode({
|
||||
"access_key_id" = "aws_access_key_id_value",
|
||||
"secret_access_key" = "aws_secret_access_key_value"
|
||||
})
|
||||
}
|
||||
|
||||
# For more information about the azure plugin, please visit here:
|
||||
# https://github.com/hashicorp/boundary-plugin-host-azure
|
||||
#
|
||||
# For more information about azure ad applications, please visit here:
|
||||
# https://learn.hashicorp.com/tutorials/boundary/azure-host-catalogs#register-a-new-azure-ad-application-1
|
||||
resource "boundary_host_catalog_plugin" "azure_example" {
|
||||
name = "My azure catalog"
|
||||
description = "My second host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
plugin_name = "azure"
|
||||
|
||||
# the attributes below must be generated in azure by creating an ad application
|
||||
attributes_json = jsonencode({
|
||||
"disable_credential_rotation" = "true",
|
||||
"tenant_id" = "ARM_TENANT_ID",
|
||||
"subscription_id" = "ARM_SUBSCRIPTION_ID",
|
||||
"client_id" = "ARM_CLIENT_ID"
|
||||
})
|
||||
|
||||
# recommended to pass in aws secrets using a file() or using environment variables
|
||||
# the secrets below must be generated in azure by creating an ad application
|
||||
secrets_json = jsonencode({
|
||||
"secret_value" = "ARM_CLIENT_SECRET"
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_host_catalog_static.foo <my-id>
|
||||
@@ -0,0 +1,20 @@
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.global.id
|
||||
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_host_catalog_static" "example" {
|
||||
name = "My catalog"
|
||||
description = "My first host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
@@ -14,10 +14,12 @@ resource "boundary_scope" "project" {
|
||||
}
|
||||
|
||||
resource "boundary_host_catalog" "static" {
|
||||
type = "static"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_host" "1" {
|
||||
resource "boundary_host" "first" {
|
||||
type = "static"
|
||||
name = "host_1"
|
||||
description = "My first host!"
|
||||
address = "10.0.0.1"
|
||||
@@ -25,7 +27,8 @@ resource "boundary_host" "1" {
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_host" "2" {
|
||||
resource "boundary_host" "second" {
|
||||
type = "static"
|
||||
name = "host_2"
|
||||
description = "My second host!"
|
||||
address = "10.0.0.2"
|
||||
@@ -35,8 +38,9 @@ resource "boundary_host" "2" {
|
||||
|
||||
resource "boundary_host_set" "web" {
|
||||
host_catalog_id = boundary_host_catalog.static.id
|
||||
type = "static"
|
||||
host_ids = [
|
||||
boundary_host.1.id,
|
||||
boundary_host.2.id,
|
||||
boundary_host.first.id,
|
||||
boundary_host.second.id,
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_host_set_plugin.foo <my-id>
|
||||
@@ -0,0 +1,103 @@
|
||||
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
|
||||
}
|
||||
|
||||
# For more information about the aws plugin, please visit here:
|
||||
# https://github.com/hashicorp/boundary-plugin-host-aws
|
||||
#
|
||||
# For more information about aws users, please visit here:
|
||||
# https://learn.hashicorp.com/tutorials/boundary/aws-host-catalogs?in=boundary/oss-access-management#configure-terraform-and-iam-user-privileges
|
||||
resource "boundary_host_catalog_plugin" "aws_example" {
|
||||
name = "My aws catalog"
|
||||
description = "My first host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
plugin_name = "aws"
|
||||
attributes_json = jsonencode({ "region" = "us-east-1" })
|
||||
|
||||
# recommended to pass in aws secrets using a file() or using environment variables
|
||||
# the secrets below must be generated in aws by creating a aws iam user with programmatic access
|
||||
secrets_json = jsonencode({
|
||||
"access_key_id" = "aws_access_key_id_value",
|
||||
"secret_access_key" = "aws_secret_access_key_value"
|
||||
})
|
||||
}
|
||||
|
||||
resource "boundary_host_set_plugin" "web" {
|
||||
name = "My web host set plugin"
|
||||
host_catalog_id = boundary_host_catalog_plugin.aws_exmaple.id
|
||||
attributes_json = jsonencode({ "filters" = "tag:service-type=web" })
|
||||
}
|
||||
|
||||
resource "boundary_host_set_plugin" "foobar" {
|
||||
name = "My foobar host set plugin"
|
||||
host_catalog_id = boundary_host_catalog_plugin.aws_exmaple.id
|
||||
preferred_endpoints = ["cidr:54.0.0.0/8"]
|
||||
attributes_json = jsonencode({
|
||||
"filters" = "tag-key=foo",
|
||||
"filters" = "tag-key=bar"
|
||||
})
|
||||
}
|
||||
|
||||
resource "boundary_host_set_plugin" "launch" {
|
||||
name = "My launch host set plugin"
|
||||
host_catalog_id = boundary_host_catalog_plugin.aws_exmaple.id
|
||||
sync_interval_seconds = 60
|
||||
attributes_json = jsonencode({
|
||||
"filters" = "tag:development=prod,dev",
|
||||
"filters" = "launch-time=2022-01-04T*"
|
||||
})
|
||||
}
|
||||
|
||||
# For more information about the azure plugin, please visit here:
|
||||
# https://github.com/hashicorp/boundary-plugin-host-azure
|
||||
#
|
||||
# For more information about azure ad applications, please visit here:
|
||||
# https://learn.hashicorp.com/tutorials/boundary/azure-host-catalogs#register-a-new-azure-ad-application-1
|
||||
resource "boundary_host_catalog_plugin" "azure_example" {
|
||||
name = "My azure catalog"
|
||||
description = "My second host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
plugin_name = "azure"
|
||||
|
||||
# the attributes below must be generated in azure by creating an ad application
|
||||
attributes_json = jsonencode({
|
||||
"disable_credential_rotation" = "true",
|
||||
"tenant_id" = "ARM_TENANT_ID",
|
||||
"subscription_id" = "ARM_SUBSCRIPTION_ID",
|
||||
"client_id" = "ARM_CLIENT_ID"
|
||||
})
|
||||
|
||||
# recommended to pass in aws secrets using a file() or using environment variables
|
||||
# the secrets below must be generated in azure by creating an ad application
|
||||
secrets_json = jsonencode({
|
||||
"secret_value" = "ARM_CLIENT_SECRET"
|
||||
})
|
||||
}
|
||||
|
||||
resource "boundary_host_set_plugin" "database" {
|
||||
name = "My database host set plugin"
|
||||
host_catalog_id = boundary_host_catalog_plugin.azure_exmaple.id
|
||||
attributes_json = jsonencode({ "filter" = "tagName eq 'service-type' and tagValue eq 'database'" })
|
||||
}
|
||||
|
||||
resource "boundary_host_set_plugin" "foodev" {
|
||||
name = "My foodev host set plugin"
|
||||
host_catalog_id = boundary_host_catalog_plugin.azure_exmaple.id
|
||||
preferred_endpoints = ["cidr:54.0.0.0/8"]
|
||||
sync_interval_seconds = 60
|
||||
attributes_json = jsonencode({
|
||||
"filter" = "tagName eq 'tag-key' and tagValue eq 'foo'",
|
||||
"filter" = "tagName eq 'application' and tagValue eq 'dev'",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_host_set_static.foo <my-id>
|
||||
@@ -0,0 +1,40 @@
|
||||
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_host_catalog_static" "example" {
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_host_static" "first" {
|
||||
name = "host_1"
|
||||
description = "My first host!"
|
||||
address = "10.0.0.1"
|
||||
host_catalog_id = boundary_host_catalog_static.example.id
|
||||
}
|
||||
|
||||
resource "boundary_host_static" "second" {
|
||||
name = "host_2"
|
||||
description = "My second host!"
|
||||
address = "10.0.0.2"
|
||||
host_catalog_id = boundary_host_catalog_static.example.id
|
||||
}
|
||||
|
||||
resource "boundary_host_set_static" "web" {
|
||||
host_catalog_id = boundary_host_catalog_static.example.id
|
||||
host_ids = [
|
||||
boundary_host_static.first.id,
|
||||
boundary_host_static.second.id,
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_host_static.foo <my-id>
|
||||
@@ -0,0 +1,27 @@
|
||||
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_host_catalog_static" "example" {
|
||||
name = "My catalog"
|
||||
description = "My first host catalog!"
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_host_static" "example" {
|
||||
name = "example_host"
|
||||
description = "My first host!"
|
||||
address = "10.0.0.1"
|
||||
host_catalog_id = boundary_host_catalog_static.example.id
|
||||
}
|
||||
@@ -20,9 +20,9 @@ resource "boundary_user" "readonly" {
|
||||
}
|
||||
|
||||
resource "boundary_role" "readonly" {
|
||||
name = "readonly"
|
||||
description = "A readonly role"
|
||||
principals = [boundary_user.readonly.id]
|
||||
grants = ["id=*;action=read"]
|
||||
scope_id = boundary_scope.project.id
|
||||
name = "readonly"
|
||||
description = "A readonly role"
|
||||
principal_ids = [boundary_user.readonly.id]
|
||||
grant_strings = ["id=*;type=*;actions=read"]
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
@@ -13,9 +13,9 @@ resource "boundary_user" "readonly" {
|
||||
}
|
||||
|
||||
resource "boundary_role" "readonly" {
|
||||
name = "readonly"
|
||||
description = "A readonly role"
|
||||
principals = [boundary_user.readonly.id]
|
||||
grants = ["id=*;action=read"]
|
||||
scope_id = boundary_scope.org.id
|
||||
name = "readonly"
|
||||
description = "A readonly role"
|
||||
principal_ids = [boundary_user.readonly.id]
|
||||
grant_strings = ["id=*;type=*;actions=read"]
|
||||
scope_id = boundary_scope.org.id
|
||||
}
|
||||
@@ -17,8 +17,8 @@ resource "boundary_user" "bar" {
|
||||
}
|
||||
|
||||
resource "boundary_role" "example" {
|
||||
name = "My role"
|
||||
description = "My first role!"
|
||||
principals = [boundary_user.foo.id, boundary_user.bar.id]
|
||||
scope_id = boundary_scope.org.id
|
||||
name = "My role"
|
||||
description = "My first role!"
|
||||
principal_ids = [boundary_user.foo.id, boundary_user.bar.id]
|
||||
scope_id = boundary_scope.org.id
|
||||
}
|
||||
@@ -32,6 +32,7 @@ resource "boundary_credential_library_vault" "foo" {
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "my/secret/foo" # change to Vault backend path
|
||||
http_method = "GET"
|
||||
credential_type = "username_password"
|
||||
}
|
||||
|
||||
resource "boundary_host_catalog" "foo" {
|
||||
@@ -42,6 +43,7 @@ resource "boundary_host_catalog" "foo" {
|
||||
}
|
||||
|
||||
resource "boundary_host" "foo" {
|
||||
type = "static"
|
||||
name = "foo"
|
||||
host_catalog_id = boundary_host_catalog.foo.id
|
||||
scope_id = boundary_scope.project.id
|
||||
@@ -49,6 +51,7 @@ resource "boundary_host" "foo" {
|
||||
}
|
||||
|
||||
resource "boundary_host" "bar" {
|
||||
type = "static"
|
||||
name = "bar"
|
||||
host_catalog_id = boundary_host_catalog.foo.id
|
||||
scope_id = boundary_scope.project.id
|
||||
@@ -56,6 +59,7 @@ resource "boundary_host" "bar" {
|
||||
}
|
||||
|
||||
resource "boundary_host_set" "foo" {
|
||||
type = "static"
|
||||
name = "foo"
|
||||
host_catalog_id = boundary_host_catalog.foo.id
|
||||
|
||||
@@ -68,12 +72,27 @@ resource "boundary_host_set" "foo" {
|
||||
resource "boundary_target" "foo" {
|
||||
name = "foo"
|
||||
description = "Foo target"
|
||||
type = "tcp"
|
||||
default_port = "22"
|
||||
scope_id = boundary_scope.project.id
|
||||
host_set_ids = [
|
||||
host_source_ids = [
|
||||
boundary_host_set.foo.id
|
||||
]
|
||||
application_credential_library_ids = [
|
||||
brokered_credential_source_ids = [
|
||||
boundary_credential_library_vault.foo.id
|
||||
]
|
||||
}
|
||||
|
||||
resource "boundary_target" "ssh_foo" {
|
||||
name = "ssh_foo"
|
||||
description = "Ssh target"
|
||||
type = "ssh"
|
||||
default_port = "22"
|
||||
scope_id = boundary_scope.project.id
|
||||
host_source_ids = [
|
||||
boundary_host_set.foo.id
|
||||
]
|
||||
injected_application_credential_source_ids = [
|
||||
boundary_credential_library_vault.foo.id
|
||||
]
|
||||
}
|
||||
@@ -11,7 +11,7 @@ resource "boundary_auth_method" "password" {
|
||||
type = "password"
|
||||
}
|
||||
|
||||
resource "boundary_account" "jeff" {
|
||||
resource "boundary_account_password" "jeff" {
|
||||
auth_method_id = boundary_auth_method.password.id
|
||||
type = "password"
|
||||
login_name = "jeff"
|
||||
@@ -21,6 +21,6 @@ resource "boundary_account" "jeff" {
|
||||
resource "boundary_user" "jeff" {
|
||||
name = "jeff"
|
||||
description = "Jeff's user resource"
|
||||
account_ids = [boundary_account.jeff.id]
|
||||
account_ids = [boundary_account_password.jeff.id]
|
||||
scope_id = boundary_scope.org.id
|
||||
}
|
||||
@@ -1,35 +1,184 @@
|
||||
module github.com/hashicorp/terraform-provider-boundary
|
||||
|
||||
go 1.16
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.68.0 // indirect
|
||||
cloud.google.com/go/storage v1.11.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v46.4.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.3 // indirect
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/validation v0.3.0 // indirect
|
||||
github.com/agext/levenshtein v1.2.3 // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.545 // indirect
|
||||
github.com/aws/aws-sdk-go v1.35.4 // indirect
|
||||
github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe // indirect
|
||||
github.com/golang/snappy v0.0.2 // indirect
|
||||
github.com/hashicorp/boundary v0.5.1
|
||||
github.com/hashicorp/boundary/api v0.0.15
|
||||
github.com/hashicorp/boundary/sdk v0.0.7
|
||||
github.com/hashicorp/cap v0.1.1
|
||||
github.com/hashicorp/go-immutable-radix v1.3.0 // indirect
|
||||
github.com/hashicorp/go-kms-wrapping v0.6.5
|
||||
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.4.0
|
||||
github.com/hashicorp/terraform-plugin-sdk/v2 v2.7.0
|
||||
github.com/hashicorp/yamux v0.0.0-20200609203250-aecfd211c9ce // indirect
|
||||
github.com/hashicorp/boundary v0.11.0
|
||||
github.com/hashicorp/boundary/api v0.0.30
|
||||
github.com/hashicorp/boundary/sdk v0.0.24
|
||||
github.com/hashicorp/cap v0.2.0
|
||||
github.com/hashicorp/go-cty v1.4.1-0.20200723130312-85980079f637
|
||||
github.com/hashicorp/go-kms-wrapping/v2 v2.0.6-0.20220722192355-a843f53fa48d
|
||||
github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.5
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7
|
||||
github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.3
|
||||
github.com/hashicorp/terraform-plugin-docs v0.13.0
|
||||
github.com/hashicorp/terraform-plugin-sdk/v2 v2.23.0
|
||||
github.com/kr/pretty v0.3.0
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
github.com/oklog/run v1.1.0 // indirect
|
||||
github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
github.com/stretchr/testify v1.8.0
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.2 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.0 // indirect
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
github.com/agext/levenshtein v1.2.2 // indirect
|
||||
github.com/alessio/shellescape v1.4.1 // 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/beorn7/perks v1.0.1 // 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/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/containerd/continuity v0.1.0 // indirect
|
||||
github.com/coreos/go-oidc/v3 v3.1.0 // indirect
|
||||
github.com/danieljoos/wincred v1.1.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/cli v20.10.7+incompatible // indirect
|
||||
github.com/docker/docker v20.10.9+incompatible // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.0.6 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-migrate/migrate/v4 v4.15.1 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.2 // 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.7 // 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-20220725170111-b7cb3aa3d628 // indirect
|
||||
github.com/hashicorp/go-hclog v1.2.2 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-kms-wrapping/extras/kms/v2 v2.0.0-20220711120347-32232bae6803 // indirect
|
||||
github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-plugin v1.4.4 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/base62 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.4 // 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.3 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/hashicorp/hc-install v0.4.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hashicorp/hcl/v2 v2.14.0 // indirect
|
||||
github.com/hashicorp/logutils v1.0.0 // indirect
|
||||
github.com/hashicorp/nodeenrollment v0.1.17 // indirect
|
||||
github.com/hashicorp/terraform-exec v0.17.3 // indirect
|
||||
github.com/hashicorp/terraform-json v0.14.0 // indirect
|
||||
github.com/hashicorp/terraform-plugin-go v0.14.0 // indirect
|
||||
github.com/hashicorp/terraform-plugin-log v0.7.0 // indirect
|
||||
github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c // indirect
|
||||
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect
|
||||
github.com/hashicorp/vault/api v1.3.1 // indirect
|
||||
github.com/hashicorp/vault/sdk v0.3.0 // 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.13 // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgconn v1.12.1 // indirect
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.3.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||
github.com/jackc/pgtype v1.11.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.16.1 // indirect
|
||||
github.com/jefferai/go-libsecret v0.0.0-20210525195240-b53481abef97 // indirect
|
||||
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f // indirect
|
||||
github.com/jefferai/keyring v1.1.7-0.20220316160357-58a74bb55891 // indirect
|
||||
github.com/jinzhu/gorm v1.9.12 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.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.3+incompatible // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/mitchellh/cli v1.1.4 // 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.5.0 // indirect
|
||||
github.com/mitchellh/pointerstructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // 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.2 // 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/prometheus/client_golang v1.12.1 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/rogpeppe/go-internal v1.8.1 // indirect
|
||||
github.com/russross/blackfriday v1.6.0 // indirect
|
||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/sethvargo/go-diceware v0.3.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
||||
github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect
|
||||
github.com/vmihailenco/tagparser v0.1.1 // 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.11.0 // indirect
|
||||
github.com/zalando/go-keyring v0.2.1 // indirect
|
||||
github.com/zclconf/go-cty v1.11.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c // indirect
|
||||
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220805133916-01dd62135a58 // indirect
|
||||
google.golang.org/grpc v1.48.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gorm.io/driver/postgres v1.3.8 // indirect
|
||||
gorm.io/driver/sqlite v1.3.6 // indirect
|
||||
gorm.io/gorm v1.23.8 // indirect
|
||||
)
|
||||
@@ -15,4 +15,30 @@ const (
|
||||
HostCatalogIdKey = "host_catalog_id"
|
||||
// AuthMethodIdKey is used for common "auth_method_id" resource attribute
|
||||
AuthMethodIdKey = "auth_method_id"
|
||||
// PluginIdKey is used for common "plugin_id" resource attribute
|
||||
PluginIdKey = "plugin_id"
|
||||
// PluginNameKey is used for common "plugin_name" resource attribute
|
||||
PluginNameKey = "plugin_name"
|
||||
// AttributesJsonKey is used for setting attributes and corresponds to the
|
||||
// API "attributes" key
|
||||
AttributesJsonKey = "attributes_json"
|
||||
// SecretsJsonKey is used for setting secrets and corresponds to the API
|
||||
// "secrets" key
|
||||
SecretsJsonKey = "secrets_json"
|
||||
// SecretsHmacKey is a read-only key used for ensuring we detect if secrets
|
||||
// have changed
|
||||
SecretsHmacKey = "secrets_hmac"
|
||||
// PreferredEndpointsKey is used for setting preferred endpoints
|
||||
PreferredEndpointsKey = "preferred_endpoints"
|
||||
// SyncIntervalSecondsKey is used for setting the interval seconds
|
||||
SyncIntervalSecondsKey = "sync_interval_seconds"
|
||||
// internalSecretsConfigHmacKey is used for storing an hmac of hmac from server +
|
||||
// config string
|
||||
internalSecretsConfigHmacKey = "internal_secrets_config_hmac"
|
||||
// internalHmacUsedForSecretsConfigHmacKey is used for storing the server-provided
|
||||
// hmac used when calculating the current value of secretsConfigHmacKey
|
||||
internalHmacUsedForSecretsConfigHmacKey = "internal_hmac_used_for_secrets_config_hmac"
|
||||
// internalForceUpdateKey is used to force updates so we can always check
|
||||
// the value of secrets
|
||||
internalForceUpdateKey = "internal_force_update"
|
||||
)
|
||||
@@ -9,9 +9,12 @@ import (
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/authmethods"
|
||||
"github.com/hashicorp/boundary/sdk/wrapper"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/hashicorp/go-secure-stdlib/configutil/v2"
|
||||
"github.com/hashicorp/go-secure-stdlib/pluginutil/v2"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
kms_plugin_assets "github.com/hashicorp/terraform-provider-boundary/plugins/kms"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -52,25 +55,44 @@ func New() *schema.Provider {
|
||||
Optional: true,
|
||||
Description: "The auth method password for password-style auth methods",
|
||||
},
|
||||
"tls_insecure": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Description: "When set to true, does not validate the Boundary API endpoint certificate",
|
||||
},
|
||||
"plugin_execution_dir": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: `Specifies a directory that the Boundary provider can use to write and execute its built-in plugins.`,
|
||||
},
|
||||
},
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
"boundary_account": resourceAccount(),
|
||||
"boundary_account_password": resourceAccountPassword(),
|
||||
"boundary_account_oidc": resourceAccountOidc(),
|
||||
"boundary_auth_method": resourceAuthMethod(),
|
||||
"boundary_auth_method_password": resourceAuthMethodPassword(),
|
||||
"boundary_auth_method_oidc": resourceAuthMethodOidc(),
|
||||
"boundary_credential_library_vault": resourceCredentialLibraryVault(),
|
||||
"boundary_credential_store_vault": resourceCredentialStoreVault(),
|
||||
"boundary_managed_group": resourceManagedGroup(),
|
||||
"boundary_group": resourceGroup(),
|
||||
"boundary_host": resourceHost(),
|
||||
"boundary_host_catalog": resourceHostCatalog(),
|
||||
"boundary_host_set": resourceHostset(),
|
||||
"boundary_role": resourceRole(),
|
||||
"boundary_scope": resourceScope(),
|
||||
"boundary_target": resourceTarget(),
|
||||
"boundary_user": resourceUser(),
|
||||
"boundary_account": resourceAccount(),
|
||||
"boundary_account_password": resourceAccountPassword(),
|
||||
"boundary_account_oidc": resourceAccountOidc(),
|
||||
"boundary_auth_method": resourceAuthMethod(),
|
||||
"boundary_auth_method_password": resourceAuthMethodPassword(),
|
||||
"boundary_auth_method_oidc": resourceAuthMethodOidc(),
|
||||
"boundary_credential_library_vault": resourceCredentialLibraryVault(),
|
||||
"boundary_credential_store_vault": resourceCredentialStoreVault(),
|
||||
"boundary_credential_store_static": resourceCredentialStoreStatic(),
|
||||
"boundary_credential_username_password": resourceCredentialUsernamePassword(),
|
||||
"boundary_credential_ssh_private_key": resourceCredentialSshPrivateKey(),
|
||||
"boundary_credential_json": resourceCredentialJson(),
|
||||
"boundary_managed_group": resourceManagedGroup(),
|
||||
"boundary_group": resourceGroup(),
|
||||
"boundary_host": resourceHost(),
|
||||
"boundary_host_static": resourceHostStatic(),
|
||||
"boundary_host_catalog": resourceHostCatalog(),
|
||||
"boundary_host_catalog_static": resourceHostCatalogStatic(),
|
||||
"boundary_host_catalog_plugin": resourceHostCatalogPlugin(),
|
||||
"boundary_host_set": resourceHostSet(),
|
||||
"boundary_host_set_static": resourceHostSetStatic(),
|
||||
"boundary_host_set_plugin": resourceHostSetPlugin(),
|
||||
"boundary_role": resourceRole(),
|
||||
"boundary_scope": resourceScope(),
|
||||
"boundary_target": resourceTarget(),
|
||||
"boundary_user": resourceUser(),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -99,15 +121,35 @@ func providerAuthenticate(ctx context.Context, d *schema.ResourceData, md *metaD
|
||||
if err != nil {
|
||||
return fmt.Errorf(`error reading data from "recovery_kms_hcl": %v`, err)
|
||||
}
|
||||
wrapper, err := wrapper.GetWrapperFromHcl(recoveryHclStr, "recovery")
|
||||
|
||||
opts := []pluginutil.Option{
|
||||
pluginutil.WithPluginsMap(kms_plugin_assets.BuiltinKmsPlugins()),
|
||||
pluginutil.WithPluginsFilesystem(kms_plugin_assets.KmsPluginPrefix, kms_plugin_assets.FileSystem()),
|
||||
}
|
||||
|
||||
if execDir, ok := d.GetOk("plugin_execution_dir"); ok {
|
||||
opts = append(opts, pluginutil.WithPluginExecutionDirectory(execDir.(string)))
|
||||
}
|
||||
|
||||
wrapper, cleanUp, err := wrapper.GetWrapperFromHcl(
|
||||
ctx,
|
||||
recoveryHclStr,
|
||||
"recovery",
|
||||
configutil.WithPluginOptions(opts...))
|
||||
if err != nil {
|
||||
return fmt.Errorf(`error reading wrappers from "recovery_kms_hcl": %v`, err)
|
||||
}
|
||||
if wrapper == nil {
|
||||
return errors.New(`No "kms" block with purpose "recovery" found in "recovery_kms_hcl"`)
|
||||
}
|
||||
if err := wrapper.Init(ctx); err != nil {
|
||||
return fmt.Errorf("error initializing recovery kms: %v", err)
|
||||
if cleanUp != nil {
|
||||
// Terraform will cancel the context when work is complete.
|
||||
go func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
_ = cleanUp()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
md.recoveryKmsWrapper = wrapper
|
||||
@@ -169,6 +211,12 @@ func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc {
|
||||
return nil, diag.Errorf(`"no valid address could be determined from "addr" or "BOUNDARY_ADDR" env var`)
|
||||
}
|
||||
|
||||
if tlsInsecure, ok := d.GetOk("tls_insecure"); ok {
|
||||
if client.SetTLSConfig(&api.TLSConfig{Insecure: tlsInsecure.(bool)}) != nil {
|
||||
return nil, diag.Errorf("could not set insecure tls")
|
||||
}
|
||||
}
|
||||
|
||||
client.SetLimiter(5, 5)
|
||||
|
||||
md := &metaData{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
@@ -8,8 +9,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/testing/controller"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping"
|
||||
"github.com/hashicorp/go-kms-wrapping/wrappers/aead"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/hashicorp/go-kms-wrapping/v2/aead"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
@@ -36,7 +37,7 @@ func providerFactories(p **schema.Provider) map[string]func() (*schema.Provider,
|
||||
}
|
||||
}
|
||||
|
||||
func testWrapper(t *testing.T, key string) wrapping.Wrapper {
|
||||
func testWrapper(ctx context.Context, t *testing.T, key string) wrapping.Wrapper {
|
||||
var keyBytes []byte
|
||||
switch key {
|
||||
case "":
|
||||
@@ -56,15 +57,13 @@ func testWrapper(t *testing.T, key string) wrapping.Wrapper {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
wrapper := aead.NewWrapper(nil)
|
||||
wrapper := aead.NewWrapper()
|
||||
|
||||
_, err := wrapper.SetConfig(map[string]string{
|
||||
"key_id": key,
|
||||
})
|
||||
_, err := wrapper.SetConfig(ctx, wrapping.WithKeyId(key))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := wrapper.SetAESGCMKeyBytes(keyBytes); err != nil {
|
||||
if err := wrapper.SetAesGcmKeyBytes(keyBytes); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return wrapper
|
||||
|
||||
@@ -12,7 +12,8 @@ import (
|
||||
|
||||
func resourceAccount() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The account resource allows you to configure a Boundary account.",
|
||||
DeprecationMessage: "Deprecated: use `boundary_account_password` instead.",
|
||||
Description: "Deprecated: use `boundary_account_password` instead.",
|
||||
|
||||
CreateContext: resourceAccountCreate,
|
||||
ReadContext: resourceAccountRead,
|
||||
@@ -54,14 +55,12 @@ func resourceAccount() *schema.Resource {
|
||||
Description: "The login name for this account.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Deprecated: "Will be removed in favor of using attributes parameter",
|
||||
},
|
||||
accountPasswordKey: {
|
||||
Description: "The account password. Only set on create, changes will not be reflected when updating account.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Deprecated: "Will be removed in favor of using attributes parameter",
|
||||
|
||||
Sensitive: true,
|
||||
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
|
||||
if d.Id() == "" {
|
||||
// This is a new resource do not suppress password diff
|
||||
@@ -88,7 +87,6 @@ func setFromAccountResponseMap(d *schema.ResourceData, raw map[string]interface{
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(malnick) - remove after deprecation cycle in favor of attributes
|
||||
switch raw["type"].(string) {
|
||||
case accountTypePassword:
|
||||
if attrsVal, ok := raw["attributes"]; ok {
|
||||
@@ -123,12 +121,10 @@ func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, meta int
|
||||
opts = append(opts, accounts.WithDescription(descVal.(string)))
|
||||
}
|
||||
|
||||
// TODO(malnick) - remove after deprecation cycle
|
||||
if name, ok := d.GetOk(accountLoginNameKey); ok {
|
||||
opts = append(opts, accounts.WithPasswordAccountLoginName(name.(string)))
|
||||
}
|
||||
|
||||
// TODO(malnick) - remove after deprecation cycle
|
||||
if pass, ok := d.GetOk(accountPasswordKey); ok {
|
||||
opts = append(opts, accounts.WithPasswordAccountPassword(pass.(string)))
|
||||
}
|
||||
@@ -195,7 +191,6 @@ func resourceAccountUpdate(ctx context.Context, d *schema.ResourceData, meta int
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(malnick) - remove after deprecation cycle
|
||||
if d.HasChange(accountLoginNameKey) {
|
||||
opts = append(opts, accounts.DefaultPasswordAccountLoginName())
|
||||
if keyVal, ok := d.GetOk(accountLoginNameKey); ok {
|
||||
|
||||
@@ -70,8 +70,11 @@ func setFromAccountOidcResponseMap(d *schema.ResourceData, raw map[string]interf
|
||||
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"])
|
||||
if attrsVal, ok := raw["attributes"]; ok {
|
||||
attrs := attrsVal.(map[string]interface{})
|
||||
d.Set(accountOidcIssuerKey, attrs["issuer"])
|
||||
d.Set(accountOidcSubjectKey, attrs["subject"])
|
||||
}
|
||||
d.SetId(raw["id"].(string))
|
||||
}
|
||||
|
||||
|
||||
@@ -60,14 +60,12 @@ func resourceAccountPassword() *schema.Resource {
|
||||
Description: "The login name for this account.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Deprecated: "Will be removed in favor of using attributes parameter",
|
||||
},
|
||||
accountPasswordKey: {
|
||||
Description: "The account password. Only set on create, changes will not be reflected when updating account.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Deprecated: "Will be removed in favor of using attributes parameter",
|
||||
|
||||
Sensitive: true,
|
||||
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
|
||||
if d.Id() == "" {
|
||||
// This is a new resource do not suppress password diff
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/hashicorp/boundary/api/authmethods"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
|
||||
)
|
||||
|
||||
func resourceAuthMethod() *schema.Resource {
|
||||
@@ -50,6 +51,10 @@ func resourceAuthMethod() *schema.Resource {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: validation.StringInSlice([]string{
|
||||
authmethodTypeOidc,
|
||||
authmethodTypePassword,
|
||||
}, false),
|
||||
},
|
||||
authmethodMinLoginNameLengthKey: {
|
||||
Description: "The minimum login name length.",
|
||||
|
||||
@@ -99,6 +99,7 @@ func resourceAuthMethodOidc() *schema.Resource {
|
||||
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,
|
||||
Sensitive: true,
|
||||
},
|
||||
authmethodOidcIssuerKey: {
|
||||
Description: "The issuer corresponding to the provider, which must match the issuer field in generated tokens.",
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentials"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
credentialJsonCredentialType = "json"
|
||||
credentialJsonObjectKey = "object"
|
||||
credentialJsonObjectHmacKey = "object_hmac"
|
||||
)
|
||||
|
||||
func resourceCredentialJson() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The json credential resource allows you to congiure a credential using a json object.",
|
||||
|
||||
CreateContext: resourceCredentialJsonCreate,
|
||||
ReadContext: resourceCredentialJsonRead,
|
||||
UpdateContext: resourceCredentialJsonUpdate,
|
||||
DeleteContext: resourceCredentialJsonDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: schema.ImportStatePassthroughContext,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
IDKey: {
|
||||
Description: "The ID of this json credential.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
NameKey: {
|
||||
Description: "The name of this json credential. Defaults to the resource name.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
DescriptionKey: {
|
||||
Description: "The description of this json credential.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialStoreIdKey: {
|
||||
Description: "The credential store in which to save this json credential.",
|
||||
Type: schema.TypeString,
|
||||
ForceNew: true,
|
||||
Required: true,
|
||||
},
|
||||
credentialJsonObjectKey: {
|
||||
Description: `The object for the this json credential. Either values encoded with the "jsonencode" function, pre-escaped JSON string, or a file`,
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
credentialJsonObjectHmacKey: {
|
||||
Description: "The object hmac.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setFromCredentialJsonResponseMap(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(credentialStoreIdKey, raw[credentialStoreIdKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if attrsVal, ok := raw["attributes"]; ok {
|
||||
attrs := attrsVal.(map[string]interface{})
|
||||
stateObjectHmac := d.Get(credentialJsonObjectHmacKey)
|
||||
boundaryObjectHmac := attrs[credentialJsonObjectHmacKey].(string)
|
||||
if stateObjectHmac.(string) != boundaryObjectHmac && fromRead {
|
||||
if err := d.Set(credentialJsonObjectKey, "(changed in Boundary)"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := d.Set(credentialJsonObjectHmacKey, boundaryObjectHmac); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialJsonCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
|
||||
var opts []credentials.Option
|
||||
if v, ok := d.GetOk(NameKey); ok {
|
||||
opts = append(opts, credentials.WithName(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(DescriptionKey); ok {
|
||||
opts = append(opts, credentials.WithDescription(v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk(credentialJsonObjectKey); ok {
|
||||
var jsonObject map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(v.(string)), &jsonObject); err != nil {
|
||||
return diag.Errorf("error unmarshaling json: %v", err)
|
||||
}
|
||||
opts = append(opts, credentials.WithJsonCredentialObject(jsonObject))
|
||||
}
|
||||
|
||||
var credentialStoreId string
|
||||
retrievedStoreId, ok := d.GetOk(credentialStoreIdKey)
|
||||
if ok {
|
||||
credentialStoreId = retrievedStoreId.(string)
|
||||
} else {
|
||||
return diag.Errorf("credential store id is unset")
|
||||
}
|
||||
|
||||
client := credentials.NewClient(md.client)
|
||||
cred, err := client.Create(ctx, credentialJsonCredentialType, credentialStoreId, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error creating credential: %v", err)
|
||||
}
|
||||
if cred == nil {
|
||||
return diag.Errorf("nil credential after create")
|
||||
}
|
||||
|
||||
if err := setFromCredentialJsonResponseMap(d, cred.GetResponse().Map, false); err != nil {
|
||||
return diag.Errorf("error generating credential from response map: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialJsonRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentials.NewClient(md.client)
|
||||
|
||||
cred, 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: %v", err)
|
||||
}
|
||||
if cred == nil {
|
||||
return diag.Errorf("credential nil after read")
|
||||
}
|
||||
|
||||
if err := setFromCredentialJsonResponseMap(d, cred.GetResponse().Map, true); err != nil {
|
||||
return diag.Errorf("error generating credential from response map: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialJsonUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentials.NewClient(md.client)
|
||||
|
||||
var opts []credentials.Option
|
||||
if d.HasChange(NameKey) {
|
||||
opts = append(opts, credentials.DefaultName())
|
||||
if v, ok := d.GetOk(NameKey); ok {
|
||||
opts = append(opts, credentials.WithName(v.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(DescriptionKey) {
|
||||
opts = append(opts, credentials.DefaultDescription())
|
||||
if v, ok := d.GetOk(DescriptionKey); ok {
|
||||
opts = append(opts, credentials.WithDescription(v.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialJsonObjectKey) {
|
||||
if v, ok := d.GetOk(credentialJsonObjectKey); ok {
|
||||
var jsonObject map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(v.(string)), &jsonObject); err != nil {
|
||||
return diag.Errorf("error unmarshaling json: %v", err)
|
||||
}
|
||||
opts = append(opts, credentials.WithJsonCredentialObject(jsonObject))
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts) > 0 {
|
||||
opts = append(opts, credentials.WithAutomaticVersioning(true))
|
||||
credUpdate, err := client.Update(ctx, d.Id(), 0, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating credential: %v", err)
|
||||
}
|
||||
if credUpdate == nil {
|
||||
return diag.Errorf("credential nil after update")
|
||||
}
|
||||
|
||||
if err = setFromCredentialJsonResponseMap(d, credUpdate.GetResponse().Map, false); err != nil {
|
||||
return diag.Errorf("error generating credential from response map: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialJsonDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentials.NewClient(md.client)
|
||||
|
||||
_, err := client.Delete(ctx, d.Id())
|
||||
if err != nil {
|
||||
return diag.Errorf("error deleting credential: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentials"
|
||||
"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 (
|
||||
jsonCredResc = "boundary_credential_json.example"
|
||||
jsonCredName = "bar"
|
||||
jsonCredNameUpdate = "foo"
|
||||
jsonCredDesc = "the bar"
|
||||
jsonCredDescUpdate = "the foo"
|
||||
jsonCredObj = `jsonencode({
|
||||
password = "password",
|
||||
username = "admin"
|
||||
})`
|
||||
jsonCredObjUpdate = `jsonencode({
|
||||
password = "password",
|
||||
username = "db-admin"
|
||||
})`
|
||||
)
|
||||
|
||||
func jsonCredResource(name, description, object string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "boundary_credential_store_static" "example" {
|
||||
name = "static store name"
|
||||
description = "static store description"
|
||||
scope_id = boundary_scope.proj1.id
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
}
|
||||
|
||||
resource "boundary_credential_json" "example" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
credential_store_id = boundary_credential_store_static.example.id
|
||||
object = %s
|
||||
}`, name, description, object)
|
||||
}
|
||||
|
||||
func TestAccCredentialJson(t *testing.T) {
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
res := jsonCredResource(
|
||||
jsonCredName,
|
||||
jsonCredDesc,
|
||||
jsonCredObj,
|
||||
)
|
||||
|
||||
resUpdate := jsonCredResource(
|
||||
jsonCredNameUpdate,
|
||||
jsonCredDescUpdate,
|
||||
jsonCredObjUpdate,
|
||||
)
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckCredentialJsonResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// create
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, res),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(jsonCredResc, NameKey, jsonCredName),
|
||||
resource.TestCheckResourceAttr(jsonCredResc, DescriptionKey, jsonCredDesc),
|
||||
resource.TestCheckResourceAttr(jsonCredResc, credentialJsonObjectKey, `{"password":"password","username":"admin"}`),
|
||||
|
||||
testAccCheckCredentialJsonObjectHmac(provider),
|
||||
testAccCheckCredentialJsonResourceExists(provider, jsonCredResc),
|
||||
),
|
||||
},
|
||||
importStep(jsonCredResc, credentialJsonObjectKey),
|
||||
{
|
||||
// update
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(jsonCredResc, NameKey, jsonCredNameUpdate),
|
||||
resource.TestCheckResourceAttr(jsonCredResc, DescriptionKey, jsonCredDescUpdate),
|
||||
resource.TestCheckResourceAttr(jsonCredResc, credentialJsonObjectKey, `{"password":"password","username":"db-admin"}`),
|
||||
|
||||
testAccCheckCredentialJsonObjectHmac(provider),
|
||||
testAccCheckCredentialJsonResourceExists(provider, jsonCredResc),
|
||||
),
|
||||
},
|
||||
importStep(jsonCredResc, credentialJsonObjectKey),
|
||||
{
|
||||
// Run a plan only update and verify no changes
|
||||
PlanOnly: true,
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
},
|
||||
importStep(jsonCredResc, credentialJsonObjectKey),
|
||||
{
|
||||
// update again but apply a preConfig to externally update resource
|
||||
PreConfig: func() { jsonCredExternalUpdate(t, provider) },
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
},
|
||||
importStep(jsonCredResc, credentialJsonObjectKey),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckCredentialJsonResourceDestroy(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_json":
|
||||
id := rs.Primary.ID
|
||||
|
||||
c := credentials.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 json credential %q: %v", id, err)
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckCredentialJsonResourceExists(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 := credentials.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 testAccCheckCredentialJsonObjectHmac(testProvider *schema.Provider) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[jsonCredResc]
|
||||
if !ok {
|
||||
return fmt.Errorf("not found: %s", jsonCredResc)
|
||||
}
|
||||
|
||||
computed := rs.Primary.Attributes["object_hmac"]
|
||||
if len(computed) != 43 {
|
||||
return fmt.Errorf("Computed password hmac not the expected length of 43 characters. hmac: %q", computed)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func jsonCredExternalUpdate(t *testing.T, testProvider *schema.Provider) {
|
||||
if storeId == "" {
|
||||
t.Fatal("storeId must be set before testing an external update")
|
||||
}
|
||||
|
||||
md := testProvider.Meta().(*metaData)
|
||||
c := credentials.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 credential options
|
||||
var opts []credentials.Option
|
||||
opts = append(opts, credentials.WithDescription("this is an updated description"))
|
||||
|
||||
_, 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))
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
"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"
|
||||
)
|
||||
@@ -16,6 +15,7 @@ const (
|
||||
credentialLibraryVaultHttpMethodKey = "http_method"
|
||||
credentialLibraryVaultHttpRequestBodyKey = "http_request_body"
|
||||
credentialLibraryVaultPathKey = "path"
|
||||
credentialLibraryCredentialTypeKey = "credential_type"
|
||||
)
|
||||
|
||||
var libraryVaultAttrs = []string{
|
||||
@@ -73,6 +73,11 @@ func resourceCredentialLibraryVault() *schema.Resource {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
credentialLibraryCredentialTypeKey: {
|
||||
Description: "The type of credential the library generates.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -121,6 +126,9 @@ func resourceCredentialLibraryCreateVault(ctx context.Context, d *schema.Resourc
|
||||
if v, ok := d.GetOk(credentialLibraryVaultPathKey); ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryPath(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialLibraryCredentialTypeKey); ok {
|
||||
opts = append(opts, credentiallibraries.WithCredentialType(v.(string)))
|
||||
}
|
||||
|
||||
var credentialStoreId string
|
||||
cid, ok := d.GetOk(credentialStoreIdKey)
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
|
||||
const (
|
||||
vaultCredResc = "boundary_credential_library_vault.example"
|
||||
vaultCredTypedResc = "boundary_credential_library_vault.typed_example"
|
||||
vaultCredLibName = "foo"
|
||||
vaultCredLibDesc = "the foo"
|
||||
vaultCredLibPath = "/foo/bar"
|
||||
@@ -52,6 +53,19 @@ resource "boundary_credential_library_vault" "example" {
|
||||
vaultCredLibMethodPost,
|
||||
vaultCredLibRequestBody)
|
||||
|
||||
var vaultTypedCredLibResource = fmt.Sprintf(`
|
||||
resource "boundary_credential_library_vault" "typed_example" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
credential_store_id = boundary_credential_store_vault.example.id
|
||||
path = "%s"
|
||||
http_method = "%s"
|
||||
credential_type = "ssh_private_key"
|
||||
}`, vaultCredLibName,
|
||||
vaultCredLibDesc,
|
||||
vaultCredLibPath,
|
||||
vaultCredLibMethodGet)
|
||||
|
||||
func TestAccCredentialLibraryVault(t *testing.T) {
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
@@ -69,8 +83,9 @@ func TestAccCredentialLibraryVault(t *testing.T) {
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
IsUnitTest: true,
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckAuthMethodResourceDestroy(t, provider),
|
||||
CheckDestroy: testAccCheckCredentialLibraryVaultResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// create
|
||||
@@ -101,6 +116,22 @@ func TestAccCredentialLibraryVault(t *testing.T) {
|
||||
),
|
||||
},
|
||||
importStep(vaultCredResc),
|
||||
|
||||
{
|
||||
// create typed credential library, note credential type is immutable so no need for update test
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, vaultCredLibResourceUpdate, vaultTypedCredLibResource),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(vaultCredTypedResc, NameKey, vaultCredLibName),
|
||||
resource.TestCheckResourceAttr(vaultCredTypedResc, DescriptionKey, vaultCredLibDesc),
|
||||
resource.TestCheckResourceAttr(vaultCredTypedResc, credentialLibraryVaultPathKey, vaultCredLibPath),
|
||||
resource.TestCheckResourceAttr(vaultCredTypedResc, credentialLibraryVaultHttpMethodKey, vaultCredLibMethodGet),
|
||||
resource.TestCheckResourceAttr(vaultCredTypedResc, credentialLibraryVaultHttpRequestBodyKey, ""),
|
||||
resource.TestCheckResourceAttr(vaultCredTypedResc, credentialLibraryCredentialTypeKey, "ssh_private_key"),
|
||||
|
||||
testAccCheckCredentialLibraryVaultResourceExists(provider, vaultCredTypedResc),
|
||||
),
|
||||
},
|
||||
importStep(vaultCredResc),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentials"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
credentialSshPrivateKeyUsernameKey = "username"
|
||||
credentialSshPrivateKeyPrivateKeyKey = "private_key"
|
||||
credentialSshPrivateKeyPrivateKeyHmacKey = "private_key_hmac"
|
||||
credentialSshPrivateKeyPassphraseKey = "private_key_passphrase"
|
||||
credentialSshPrivateKeyPassphraseHmacKey = "private_key_passphrase_hmac"
|
||||
credentialSshPrivateKeyCredentialType = "ssh_private_key"
|
||||
)
|
||||
|
||||
func resourceCredentialSshPrivateKey() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The SSH private key credential resource allows you to configure a credential using a username, private key and optional passphrase.",
|
||||
|
||||
CreateContext: resourceCredentialSshPrivateKeyCreate,
|
||||
ReadContext: resourceCredentialSshPrivateKeyRead,
|
||||
UpdateContext: resourceCredentialSshPrivateKeyUpdate,
|
||||
DeleteContext: resourceCredentialSshPrivateKeyDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: schema.ImportStatePassthroughContext,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
IDKey: {
|
||||
Description: "The ID of the credential.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
NameKey: {
|
||||
Description: "The name of the credential. Defaults to the resource name.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
DescriptionKey: {
|
||||
Description: "The description of the credential.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialStoreIdKey: {
|
||||
Description: "ID of the credential store this credential belongs to.",
|
||||
Type: schema.TypeString,
|
||||
ForceNew: true,
|
||||
Required: true,
|
||||
},
|
||||
credentialSshPrivateKeyUsernameKey: {
|
||||
Description: "The username associated with the credential.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
credentialSshPrivateKeyPrivateKeyKey: {
|
||||
Description: "The private key associated with the credential.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
credentialSshPrivateKeyPrivateKeyHmacKey: {
|
||||
Description: "The private key hmac.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
credentialSshPrivateKeyPassphraseKey: {
|
||||
Description: "The passphrase of the private key associated with the credential.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
credentialSshPrivateKeyPassphraseHmacKey: {
|
||||
Description: "The private key passphrase hmac.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setFromCredentialSshPrivateKeyResponseMap(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(credentialStoreIdKey, raw[credentialStoreIdKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if attrsVal, ok := raw["attributes"]; ok {
|
||||
attrs := attrsVal.(map[string]interface{})
|
||||
if err := d.Set(credentialSshPrivateKeyUsernameKey, attrs[credentialSshPrivateKeyUsernameKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
statePrivKeyHmac := d.Get(credentialSshPrivateKeyPrivateKeyHmacKey)
|
||||
boundaryPrivKeyHmac := attrs[credentialSshPrivateKeyPrivateKeyHmacKey].(string)
|
||||
if statePrivKeyHmac.(string) != boundaryPrivKeyHmac && fromRead {
|
||||
// PrivateKeyHmac has changed in Boundary, therefore the private key has changed.
|
||||
// Update private key value to force tf to attempt update.
|
||||
if err := d.Set(credentialSshPrivateKeyPrivateKeyKey, "(changed in Boundary)"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := d.Set(credentialSshPrivateKeyPrivateKeyHmacKey, boundaryPrivKeyHmac); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var statePassphraseHmac, boundaryPassphraseHmac string
|
||||
if v := d.Get(credentialSshPrivateKeyPassphraseHmacKey); v != nil {
|
||||
statePassphraseHmac = v.(string)
|
||||
}
|
||||
if v := attrs[credentialSshPrivateKeyPassphraseHmacKey]; v != nil {
|
||||
boundaryPassphraseHmac = v.(string)
|
||||
}
|
||||
if statePassphraseHmac != boundaryPassphraseHmac && fromRead {
|
||||
// PassphraseHmac has changed in Boundary, therefore the private key passphrase has changed.
|
||||
// Update private key passphrase value to force tf to attempt update.
|
||||
if err := d.Set(credentialSshPrivateKeyPassphraseKey, "(changed in Boundary)"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := d.Set(credentialSshPrivateKeyPassphraseHmacKey, boundaryPassphraseHmac); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialSshPrivateKeyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
|
||||
var opts []credentials.Option
|
||||
if v, ok := d.GetOk(NameKey); ok {
|
||||
opts = append(opts, credentials.WithName(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(DescriptionKey); ok {
|
||||
opts = append(opts, credentials.WithDescription(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialSshPrivateKeyUsernameKey); ok {
|
||||
opts = append(opts, credentials.WithSshPrivateKeyCredentialUsername(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialSshPrivateKeyPrivateKeyKey); ok {
|
||||
opts = append(opts, credentials.WithSshPrivateKeyCredentialPrivateKey(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialSshPrivateKeyPassphraseKey); ok {
|
||||
opts = append(opts, credentials.WithSshPrivateKeyCredentialPrivateKeyPassphrase(v.(string)))
|
||||
}
|
||||
|
||||
var credentialStoreId string
|
||||
retrievedStoreId, ok := d.GetOk(credentialStoreIdKey)
|
||||
if ok {
|
||||
credentialStoreId = retrievedStoreId.(string)
|
||||
} else {
|
||||
return diag.Errorf("credential store id is unset")
|
||||
}
|
||||
|
||||
client := credentials.NewClient(md.client)
|
||||
cr, err := client.Create(ctx, credentialSshPrivateKeyCredentialType, credentialStoreId, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error creating credential: %v", err)
|
||||
}
|
||||
if cr == nil {
|
||||
return diag.Errorf("nil credential after create")
|
||||
}
|
||||
|
||||
if err := setFromCredentialSshPrivateKeyResponseMap(d, cr.GetResponse().Map, false); err != nil {
|
||||
return diag.Errorf("error generating credential from response map: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialSshPrivateKeyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentials.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: %v", err)
|
||||
}
|
||||
if cr == nil {
|
||||
return diag.Errorf("credential nil after read")
|
||||
}
|
||||
|
||||
if err := setFromCredentialSshPrivateKeyResponseMap(d, cr.GetResponse().Map, true); err != nil {
|
||||
return diag.Errorf("error generating credential from response map: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialSshPrivateKeyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentials.NewClient(md.client)
|
||||
|
||||
var opts []credentials.Option
|
||||
if d.HasChange(NameKey) {
|
||||
opts = append(opts, credentials.DefaultName())
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
opts = append(opts, credentials.WithName(nameVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(DescriptionKey) {
|
||||
opts = append(opts, credentials.DefaultDescription())
|
||||
descVal, ok := d.GetOk(DescriptionKey)
|
||||
if ok {
|
||||
opts = append(opts, credentials.WithDescription(descVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialSshPrivateKeyUsernameKey) {
|
||||
usernameVal, ok := d.GetOk(credentialSshPrivateKeyUsernameKey)
|
||||
if ok {
|
||||
opts = append(opts, credentials.WithSshPrivateKeyCredentialUsername(usernameVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialSshPrivateKeyPrivateKeyKey) {
|
||||
privKeyVal, ok := d.GetOk(credentialSshPrivateKeyPrivateKeyKey)
|
||||
if ok {
|
||||
opts = append(opts, credentials.WithSshPrivateKeyCredentialPrivateKey(privKeyVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialSshPrivateKeyPassphraseKey) {
|
||||
passVal, ok := d.GetOk(credentialSshPrivateKeyPassphraseKey)
|
||||
if ok {
|
||||
opts = append(opts, credentials.WithSshPrivateKeyCredentialPrivateKeyPassphrase(passVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts) > 0 {
|
||||
opts = append(opts, credentials.WithAutomaticVersioning(true))
|
||||
crUpdate, err := client.Update(ctx, d.Id(), 0, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating credential: %v", err)
|
||||
}
|
||||
if crUpdate == nil {
|
||||
return diag.Errorf("credential nil after update")
|
||||
}
|
||||
|
||||
if err = setFromCredentialSshPrivateKeyResponseMap(d, crUpdate.GetResponse().Map, false); err != nil {
|
||||
return diag.Errorf("error generating credential from response map: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialSshPrivateKeyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentials.NewClient(md.client)
|
||||
|
||||
_, err := client.Delete(ctx, d.Id())
|
||||
if err != nil {
|
||||
return diag.Errorf("error deleting credential: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentials"
|
||||
"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"
|
||||
"golang.org/x/crypto/ssh/testdata"
|
||||
)
|
||||
|
||||
const (
|
||||
sshPrivateKeyCredResc = "boundary_credential_ssh_private_key.example"
|
||||
sshPrivateKeyCredName = "Dr Jekyll"
|
||||
sshPrivateKeyCredDesc = "my best description"
|
||||
sshPrivateKeyUpdate = "some magic update string"
|
||||
sshPrivateKeyUsername = "my-user"
|
||||
)
|
||||
|
||||
var staticStore = fmt.Sprintf(`
|
||||
resource "boundary_credential_store_static" "ssh_store" {
|
||||
name = "static store name"
|
||||
description = "static store description"
|
||||
scope_id = boundary_scope.proj1.id
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
}`)
|
||||
|
||||
func sshPrivateKeyResource(name, description, username, privateKey, passphrase string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "boundary_credential_ssh_private_key" "example" {
|
||||
name = %q
|
||||
description = %q
|
||||
credential_store_id = boundary_credential_store_static.ssh_store.id
|
||||
username = %q
|
||||
private_key = %q
|
||||
private_key_passphrase = %q
|
||||
}`, name, description, username, privateKey, passphrase)
|
||||
}
|
||||
|
||||
func TestAccCredentialSshPrivateKey(t *testing.T) {
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
privKey := string(testdata.PEMBytes["rsa"])
|
||||
privKeyUpdate := string(testdata.PEMEncryptedKeys[0].PEMBytes)
|
||||
privKeyUpdatePassphrase := testdata.PEMEncryptedKeys[0].EncryptionKey
|
||||
|
||||
res := sshPrivateKeyResource(
|
||||
sshPrivateKeyCredName,
|
||||
sshPrivateKeyCredDesc,
|
||||
sshPrivateKeyUsername,
|
||||
privKey,
|
||||
"",
|
||||
)
|
||||
|
||||
resUpdate := sshPrivateKeyResource(
|
||||
sshPrivateKeyCredName+sshPrivateKeyUpdate,
|
||||
sshPrivateKeyCredDesc+sshPrivateKeyUpdate,
|
||||
sshPrivateKeyUsername+sshPrivateKeyUpdate,
|
||||
privKeyUpdate,
|
||||
privKeyUpdatePassphrase,
|
||||
)
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
IsUnitTest: true,
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckCredentialSshPrivateKeyResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// create
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, staticStore, res),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(sshPrivateKeyCredResc, NameKey, sshPrivateKeyCredName),
|
||||
resource.TestCheckResourceAttr(sshPrivateKeyCredResc, DescriptionKey, sshPrivateKeyCredDesc),
|
||||
resource.TestCheckResourceAttr(sshPrivateKeyCredResc, credentialSshPrivateKeyUsernameKey, sshPrivateKeyUsername),
|
||||
resource.TestCheckResourceAttr(sshPrivateKeyCredResc, credentialSshPrivateKeyPrivateKeyKey, privKey),
|
||||
resource.TestCheckResourceAttr(sshPrivateKeyCredResc, credentialSshPrivateKeyPassphraseKey, ""),
|
||||
|
||||
testAccCheckCredentialStoreSshPrivateKeyHmac(provider),
|
||||
testAccCheckCredentialSshPrivateKeyResourceExists(provider, sshPrivateKeyCredResc),
|
||||
),
|
||||
},
|
||||
importStep(sshPrivateKeyCredResc, credentialSshPrivateKeyPrivateKeyKey, credentialSshPrivateKeyPassphraseKey),
|
||||
{
|
||||
// update
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, staticStore, resUpdate),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(sshPrivateKeyCredResc, NameKey, sshPrivateKeyCredName+sshPrivateKeyUpdate),
|
||||
resource.TestCheckResourceAttr(sshPrivateKeyCredResc, DescriptionKey, sshPrivateKeyCredDesc+sshPrivateKeyUpdate),
|
||||
resource.TestCheckResourceAttr(sshPrivateKeyCredResc, credentialSshPrivateKeyUsernameKey, sshPrivateKeyUsername+sshPrivateKeyUpdate),
|
||||
resource.TestCheckResourceAttr(sshPrivateKeyCredResc, credentialSshPrivateKeyPrivateKeyKey, privKeyUpdate),
|
||||
resource.TestCheckResourceAttr(sshPrivateKeyCredResc, credentialSshPrivateKeyPassphraseKey, privKeyUpdatePassphrase),
|
||||
|
||||
testAccCheckCredentialStoreSshPrivateKeyHmac(provider),
|
||||
testAccCheckCredentialSshPrivateKeyResourceExists(provider, sshPrivateKeyCredResc),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckCredentialSshPrivateKeyResourceExists(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 := credentials.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 testAccCheckCredentialSshPrivateKeyResourceDestroy(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_ssh_private_key":
|
||||
id := rs.Primary.ID
|
||||
|
||||
c := credentials.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 credential %q: %v", id, err)
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckCredentialStoreSshPrivateKeyHmac(testProvider *schema.Provider) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[sshPrivateKeyCredResc]
|
||||
if !ok {
|
||||
return fmt.Errorf("not found: %s", sshPrivateKeyCredResc)
|
||||
}
|
||||
|
||||
computed := rs.Primary.Attributes["private_key_hmac"]
|
||||
if len(computed) != 43 {
|
||||
return fmt.Errorf("computed private key hmac not the expected length of 43 characters, got: %q", computed)
|
||||
}
|
||||
|
||||
if rs.Primary.Attributes["private_key_passphrase"] != "" {
|
||||
// We set a passphrase, validate the computed hmac is expected length
|
||||
computed := rs.Primary.Attributes["private_key_passphrase_hmac"]
|
||||
if len(computed) != 43 {
|
||||
return fmt.Errorf("computed private key passphrase hmac not the expected length of 43 characters, got: %q", computed)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
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 staticCredentialStoreType = "static"
|
||||
|
||||
func resourceCredentialStoreStatic() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The static credential store resource allows you to configure a Boundary static credential store.",
|
||||
|
||||
CreateContext: resourceStaticCredentialStoreCreate,
|
||||
ReadContext: resourceStaticCredentialStoreRead,
|
||||
UpdateContext: resourceStaticCredentialStoreUpdate,
|
||||
DeleteContext: resourceStaticCredentialStoreDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: schema.ImportStatePassthroughContext,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
IDKey: {
|
||||
Description: "The ID of the static credential store.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
NameKey: {
|
||||
Description: "The static credential store name. Defaults to the resource name.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
DescriptionKey: {
|
||||
Description: "The static credential store description.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
ScopeIdKey: {
|
||||
Description: "The scope for this credential store.",
|
||||
Type: schema.TypeString,
|
||||
ForceNew: true,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setFromStaticCredentialStoreResponseMap(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
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceStaticCredentialStoreCreate(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)))
|
||||
}
|
||||
|
||||
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, staticCredentialStoreType, 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 := setFromStaticCredentialStoreResponseMap(d, cr.GetResponse().Map, false); err != nil {
|
||||
return diag.Errorf("error generating credential store from response map: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceStaticCredentialStoreRead(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 := setFromStaticCredentialStoreResponseMap(d, cr.GetResponse().Map, true); err != nil {
|
||||
return diag.Errorf("error generating credential store from response map: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceStaticCredentialStoreUpdate(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 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 = setFromStaticCredentialStoreResponseMap(d, crUpdate.GetResponse().Map, false); err != nil {
|
||||
return diag.Errorf("error generating credential store from response map: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceStaticCredentialStoreDelete(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,139 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentialstores"
|
||||
"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 (
|
||||
staticCredStoreResc = "boundary_credential_store_static.example"
|
||||
staticCredStoreName = "foo"
|
||||
staticCredStoreDesc = "the foo"
|
||||
staticCredStoreNamespace = "static"
|
||||
staticCredStoreUpdate = "_random"
|
||||
)
|
||||
|
||||
func staticCredStoreResource(name string, description string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "boundary_credential_store_static" "example" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
scope_id = boundary_scope.proj1.id
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
}`, name,
|
||||
description)
|
||||
}
|
||||
|
||||
func TestAccCredentialStoreStatic(t *testing.T) {
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
res := staticCredStoreResource(staticCredStoreName,
|
||||
staticCredStoreDesc)
|
||||
|
||||
resUpdate := staticCredStoreResource(staticCredStoreName+staticCredStoreUpdate,
|
||||
staticCredStoreDesc+staticCredStoreUpdate)
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckCredentialStoreStaticResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// create
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, res),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(staticCredStoreResc, NameKey, staticCredStoreName),
|
||||
resource.TestCheckResourceAttr(staticCredStoreResc, DescriptionKey, staticCredStoreDesc),
|
||||
|
||||
testAccCheckCredentialStoreVaultResourceExists(provider, staticCredStoreResc),
|
||||
),
|
||||
},
|
||||
importStep(staticCredStoreResc),
|
||||
{
|
||||
// update
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(staticCredStoreResc, NameKey, staticCredStoreName+staticCredStoreUpdate),
|
||||
resource.TestCheckResourceAttr(staticCredStoreResc, DescriptionKey, staticCredStoreDesc+staticCredStoreUpdate),
|
||||
|
||||
testAccCheckCredentialStoreVaultResourceExists(provider, staticCredStoreResc),
|
||||
),
|
||||
},
|
||||
importStep(staticCredStoreResc),
|
||||
{
|
||||
// Run a plan only update and verify no changes
|
||||
PlanOnly: true,
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
},
|
||||
importStep(staticCredStoreResc),
|
||||
{
|
||||
// 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() { staticCredentialStoreExternalUpdate(t, provider) },
|
||||
PlanOnly: true,
|
||||
ExpectNonEmptyPlan: true,
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
},
|
||||
importStep(staticCredStoreResc),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func staticCredentialStoreExternalUpdate(t *testing.T, testProvider *schema.Provider) {
|
||||
if storeId == "" {
|
||||
t.Fatal("storeId must be set before testing an external update")
|
||||
}
|
||||
|
||||
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 credential store options
|
||||
var opts []credentialstores.Option
|
||||
opts = append(opts, credentialstores.WithDescription("this is an updated description, my guy"))
|
||||
|
||||
_, 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 testAccCheckCredentialStoreStaticResourceDestroy(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_static":
|
||||
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 static credential store %q: %v", id, err)
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
@@ -123,36 +124,49 @@ func resourceCredentialStoreVault() *schema.Resource {
|
||||
}
|
||||
}
|
||||
|
||||
func setFromVaultCredentialStoreResponseMap(d *schema.ResourceData, raw map[string]interface{}, fromRead bool) error {
|
||||
func setFromVaultCredentialStoreResponseMap(d *schema.ResourceData, raw map[string]interface{}, fromRead bool) diag.Diagnostics {
|
||||
if err := d.Set(NameKey, raw[NameKey]); err != nil {
|
||||
return err
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
if err := d.Set(DescriptionKey, raw[DescriptionKey]); err != nil {
|
||||
return err
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
if err := d.Set(ScopeIdKey, raw[ScopeIdKey]); err != nil {
|
||||
return err
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
var diags diag.Diagnostics
|
||||
csId := raw["id"]
|
||||
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
|
||||
return diag.FromErr(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
|
||||
boundaryTokenHmac, ok := attrs[credentialStoreVaultTokenHmacKey]
|
||||
tokenStatus := attrs["token_status"]
|
||||
switch {
|
||||
case ok:
|
||||
boundaryTokenHmacStr := boundaryTokenHmac.(string)
|
||||
stateTokenHmac := d.Get(credentialStoreVaultTokenHmacKey)
|
||||
if stateTokenHmac.(string) != boundaryTokenHmacStr && 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 diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := d.Set(credentialStoreVaultTokenHmacKey, boundaryTokenHmac); err != nil {
|
||||
return err
|
||||
if err := d.Set(credentialStoreVaultTokenHmacKey, boundaryTokenHmacStr); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
case tokenStatus == "expired":
|
||||
diags = append(diags, diag.Diagnostic{
|
||||
Severity: diag.Warning,
|
||||
Summary: fmt.Sprintf("Vault token has expired for credential store %q, please update.", csId),
|
||||
})
|
||||
}
|
||||
|
||||
stateClientKeyHmac := d.Get(credentialStoreVaultClientCertificateKeyHmacKey)
|
||||
@@ -164,17 +178,17 @@ func setFromVaultCredentialStoreResponseMap(d *schema.ResourceData, raw map[stri
|
||||
// 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
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
if err := d.Set(credentialStoreVaultClientCertificateKeyHmacKey, boundaryClientKeyHmac); err != nil {
|
||||
return err
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
d.SetId(csId.(string))
|
||||
|
||||
return nil
|
||||
return diags
|
||||
}
|
||||
|
||||
func resourceCredentialStoreVaultCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
@@ -229,8 +243,8 @@ func resourceCredentialStoreVaultCreate(ctx context.Context, d *schema.ResourceD
|
||||
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)
|
||||
if diagnostics := setFromVaultCredentialStoreResponseMap(d, cr.GetResponse().Map, false); diagnostics != nil {
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -252,8 +266,8 @@ func resourceCredentialStoreVaultRead(ctx context.Context, d *schema.ResourceDat
|
||||
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)
|
||||
if diagnostics := setFromVaultCredentialStoreResponseMap(d, cr.GetResponse().Map, true); diagnostics != nil {
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -352,8 +366,8 @@ func resourceCredentialStoreVaultUpdate(ctx context.Context, d *schema.ResourceD
|
||||
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)
|
||||
if diagnostics := setFromVaultCredentialStoreResponseMap(d, crUpdate.GetResponse().Map, false); diagnostics != nil {
|
||||
return diagnostics
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentials"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
credentialUsernamePasswordUsernameKey = "username"
|
||||
credentialUsernamePasswordPasswordKey = "password"
|
||||
credentialUsernamePasswordPasswordHmacKey = "password_hmac"
|
||||
credentialUsernamePasswordCredentialType = "username_password"
|
||||
)
|
||||
|
||||
func resourceCredentialUsernamePassword() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The username/password credential resource allows you to configure a credential using a username and password pair.",
|
||||
|
||||
CreateContext: resourceCredentialUsernamePasswordCreate,
|
||||
ReadContext: resourceCredentialUsernamePasswordRead,
|
||||
UpdateContext: resourceCredentialUsernamePasswordUpdate,
|
||||
DeleteContext: resourceCredentialUsernamePasswordDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: schema.ImportStatePassthroughContext,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
IDKey: {
|
||||
Description: "The ID of this username/password credential.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
NameKey: {
|
||||
Description: "The name of this username/password credential. Defaults to the resource name.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
DescriptionKey: {
|
||||
Description: "The description of this username/password credential.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialStoreIdKey: {
|
||||
Description: "The credential store in which to save this username/password credential.",
|
||||
Type: schema.TypeString,
|
||||
ForceNew: true,
|
||||
Required: true,
|
||||
},
|
||||
credentialUsernamePasswordUsernameKey: {
|
||||
Description: "The username of this username/password credential.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
credentialUsernamePasswordPasswordKey: {
|
||||
Description: "The password of this username/password credential.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
credentialUsernamePasswordPasswordHmacKey: {
|
||||
Description: "The password hmac.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setFromCredentialUsernamePasswordResponseMap(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(credentialStoreIdKey, raw[credentialStoreIdKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if attrsVal, ok := raw["attributes"]; ok {
|
||||
attrs := attrsVal.(map[string]interface{})
|
||||
if err := d.Set(credentialUsernamePasswordUsernameKey, attrs[credentialUsernamePasswordUsernameKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
statePasswordHmac := d.Get(credentialUsernamePasswordPasswordHmacKey)
|
||||
boundaryPasswordHmac := attrs[credentialUsernamePasswordPasswordHmacKey].(string)
|
||||
if statePasswordHmac.(string) != boundaryPasswordHmac && fromRead {
|
||||
// PasswordHmac has changed in Boundary, therefore the password has changed.
|
||||
// Update password value to force tf to attempt update.
|
||||
if err := d.Set(credentialUsernamePasswordPasswordKey, "(changed in Boundary)"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := d.Set(credentialUsernamePasswordPasswordHmacKey, boundaryPasswordHmac); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialUsernamePasswordCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
|
||||
var opts []credentials.Option
|
||||
if v, ok := d.GetOk(NameKey); ok {
|
||||
opts = append(opts, credentials.WithName(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(DescriptionKey); ok {
|
||||
opts = append(opts, credentials.WithDescription(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialUsernamePasswordUsernameKey); ok {
|
||||
opts = append(opts, credentials.WithUsernamePasswordCredentialUsername(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialUsernamePasswordPasswordKey); ok {
|
||||
opts = append(opts, credentials.WithUsernamePasswordCredentialPassword(v.(string)))
|
||||
}
|
||||
|
||||
var credentialStoreId string
|
||||
retrievedStoreId, ok := d.GetOk(credentialStoreIdKey)
|
||||
if ok {
|
||||
credentialStoreId = retrievedStoreId.(string)
|
||||
} else {
|
||||
return diag.Errorf("credential store id is unset")
|
||||
}
|
||||
|
||||
client := credentials.NewClient(md.client)
|
||||
cr, err := client.Create(ctx, credentialUsernamePasswordCredentialType, credentialStoreId, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error creating credential: %v", err)
|
||||
}
|
||||
if cr == nil {
|
||||
return diag.Errorf("nil credential after create")
|
||||
}
|
||||
|
||||
if err := setFromCredentialUsernamePasswordResponseMap(d, cr.GetResponse().Map, false); err != nil {
|
||||
return diag.Errorf("error generating credential from response map: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialUsernamePasswordRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentials.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: %v", err)
|
||||
}
|
||||
if cr == nil {
|
||||
return diag.Errorf("credential nil after read")
|
||||
}
|
||||
|
||||
if err := setFromCredentialUsernamePasswordResponseMap(d, cr.GetResponse().Map, true); err != nil {
|
||||
return diag.Errorf("error generating credential from response map: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialUsernamePasswordUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentials.NewClient(md.client)
|
||||
|
||||
var opts []credentials.Option
|
||||
if d.HasChange(NameKey) {
|
||||
opts = append(opts, credentials.DefaultName())
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
opts = append(opts, credentials.WithName(nameVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(DescriptionKey) {
|
||||
opts = append(opts, credentials.DefaultDescription())
|
||||
descVal, ok := d.GetOk(DescriptionKey)
|
||||
if ok {
|
||||
opts = append(opts, credentials.WithDescription(descVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialUsernamePasswordUsernameKey) {
|
||||
usernameVal, ok := d.GetOk(credentialUsernamePasswordUsernameKey)
|
||||
if ok {
|
||||
opts = append(opts, credentials.WithUsernamePasswordCredentialUsername(usernameVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialUsernamePasswordPasswordKey) {
|
||||
passwordVal, ok := d.GetOk(credentialUsernamePasswordPasswordKey)
|
||||
if ok {
|
||||
opts = append(opts, credentials.WithUsernamePasswordCredentialPassword(passwordVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts) > 0 {
|
||||
opts = append(opts, credentials.WithAutomaticVersioning(true))
|
||||
crUpdate, err := client.Update(ctx, d.Id(), 0, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating credential: %v", err)
|
||||
}
|
||||
if crUpdate == nil {
|
||||
return diag.Errorf("credential nil after update")
|
||||
}
|
||||
|
||||
if err = setFromCredentialUsernamePasswordResponseMap(d, crUpdate.GetResponse().Map, false); err != nil {
|
||||
return diag.Errorf("error generating credential from response map: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialUsernamePasswordDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentials.NewClient(md.client)
|
||||
|
||||
_, err := client.Delete(ctx, d.Id())
|
||||
if err != nil {
|
||||
return diag.Errorf("error deleting credential: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentials"
|
||||
"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 (
|
||||
usernamePasswordCredResc = "boundary_credential_username_password.example"
|
||||
usernamePasswordCredName = "foo"
|
||||
usernamePasswordCredDesc = "the foo"
|
||||
usernamePasswordCredUsername = "default_username"
|
||||
usernamePasswordCredPassword = "default_password"
|
||||
usernamePasswordCredUpdate = "_random"
|
||||
)
|
||||
|
||||
func usernamePasswordCredResource(name, description, username, password string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "boundary_credential_store_static" "example" {
|
||||
name = "static store name"
|
||||
description = "static store description"
|
||||
scope_id = boundary_scope.proj1.id
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
}
|
||||
|
||||
resource "boundary_credential_username_password" "example" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
credential_store_id = boundary_credential_store_static.example.id
|
||||
username = "%s"
|
||||
password = "%s"
|
||||
}`, name,
|
||||
description,
|
||||
username,
|
||||
password)
|
||||
}
|
||||
|
||||
func TestAccCredentialUsernamePassword(t *testing.T) {
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
res := usernamePasswordCredResource(
|
||||
usernamePasswordCredName,
|
||||
usernamePasswordCredDesc,
|
||||
usernamePasswordCredUsername,
|
||||
usernamePasswordCredPassword,
|
||||
)
|
||||
|
||||
resUpdate := usernamePasswordCredResource(
|
||||
usernamePasswordCredName,
|
||||
usernamePasswordCredDesc,
|
||||
usernamePasswordCredUsername+usernamePasswordCredUpdate,
|
||||
usernamePasswordCredPassword+usernamePasswordCredUpdate,
|
||||
)
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckCredentialUsernamePasswordResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// create
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, res),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(usernamePasswordCredResc, NameKey, usernamePasswordCredName),
|
||||
resource.TestCheckResourceAttr(usernamePasswordCredResc, DescriptionKey, usernamePasswordCredDesc),
|
||||
resource.TestCheckResourceAttr(usernamePasswordCredResc, credentialUsernamePasswordUsernameKey, usernamePasswordCredUsername),
|
||||
resource.TestCheckResourceAttr(usernamePasswordCredResc, credentialUsernamePasswordPasswordKey, usernamePasswordCredPassword),
|
||||
|
||||
testAccCheckCredentialStoreUsernamePasswordHmac(provider),
|
||||
testAccCheckCredentialUsernamePasswordResourceExists(provider, usernamePasswordCredResc),
|
||||
),
|
||||
},
|
||||
importStep(usernamePasswordCredResc, credentialUsernamePasswordPasswordKey),
|
||||
{
|
||||
// update
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(usernamePasswordCredResc, NameKey, usernamePasswordCredName),
|
||||
resource.TestCheckResourceAttr(usernamePasswordCredResc, DescriptionKey, usernamePasswordCredDesc),
|
||||
resource.TestCheckResourceAttr(usernamePasswordCredResc, credentialUsernamePasswordUsernameKey, usernamePasswordCredUsername+usernamePasswordCredUpdate),
|
||||
resource.TestCheckResourceAttr(usernamePasswordCredResc, credentialUsernamePasswordPasswordKey, usernamePasswordCredPassword+usernamePasswordCredUpdate),
|
||||
|
||||
testAccCheckCredentialStoreUsernamePasswordHmac(provider),
|
||||
testAccCheckCredentialUsernamePasswordResourceExists(provider, usernamePasswordCredResc),
|
||||
),
|
||||
},
|
||||
importStep(usernamePasswordCredResc, credentialUsernamePasswordPasswordKey),
|
||||
{
|
||||
// Run a plan only update and verify no changes
|
||||
PlanOnly: true,
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
},
|
||||
importStep(usernamePasswordCredResc, credentialUsernamePasswordPasswordKey),
|
||||
{
|
||||
// update again but apply a preConfig to externally update resource
|
||||
PreConfig: func() { usernamePasswordCredExternalUpdate(t, provider) },
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
},
|
||||
importStep(usernamePasswordCredResc, credentialUsernamePasswordPasswordKey),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func usernamePasswordCredExternalUpdate(t *testing.T, testProvider *schema.Provider) {
|
||||
if storeId == "" {
|
||||
t.Fatal("storeId must be set before testing an external update")
|
||||
}
|
||||
|
||||
md := testProvider.Meta().(*metaData)
|
||||
c := credentials.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 credential options
|
||||
var opts []credentials.Option
|
||||
opts = append(opts, credentials.WithDescription("this is an updated description, my guy"))
|
||||
|
||||
_, 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 testAccCheckCredentialUsernamePasswordResourceExists(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 := credentials.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 testAccCheckCredentialUsernamePasswordResourceDestroy(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_username_password":
|
||||
id := rs.Primary.ID
|
||||
|
||||
c := credentials.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 username password credential %q: %v", id, err)
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckCredentialStoreUsernamePasswordHmac(testProvider *schema.Provider) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[usernamePasswordCredResc]
|
||||
if !ok {
|
||||
return fmt.Errorf("not found: %s", usernamePasswordCredResc)
|
||||
}
|
||||
|
||||
computed := rs.Primary.Attributes["password_hmac"]
|
||||
if len(computed) != 43 {
|
||||
return fmt.Errorf("Computed password hmac not the expected length of 43 characters. hmac: %q", computed)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,7 @@ resource "boundary_group" "proj1" {
|
||||
|
||||
// NOTE: this test also tests out the recovery KMS mechanism.
|
||||
func TestAccGroup(t *testing.T) {
|
||||
wrapper := testWrapper(t, tcRecoveryKey)
|
||||
wrapper := testWrapper(context.Background(), t, tcRecoveryKey)
|
||||
tc := controller.NewTestController(t, append(tcConfig, controller.WithRecoveryKms(wrapper))...)
|
||||
|
||||
defer tc.Shutdown()
|
||||
@@ -122,6 +122,7 @@ func TestAccGroup(t *testing.T) {
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
IsUnitTest: true,
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckGroupResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
|
||||
@@ -1,216 +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` is supported.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setFromHostCatalogResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
|
||||
if err := d.Set(NameKey, raw["name"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(ScopeIdKey, raw["scope_id"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(TypeKey, raw["type"]); err != nil {
|
||||
return err
|
||||
}
|
||||
d.SetId(raw["id"].(string))
|
||||
return nil
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
if err := setFromHostCatalogResponseMap(d, hccr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
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.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 := setFromHostCatalogResponseMap(d, hcrr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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 `boundary_host_catalog_static` instead.",
|
||||
Description: "Deprecated: use `boundary_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
|
||||
}
|
||||
+39
-19
@@ -20,30 +20,50 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
projHostCatalog = fmt.Sprintf(`
|
||||
resource "boundary_host_catalog" "foo" {
|
||||
projHostCatalog = `
|
||||
resource "%s" "foo" {
|
||||
name = "foo"
|
||||
description = "%s"
|
||||
description = "bar"
|
||||
scope_id = boundary_scope.proj1.id
|
||||
type = "static"
|
||||
%s
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
}`, fooHostCatalogDescription)
|
||||
}`
|
||||
|
||||
projHostCatalogUpdate = fmt.Sprintf(`
|
||||
resource "boundary_host_catalog" "foo" {
|
||||
projHostCatalogUpdate = `
|
||||
resource "%s" "foo" {
|
||||
name = "foo"
|
||||
description = "%s"
|
||||
description = "foo bar"
|
||||
scope_id = boundary_scope.proj1.id
|
||||
type = "static"
|
||||
%s
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
}`, fooHostCatalogDescriptionUpdate)
|
||||
}`
|
||||
)
|
||||
|
||||
func TestAccHostCatalogCreate(t *testing.T) {
|
||||
t.Run("non-static", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testAccHostCatalog(t, false)
|
||||
})
|
||||
t.Run("static", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testAccHostCatalog(t, true)
|
||||
})
|
||||
}
|
||||
|
||||
func testAccHostCatalog(t *testing.T, static bool) {
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
resName := "boundary_host_catalog"
|
||||
typeStr := `type = "static"`
|
||||
if static {
|
||||
resName = "boundary_host_catalog_static"
|
||||
typeStr = ""
|
||||
}
|
||||
hc, hcu := fmt.Sprintf(projHostCatalog, resName, typeStr), fmt.Sprintf(projHostCatalogUpdate, resName, typeStr)
|
||||
fooName := fmt.Sprintf("%s.foo", resName)
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
@@ -51,24 +71,24 @@ func TestAccHostCatalogCreate(t *testing.T) {
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// test create
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, projHostCatalog),
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, hc),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckScopeResourceExists(provider, "boundary_scope.org1"),
|
||||
testAccCheckScopeResourceExists(provider, "boundary_scope.proj1"),
|
||||
testAccCheckHostCatalogResourceExists(provider, "boundary_host_catalog.foo"),
|
||||
resource.TestCheckResourceAttr("boundary_host_catalog.foo", DescriptionKey, fooHostCatalogDescription),
|
||||
testAccCheckHostCatalogResourceExists(provider, fooName),
|
||||
resource.TestCheckResourceAttr(fooName, DescriptionKey, fooHostCatalogDescription),
|
||||
),
|
||||
},
|
||||
importStep("boundary_host_catalog.foo"),
|
||||
importStep(fooName),
|
||||
{
|
||||
// test update
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, projHostCatalogUpdate),
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, hcu),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckHostCatalogResourceExists(provider, "boundary_host_catalog.foo"),
|
||||
resource.TestCheckResourceAttr("boundary_host_catalog.foo", DescriptionKey, fooHostCatalogDescriptionUpdate),
|
||||
testAccCheckHostCatalogResourceExists(provider, fooName),
|
||||
resource.TestCheckResourceAttr(fooName, DescriptionKey, fooHostCatalogDescriptionUpdate),
|
||||
),
|
||||
},
|
||||
importStep("boundary_host_catalog.foo"),
|
||||
importStep(fooName),
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -102,7 +122,7 @@ func testAccCheckHostCatalogResourceDestroy(t *testing.T, testProvider *schema.P
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
switch rs.Type {
|
||||
case "boundary_host_catalog":
|
||||
case "boundary_host_catalog", "boundary_host_catalog_static":
|
||||
|
||||
id := rs.Primary.ID
|
||||
hcClient := hostcatalogs.NewClient(md.client)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+87
-33
@@ -11,51 +11,52 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
hostsetHostIdsKey = "host_ids"
|
||||
hostsetTypeStatic = "static"
|
||||
hostSetHostIdsKey = "host_ids"
|
||||
hostSetTypeStatic = "static"
|
||||
)
|
||||
|
||||
func resourceHostset() *schema.Resource {
|
||||
func resourceHostSet() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
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.",
|
||||
DeprecationMessage: "Deprecated: use `boundary_host_set_static` instead.",
|
||||
Description: "Deprecated: use `boundary_host_set_static` instead.",
|
||||
|
||||
CreateContext: resourceHostsetCreate,
|
||||
ReadContext: resourceHostsetRead,
|
||||
UpdateContext: resourceHostsetUpdate,
|
||||
DeleteContext: resourceHostsetDelete,
|
||||
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 hostset.",
|
||||
Description: "The ID of the host set.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
NameKey: {
|
||||
Description: "The hostset name. Defaults to the resource name.",
|
||||
Description: "The host set name. Defaults to the resource name.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
DescriptionKey: {
|
||||
Description: "The hostset description.",
|
||||
Description: "The host set description.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
TypeKey: {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
HostCatalogIdKey: {
|
||||
Description: "The catalog for the hostset.",
|
||||
Description: "The type of host set",
|
||||
Type: schema.TypeString,
|
||||
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.",
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
@@ -65,7 +66,60 @@ func resourceHostset() *schema.Resource {
|
||||
}
|
||||
}
|
||||
|
||||
func setFromHostSetResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
|
||||
func resourceHostSetStatic() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "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.",
|
||||
|
||||
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 setFromHostSetStaticResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
|
||||
if err := d.Set(NameKey, raw["name"]); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -78,14 +132,14 @@ func setFromHostSetResponseMap(d *schema.ResourceData, raw map[string]interface{
|
||||
if err := d.Set(TypeKey, raw["type"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(hostsetHostIdsKey, raw["host_ids"]); err != nil {
|
||||
if err := d.Set(hostSetHostIdsKey, raw["host_ids"]); err != nil {
|
||||
return err
|
||||
}
|
||||
d.SetId(raw["id"].(string))
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceHostsetCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
func resourceHostSetStaticCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
|
||||
var hostsetHostCatalogId string
|
||||
@@ -96,7 +150,7 @@ func resourceHostsetCreate(ctx context.Context, d *schema.ResourceData, meta int
|
||||
}
|
||||
|
||||
var hostIds []string
|
||||
if hostIdsVal, ok := d.GetOk(hostsetHostIdsKey); ok {
|
||||
if hostIdsVal, ok := d.GetOk(hostSetHostIdsKey); ok {
|
||||
list := hostIdsVal.(*schema.Set).List()
|
||||
hostIds = make([]string, 0, len(list))
|
||||
for _, i := range list {
|
||||
@@ -115,7 +169,7 @@ func resourceHostsetCreate(ctx context.Context, d *schema.ResourceData, meta int
|
||||
switch typeStr {
|
||||
// NOTE: When other types are added, ensure they don't accept hostSetIds if
|
||||
// it's not allowed
|
||||
case hostsetTypeStatic:
|
||||
case hostSetTypeStatic:
|
||||
default:
|
||||
return diag.Errorf("invalid type provided")
|
||||
}
|
||||
@@ -154,14 +208,14 @@ func resourceHostsetCreate(ctx context.Context, d *schema.ResourceData, meta int
|
||||
raw = hsshr.GetResponse().Map
|
||||
}
|
||||
|
||||
if err := setFromHostSetResponseMap(d, raw); err != nil {
|
||||
if err := setFromHostSetStaticResponseMap(d, raw); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
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)
|
||||
hsClient := hostsets.NewClient(md.client)
|
||||
|
||||
@@ -177,14 +231,14 @@ func resourceHostsetRead(ctx context.Context, d *schema.ResourceData, meta inter
|
||||
return diag.Errorf("host set nil after read")
|
||||
}
|
||||
|
||||
if err := setFromHostSetResponseMap(d, hsrr.GetResponse().Map); err != nil {
|
||||
if err := setFromHostSetStaticResponseMap(d, hsrr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
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)
|
||||
hsClient := hostsets.NewClient(md.client)
|
||||
|
||||
@@ -233,9 +287,9 @@ func resourceHostsetUpdate(ctx context.Context, d *schema.ResourceData, meta int
|
||||
|
||||
// The above call may not actually happen, so we use d.Id() and automatic
|
||||
// versioning here
|
||||
if d.HasChange(hostsetHostIdsKey) {
|
||||
if d.HasChange(hostSetHostIdsKey) {
|
||||
var hostIds []string
|
||||
if hostIdsVal, ok := d.GetOk(hostsetHostIdsKey); ok {
|
||||
if hostIdsVal, ok := d.GetOk(hostSetHostIdsKey); ok {
|
||||
hosts := hostIdsVal.(*schema.Set).List()
|
||||
for _, host := range hosts {
|
||||
hostIds = append(hostIds, host.(string))
|
||||
@@ -245,7 +299,7 @@ func resourceHostsetUpdate(ctx context.Context, d *schema.ResourceData, meta int
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating hosts in host set: %v", err)
|
||||
}
|
||||
if err := d.Set(hostsetHostIdsKey, hostIds); err != nil {
|
||||
if err := d.Set(hostSetHostIdsKey, hostIds); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
@@ -253,7 +307,7 @@ func resourceHostsetUpdate(ctx context.Context, d *schema.ResourceData, meta int
|
||||
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)
|
||||
hsClient := hostsets.NewClient(md.client)
|
||||
|
||||
+71
-64
@@ -14,97 +14,104 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
||||
)
|
||||
|
||||
var (
|
||||
fooHostset = `
|
||||
resource "boundary_host_catalog" "foo" {
|
||||
scope_id = boundary_scope.proj1.id
|
||||
type = "static"
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
func TestAccHostSetStatic(t *testing.T) {
|
||||
t.Run("non-static", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testAccHostSetStatic(t, false)
|
||||
})
|
||||
t.Run("static", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testAccHostSetStatic(t, true)
|
||||
})
|
||||
}
|
||||
|
||||
resource "boundary_host" "foo" {
|
||||
type = "static"
|
||||
host_catalog_id = boundary_host_catalog.foo.id
|
||||
address = "10.0.0.1"
|
||||
}
|
||||
func testAccHostSetStatic(t *testing.T, static bool) {
|
||||
catalogBlock := `
|
||||
resource "%s" "foo" {
|
||||
%s
|
||||
scope_id = boundary_scope.proj1.id
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
}`
|
||||
|
||||
resource "boundary_host_set" "foo" {
|
||||
type = "static"
|
||||
name = "test"
|
||||
description = "test hostset"
|
||||
host_catalog_id = boundary_host_catalog.foo.id
|
||||
host_ids = [boundary_host.foo.id]
|
||||
}`
|
||||
hostBlock := `
|
||||
resource "%s" "foo" {
|
||||
host_catalog_id = %s.foo.id
|
||||
%s
|
||||
address = "10.0.0.1"
|
||||
}`
|
||||
|
||||
fooHostsetUpdate = `
|
||||
resource "boundary_host_catalog" "foo" {
|
||||
scope_id = boundary_scope.proj1.id
|
||||
type = "static"
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
}
|
||||
hostSetBlock := `
|
||||
resource "%s" "foo" {
|
||||
host_catalog_id = %s.foo.id
|
||||
name = "test"
|
||||
description = "test hostset"
|
||||
%s
|
||||
host_ids = [%s.foo.id%s]
|
||||
}`
|
||||
|
||||
resource "boundary_host" "foo" {
|
||||
type = "static"
|
||||
host_catalog_id = boundary_host_catalog.foo.id
|
||||
address = "10.0.0.1"
|
||||
}
|
||||
host2Block := `
|
||||
resource "%s" "bar" {
|
||||
host_catalog_id = %s.foo.id
|
||||
%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...)
|
||||
defer tc.Shutdown()
|
||||
// org := iam.TestOrg(t, tc.IamRepo())
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
catalogName := "boundary_host_catalog"
|
||||
hostName := "boundary_host"
|
||||
setName := "boundary_host_set"
|
||||
typeStr := `type = "static"`
|
||||
if static {
|
||||
catalogName = "boundary_host_catalog_static"
|
||||
hostName = "boundary_host_static"
|
||||
setName = "boundary_host_set_static"
|
||||
typeStr = ""
|
||||
}
|
||||
fooSetName := fmt.Sprintf("%s.foo", setName)
|
||||
fooHostName := fmt.Sprintf("%s.foo", hostName)
|
||||
barHostName := fmt.Sprintf("%s.bar", hostName)
|
||||
hcBlock := fmt.Sprintf(catalogBlock, catalogName, typeStr)
|
||||
hBlock := fmt.Sprintf(hostBlock, hostName, catalogName, typeStr)
|
||||
h2Block := fmt.Sprintf(host2Block, hostName, catalogName, typeStr)
|
||||
hsBlock := fmt.Sprintf(hostSetBlock, setName, catalogName, typeStr, hostName, "")
|
||||
hsUpdateBlock := fmt.Sprintf(hostSetBlock, setName, catalogName, typeStr, hostName, fmt.Sprintf(`, %s.bar.id`, hostName))
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckHostsetResourceDestroy(t, provider),
|
||||
CheckDestroy: testAccCheckHostSetStaticResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// test project hostset create
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, fooHostset),
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, hcBlock, hBlock, hsBlock),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckHostsetResourceExists(provider, "boundary_host_set.foo"),
|
||||
testAccCheckHostsetHostIDsSet(provider, "boundary_host_set.foo", []string{"boundary_host.foo"}),
|
||||
resource.TestCheckResourceAttr("boundary_host_set.foo", "name", "test"),
|
||||
resource.TestCheckResourceAttr("boundary_host_set.foo", "description", "test hostset"),
|
||||
testAccCheckHostSetStaticResourceExists(provider, fooSetName),
|
||||
testAccCheckHostSetStaticHostIDsSet(provider, fooSetName, []string{fooHostName}),
|
||||
resource.TestCheckResourceAttr(fooSetName, "name", "test"),
|
||||
resource.TestCheckResourceAttr(fooSetName, "description", "test hostset"),
|
||||
),
|
||||
},
|
||||
importStep("boundary_host_set.foo"),
|
||||
importStep(fooSetName),
|
||||
{
|
||||
// test project hostset update
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, fooHostsetUpdate),
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, hcBlock, hBlock, h2Block, hsUpdateBlock),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckHostsetResourceExists(provider, "boundary_host_set.foo"),
|
||||
testAccCheckHostsetHostIDsSet(provider, "boundary_host_set.foo", []string{"boundary_host.foo", "boundary_host.bar"}),
|
||||
resource.TestCheckResourceAttr("boundary_host_set.foo", "name", "test"),
|
||||
resource.TestCheckResourceAttr("boundary_host_set.foo", "description", "test hostset"),
|
||||
testAccCheckHostSetStaticResourceExists(provider, fooSetName),
|
||||
testAccCheckHostSetStaticHostIDsSet(provider, fooSetName, []string{fooHostName, barHostName}),
|
||||
resource.TestCheckResourceAttr(fooSetName, "name", "test"),
|
||||
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 {
|
||||
rs, ok := s.RootModule().Resources[name]
|
||||
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 {
|
||||
rs, ok := s.RootModule().Resources[name]
|
||||
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 {
|
||||
if testProvider.Meta() == nil {
|
||||
t.Fatal("got nil provider metadata")
|
||||
@@ -17,14 +17,13 @@ const (
|
||||
|
||||
func resourceHost() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
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.",
|
||||
DeprecationMessage: "Deprecated: use `boundary_host_static` instead.",
|
||||
Description: "Deprecated: use `boundary_host_static` instead.",
|
||||
|
||||
CreateContext: resourceHostCreate,
|
||||
ReadContext: resourceHostRead,
|
||||
UpdateContext: resourceHostUpdate,
|
||||
DeleteContext: resourceHostDelete,
|
||||
CreateContext: resourceHostStaticCreate,
|
||||
ReadContext: resourceHostStaticRead,
|
||||
UpdateContext: resourceHostStaticUpdate,
|
||||
DeleteContext: resourceHostStaticDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: schema.ImportStatePassthroughContext,
|
||||
},
|
||||
@@ -46,9 +45,60 @@ func resourceHost() *schema.Resource {
|
||||
Optional: true,
|
||||
},
|
||||
TypeKey: {
|
||||
Description: "The type of host",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
HostCatalogIdKey: {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
hostAddressKey: {
|
||||
Description: "The static address of the host resource as `<IP>` (note: port assignment occurs in the target resource definition, do not add :port here) or a domain name.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceHostStatic() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The static host resource allows you to configure a Boundary static host. Hosts are " +
|
||||
"always part of a project, so a project resource should be used inline or you should have " +
|
||||
"the project ID in hand to successfully configure a host.",
|
||||
|
||||
CreateContext: resourceHostStaticCreate,
|
||||
ReadContext: resourceHostStaticRead,
|
||||
UpdateContext: resourceHostStaticUpdate,
|
||||
DeleteContext: resourceHostStaticDelete,
|
||||
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,
|
||||
@@ -91,7 +141,7 @@ func setFromHostResponseMap(d *schema.ResourceData, raw map[string]interface{})
|
||||
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)
|
||||
|
||||
var hostCatalogId string
|
||||
@@ -158,7 +208,7 @@ func resourceHostCreate(ctx context.Context, d *schema.ResourceData, meta interf
|
||||
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)
|
||||
hClient := hosts.NewClient(md.client)
|
||||
|
||||
@@ -181,7 +231,7 @@ func resourceHostRead(ctx context.Context, d *schema.ResourceData, meta interfac
|
||||
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)
|
||||
hClient := hosts.NewClient(md.client)
|
||||
|
||||
@@ -252,7 +302,7 @@ func resourceHostUpdate(ctx context.Context, d *schema.ResourceData, meta interf
|
||||
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)
|
||||
hClient := hosts.NewClient(md.client)
|
||||
|
||||
Loaded 100 of 155 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user