Add dynamic host plugin catalog/set (#159)
Co-authored-by: Todd <T.Alan.Knight@gmail.com> Co-authored-by: Louis Ruch <louisruch@gmail.com>
This commit is contained in:
29 files changed
+2930
-716
No files matched your search
@@ -18,20 +18,8 @@ jobs:
|
||||
make install-go
|
||||
source ~/.bashrc
|
||||
go mod download
|
||||
- run:
|
||||
name: "Initialize Test Database"
|
||||
command: |
|
||||
sudo apt-get update && sudo apt-get install -y postgresql-client jq
|
||||
source ~/.bashrc
|
||||
make test-database-up
|
||||
until pg_isready -h 127.0.0.1; do docker container inspect boundary-sql-tests &> /dev/null || exit -1; sleep 1; done
|
||||
- run:
|
||||
name: "Run Acceptance Tests"
|
||||
command: |
|
||||
source ~/.bashrc
|
||||
make testacc-ci
|
||||
- run:
|
||||
name: "Cleanup"
|
||||
when: always
|
||||
command: |
|
||||
make test-database-down
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.16.3
|
||||
1.17.5
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.16
|
||||
FROM golang:1.17
|
||||
|
||||
WORKDIR /go/src/terraform-provider-boundary
|
||||
COPY . .
|
||||
|
||||
@@ -34,7 +34,7 @@ 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
|
||||
|
||||
@@ -62,7 +62,7 @@ rm-id-flag-from-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/postgres:/migrations"
|
||||
@echo "Using volume: $(GOMODCACHE)/github.com/hashicorp/boundary@$(BOUNDARY_VERSION)/internal/db/schema/migrations:/migrations"
|
||||
@docker run \
|
||||
$(DOCKER_ARGS) \
|
||||
--name boundary-sql-tests \
|
||||
@@ -72,7 +72,7 @@ test-database-up:
|
||||
-e POSTGRES_DB=boundary \
|
||||
-e PGDATA=/pgdata \
|
||||
--mount type=tmpfs,destination=/pgdata \
|
||||
-v "$(GOMODCACHE)/github.com/hashicorp/boundary@$(BOUNDARY_VERSION)/internal/db/schema/migrations/postgres":/migrations \
|
||||
-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
|
||||
|
||||
@@ -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.17
|
||||
|
||||
Building The Provider
|
||||
---------------------
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
VERSION="1.16"
|
||||
VERSION="1.17.5"
|
||||
|
||||
[ -z "$GOROOT" ] && GOROOT="$HOME/.go"
|
||||
[ -z "$GOPATH" ] && GOPATH="$HOME/go"
|
||||
|
||||
@@ -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 resource_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 `resource_host_static` instead.
|
||||
|
||||
## Example Usage
|
||||
|
||||
@@ -49,7 +49,7 @@ resource "boundary_host" "example" {
|
||||
### Required
|
||||
|
||||
- **host_catalog_id** (String)
|
||||
- **type** (String)
|
||||
- **type** (String) The type of host
|
||||
|
||||
### Optional
|
||||
|
||||
|
||||
@@ -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 resource_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 `resource_host_catalog_static` instead.
|
||||
|
||||
## Example Usage
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_host_catalog_plugin Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The host catalog resource allows you to configure a Boundary plugin-type host catalog. Host catalogs are always part of a project, so a project resource should be used inline or you should have the project ID in hand to successfully configure a host catalog.
|
||||
---
|
||||
|
||||
# boundary_host_catalog_plugin (Resource)
|
||||
|
||||
The host catalog resource allows you to configure a Boundary plugin-type host catalog. Host catalogs are always part of a project, so a project resource should be used inline or you should have the project ID in hand to successfully configure a host catalog.
|
||||
|
||||
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- **scope_id** (String) The scope ID in which the resource is created.
|
||||
|
||||
### Optional
|
||||
|
||||
- **attributes_json** (String) The attributes for the host catalog. Either values encoded with the "jsonencode" function, pre-escaped JSON string, or a file:// or env:// path. Set to a string "null" or remove the block to clear all attributes in the host catalog.
|
||||
- **description** (String) The host catalog description.
|
||||
- **internal_force_update** (String) Internal only. Used to force update so that we can always check the value of secrets.
|
||||
- **internal_hmac_used_for_secrets_config_hmac** (String) Internal only. The Boundary-provided HMAC used to calculate the current value of the HMAC'd config. Used for drift detection.
|
||||
- **internal_secrets_config_hmac** (String) Internal only. HMAC of (serverSecretsHmac + config secrets). Used for proper secrets handling.
|
||||
- **name** (String) The host catalog name. Defaults to the resource name.
|
||||
- **plugin_id** (String) The ID of the plugin that should back the resource. This or plugin_name must be defined.
|
||||
- **plugin_name** (String) The name of the plugin that should back the resource. This or plugin_id must be defined.
|
||||
- **secrets_hmac** (String) The HMAC'd secrets value returned from the server.
|
||||
- **secrets_json** (String, Sensitive) The secrets for the host catalog. Either values encoded with the "jsonencode" function, pre-escaped JSON string, or a file:// or env:// path. Set to a string "null" to clear any existing values. NOTE: Unlike "attributes_json", removing this block will NOT clear secrets from the host catalog; this allows injecting secrets for one call, then removing them for storage.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the host catalog.
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_host_catalog_static Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The static host catalog resource allows you to configure a Boundary static-type host catalog. Host catalogs are always part of a project, so a project resource should be used inline or you should have the project ID in hand to successfully configure a host catalog.
|
||||
---
|
||||
|
||||
# boundary_host_catalog_static (Resource)
|
||||
|
||||
The static host catalog resource allows you to configure a Boundary static-type host catalog. Host catalogs are always part of a project, so a project resource should be used inline or you should have the project ID in hand to successfully configure a host catalog.
|
||||
|
||||
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- **scope_id** (String) The scope ID in which the resource is created.
|
||||
|
||||
### Optional
|
||||
|
||||
- **description** (String) The host catalog description.
|
||||
- **name** (String) The host catalog name. Defaults to the resource name.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the host catalog.
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
page_title: "boundary_host_set Resource - terraform-provider-boundary"
|
||||
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 resource_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 `resource_host_set_static` instead.
|
||||
|
||||
## Example Usage
|
||||
|
||||
@@ -63,18 +63,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.
|
||||
- **description** (String) The host set 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.
|
||||
- **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,35 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_host_set_plugin Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The hostsetplugin resource allows you to configure a Boundary host set. Host sets are always part of a host catalog, so a host catalog resource should be used inline or you should have the host catalog ID in hand to successfully configure a host set.
|
||||
---
|
||||
|
||||
# boundary_host_set_plugin (Resource)
|
||||
|
||||
The host_set_plugin resource allows you to configure a Boundary host set. Host sets are always part of a host catalog, so a host catalog resource should be used inline or you should have the host catalog ID in hand to successfully configure a host set.
|
||||
|
||||
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- **host_catalog_id** (String) The catalog for the host set.
|
||||
|
||||
### Optional
|
||||
|
||||
- **attributes_json** (String) The attributes for the host set. Either values encoded with the "jsonencode" function, pre-escaped JSON string, or a file:// or env:// path. Set to a string "null" or remove the block to clear all attributes in the host set.
|
||||
- **description** (String) The host set description.
|
||||
- **name** (String) The host set name. Defaults to the resource name.
|
||||
- **preferred_endpoints** (List of String) The ordered list of preferred endpoints.
|
||||
- **sync_interval_seconds** (Number) The value to set for the sync interval seconds.
|
||||
- **type** (String) The type of host set
|
||||
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the host set.
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_host_set_static Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The hostsetstatic resource allows you to configure a Boundary host set. Host sets are always part of a host catalog, so a host catalog resource should be used inline or you should have the host catalog ID in hand to successfully configure a host set.
|
||||
---
|
||||
|
||||
# boundary_host_set_static (Resource)
|
||||
|
||||
The host_set_static resource allows you to configure a Boundary host set. Host sets are always part of a host catalog, so a host catalog resource should be used inline or you should have the host catalog ID in hand to successfully configure a host set.
|
||||
|
||||
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- **host_catalog_id** (String) The catalog for the host set.
|
||||
|
||||
### Optional
|
||||
|
||||
- **description** (String) The host set description.
|
||||
- **host_ids** (Set of String) The list of host IDs contained in this set.
|
||||
- **name** (String) The host set name. Defaults to the resource name.
|
||||
- **type** (String) The type of host set
|
||||
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the host set.
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_host_static Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The static host resource allows you to configure a Boundary static host. Hosts are always part of a project, so a project resource should be used inline or you should have the project ID in hand to successfully configure a host.
|
||||
---
|
||||
|
||||
# boundary_host_static (Resource)
|
||||
|
||||
The static host resource allows you to configure a Boundary static host. Hosts are always part of a project, so a project resource should be used inline or you should have the project ID in hand to successfully configure a host.
|
||||
|
||||
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- **host_catalog_id** (String)
|
||||
|
||||
### Optional
|
||||
|
||||
- **address** (String) The static address of the host resource as `<IP>` (note: port assignment occurs in the target resource definition, do not add :port here) or a domain name.
|
||||
- **description** (String) The host description.
|
||||
- **name** (String) The host name. Defaults to the resource name.
|
||||
- **type** (String) The type of host
|
||||
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the host.
|
||||
|
||||
|
||||
@@ -1,34 +1,206 @@
|
||||
module github.com/hashicorp/terraform-provider-boundary
|
||||
|
||||
go 1.16
|
||||
go 1.17
|
||||
|
||||
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.6.2
|
||||
github.com/hashicorp/boundary/api v0.0.19
|
||||
github.com/hashicorp/boundary/sdk v0.0.11
|
||||
github.com/hashicorp/boundary v0.7.4
|
||||
github.com/hashicorp/boundary/api v0.0.21
|
||||
github.com/hashicorp/boundary/sdk v0.0.13
|
||||
github.com/hashicorp/cap v0.1.1
|
||||
github.com/hashicorp/go-immutable-radix v1.3.0 // indirect
|
||||
github.com/hashicorp/go-kms-wrapping v0.6.7
|
||||
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.5.0
|
||||
github.com/hashicorp/go-cty v1.4.1-0.20200723130312-85980079f637
|
||||
github.com/hashicorp/go-kms-wrapping v0.6.8
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.2
|
||||
github.com/hashicorp/terraform-plugin-docs v0.5.1
|
||||
github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.1
|
||||
github.com/hashicorp/yamux v0.0.0-20200609203250-aecfd211c9ce // indirect
|
||||
github.com/kr/pretty v0.3.0
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
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-20210915214749-c084706c2272
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
github.com/stretchr/testify v1.7.0
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.81.0 // indirect
|
||||
cloud.google.com/go/storage v1.10.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v58.0.0+incompatible // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.21 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.14 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
|
||||
github.com/Microsoft/go-winio v0.4.16 // indirect
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
github.com/agext/levenshtein v1.2.2 // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190620160927-9418d7b0cd0f // indirect
|
||||
github.com/apparentlymart/go-textseg v1.0.0 // indirect
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
||||
github.com/armon/go-metrics v0.3.9 // indirect
|
||||
github.com/armon/go-radix v1.0.0 // indirect
|
||||
github.com/aws/aws-sdk-go v1.40.55 // indirect
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
|
||||
github.com/bgentry/speakeasy v0.1.0 // indirect
|
||||
github.com/cenkalti/backoff/v3 v3.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.0 // indirect
|
||||
github.com/containerd/containerd v1.4.3 // indirect
|
||||
github.com/containerd/continuity v0.0.0-20200709052629-daa8e1ccc0bc // indirect
|
||||
github.com/coreos/go-oidc/v3 v3.0.0 // indirect
|
||||
github.com/danieljoos/wincred v1.1.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/docker/cli v20.10.7+incompatible // indirect
|
||||
github.com/docker/docker v20.10.7+incompatible // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect
|
||||
github.com/godbus/dbus/v5 v5.0.4 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-migrate/migrate/v4 v4.14.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.1 // indirect
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0 // indirect
|
||||
github.com/hashicorp/dbassert v0.0.0-20210708202608-ecf920cf1ed8 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/eventlogger v0.1.1-0.20211106154408-4ff8da3a890c // indirect
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.6-0.20211027211326-5db60a48f239 // indirect
|
||||
github.com/hashicorp/go-bexpr v0.1.10 // indirect
|
||||
github.com/hashicorp/go-checkpoint v0.5.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-dbw v0.0.0-20211215222256-2ff0d37184ff // indirect
|
||||
github.com/hashicorp/go-getter v1.5.3 // indirect
|
||||
github.com/hashicorp/go-hclog v1.0.0 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-plugin v1.4.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||
github.com/hashicorp/go-safetemp v1.0.0 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/configutil v0.1.2 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/gatedwriter v0.1.1 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/kv-builder v0.1.1 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.3-0.20211129134915-8f7e6fb52622 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1 // indirect
|
||||
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.2 // indirect
|
||||
github.com/hashicorp/go-version v1.3.0 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.3 // indirect
|
||||
github.com/hashicorp/hc-install v0.3.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hashicorp/hcl/v2 v2.3.0 // indirect
|
||||
github.com/hashicorp/logutils v1.0.0 // indirect
|
||||
github.com/hashicorp/terraform-exec v0.15.0 // indirect
|
||||
github.com/hashicorp/terraform-json v0.13.0 // indirect
|
||||
github.com/hashicorp/terraform-plugin-go v0.5.0 // indirect
|
||||
github.com/hashicorp/terraform-plugin-log v0.2.0 // indirect
|
||||
github.com/hashicorp/terraform-registry-address v0.0.0-20210412075316-9b2996cce896 // indirect
|
||||
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect
|
||||
github.com/hashicorp/vault/api v1.1.1 // indirect
|
||||
github.com/hashicorp/vault/sdk v0.2.1 // indirect
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgconn v1.10.1 // indirect
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.2.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||
github.com/jackc/pgtype v1.9.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.14.0 // indirect
|
||||
github.com/jefferai/go-libsecret v0.0.0-20210105015933-d08a58b018bc // indirect
|
||||
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f // indirect
|
||||
github.com/jefferai/keyring v1.1.7-0.20210105022822-8749b3d9ce79 // indirect
|
||||
github.com/jinzhu/gorm v1.9.12 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.3 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/json-iterator/go v1.1.11 // indirect
|
||||
github.com/jstemmer/go-junit-report v0.9.1 // indirect
|
||||
github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d // indirect
|
||||
github.com/klauspost/compress v1.13.5 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/lib/pq v1.10.2 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mattn/go-sqlite3 v2.0.1+incompatible // indirect
|
||||
github.com/mitchellh/cli v1.1.2 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.2 // indirect
|
||||
github.com/mitchellh/pointerstructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||
github.com/mtibben/percent v0.2.1 // indirect
|
||||
github.com/oklog/run v1.0.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||
github.com/opencontainers/runc v1.0.0-rc9 // indirect
|
||||
github.com/oracle/oci-go-sdk v13.1.0+incompatible // indirect
|
||||
github.com/ory/dockertest/v3 v3.7.0 // indirect
|
||||
github.com/pierrec/lz4 v2.5.2+incompatible // indirect
|
||||
github.com/pires/go-proxyproto v0.6.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/posener/complete v1.2.3 // indirect
|
||||
github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 // indirect
|
||||
github.com/russross/blackfriday v1.6.0 // indirect
|
||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/sirupsen/logrus v1.7.0 // indirect
|
||||
github.com/ulikunitz/xz v0.5.8 // indirect
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/xo/dburl v0.9.0 // indirect
|
||||
github.com/zalando/go-keyring v0.1.1 // indirect
|
||||
github.com/zclconf/go-cty v1.10.0 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
|
||||
golang.org/x/mod v0.5.1 // indirect
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
|
||||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486 // indirect
|
||||
golang.org/x/term v0.0.0-20210916214954-140adaaadfaf // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
|
||||
golang.org/x/tools v0.1.8-0.20211102182255-bb4add04ddef // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/api v0.44.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83 // indirect
|
||||
google.golang.org/grpc v1.41.0-dev.0.20210907181116-2f3355d2244e // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
gorm.io/driver/postgres v1.2.2 // indirect
|
||||
gorm.io/driver/sqlite v1.2.4 // indirect
|
||||
gorm.io/gorm v1.22.3 // indirect
|
||||
)
|
||||
@@ -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"
|
||||
)
|
||||
@@ -56,7 +56,6 @@ func New() *schema.Provider {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Description: "When set to true, does not validate the Boundary API endpoint certificate",
|
||||
DefaultFunc: schema.EnvDefaultFunc("BOUNDARY_TLS_INSECURE", false),
|
||||
},
|
||||
},
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
@@ -71,8 +70,13 @@ func New() *schema.Provider {
|
||||
"boundary_managed_group": resourceManagedGroup(),
|
||||
"boundary_group": resourceGroup(),
|
||||
"boundary_host": resourceHost(),
|
||||
"boundary_host_static": resourceHostStatic(),
|
||||
"boundary_host_catalog": resourceHostCatalog(),
|
||||
"boundary_host_set": resourceHostset(),
|
||||
"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(),
|
||||
|
||||
@@ -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 `resource_host_catalog_static` instead.",
|
||||
Description: "Deprecated: use `resource_host_catalog_static` instead.",
|
||||
|
||||
CreateContext: resourceHostCatalogStaticCreate(true),
|
||||
ReadContext: resourceHostCatalogStaticRead(true),
|
||||
UpdateContext: resourceHostCatalogStaticUpdate(true),
|
||||
DeleteContext: resourceHostCatalogStaticDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: schema.ImportStatePassthroughContext,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
IDKey: {
|
||||
Description: "The ID of the host catalog.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
NameKey: {
|
||||
Description: "The host catalog name. Defaults to the resource name.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
DescriptionKey: {
|
||||
Description: "The host catalog description.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
ScopeIdKey: {
|
||||
Description: "The scope ID in which the resource is created.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
TypeKey: {
|
||||
Description: "The host catalog type. Only `static` is supported.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceHostCatalogStatic() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The static host catalog resource allows you to configure a Boundary static-type host catalog. Host " +
|
||||
"catalogs are always part of a project, so a project resource should be used inline or you " +
|
||||
"should have the project ID in hand to successfully configure a host catalog.",
|
||||
|
||||
CreateContext: resourceHostCatalogStaticCreate(false),
|
||||
ReadContext: resourceHostCatalogStaticRead(false),
|
||||
UpdateContext: resourceHostCatalogStaticUpdate(false),
|
||||
DeleteContext: resourceHostCatalogStaticDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: schema.ImportStatePassthroughContext,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
IDKey: {
|
||||
Description: "The ID of the host catalog.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
NameKey: {
|
||||
Description: "The host catalog name. Defaults to the resource name.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
DescriptionKey: {
|
||||
Description: "The host catalog description.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
ScopeIdKey: {
|
||||
Description: "The scope ID in which the resource is created.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setFromHostCatalogStaticResponseMap(d *schema.ResourceData, raw map[string]interface{}, hasTypeKey bool) error {
|
||||
if err := d.Set(NameKey, raw["name"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(ScopeIdKey, raw["scope_id"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if hasTypeKey {
|
||||
if err := d.Set(TypeKey, raw["type"]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
d.SetId(raw["id"].(string))
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceHostCatalogStaticCreate(hasTypeKey bool) schema.CreateContextFunc {
|
||||
return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
|
||||
var scopeId string
|
||||
if scopeIdVal, ok := d.GetOk(ScopeIdKey); ok {
|
||||
scopeId = scopeIdVal.(string)
|
||||
} else {
|
||||
return diag.Errorf("no scope ID provided")
|
||||
}
|
||||
|
||||
typeStr := hostCatalogTypeStatic
|
||||
if hasTypeKey {
|
||||
// Perform backwards-compat validation
|
||||
if typeVal, ok := d.GetOk(TypeKey); ok {
|
||||
typeStr = typeVal.(string)
|
||||
} else {
|
||||
return diag.Errorf("no type provided")
|
||||
}
|
||||
switch typeStr {
|
||||
case hostCatalogTypeStatic:
|
||||
default:
|
||||
return diag.Errorf("invalid type provided")
|
||||
}
|
||||
}
|
||||
|
||||
opts := []hostcatalogs.Option{}
|
||||
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
nameStr := nameVal.(string)
|
||||
opts = append(opts, hostcatalogs.WithName(nameStr))
|
||||
}
|
||||
|
||||
descVal, ok := d.GetOk(DescriptionKey)
|
||||
if ok {
|
||||
descStr := descVal.(string)
|
||||
opts = append(opts, hostcatalogs.WithDescription(descStr))
|
||||
}
|
||||
|
||||
hcClient := hostcatalogs.NewClient(md.client)
|
||||
|
||||
hccr, err := hcClient.Create(ctx, typeStr, scopeId, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error creating host catalog: %v", err)
|
||||
}
|
||||
if hccr == nil {
|
||||
return diag.Errorf("nil host catalog after create")
|
||||
}
|
||||
|
||||
if err := setFromHostCatalogStaticResponseMap(d, hccr.GetResponse().Map, hasTypeKey); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func resourceHostCatalogStaticRead(hasTypeKey bool) schema.ReadContextFunc {
|
||||
return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
hcClient := hostcatalogs.NewClient(md.client)
|
||||
|
||||
hcrr, err := hcClient.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
return diag.Errorf("error reading host catalog: %v", err)
|
||||
}
|
||||
if hcrr == nil {
|
||||
return diag.Errorf("host catalog nil after read")
|
||||
}
|
||||
|
||||
if err := setFromHostCatalogStaticResponseMap(d, hcrr.GetResponse().Map, hasTypeKey); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func resourceHostCatalogStaticUpdate(hasTypeKey bool) schema.UpdateContextFunc {
|
||||
return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
hcClient := hostcatalogs.NewClient(md.client)
|
||||
|
||||
opts := []hostcatalogs.Option{}
|
||||
|
||||
if d.HasChange(NameKey) {
|
||||
opts = append(opts, hostcatalogs.DefaultName())
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
nameStr := nameVal.(string)
|
||||
opts = append(opts, hostcatalogs.WithName(nameStr))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(DescriptionKey) {
|
||||
opts = append(opts, hostcatalogs.DefaultDescription())
|
||||
descVal, ok := d.GetOk(DescriptionKey)
|
||||
if ok {
|
||||
descStr := descVal.(string)
|
||||
opts = append(opts, hostcatalogs.WithDescription(descStr))
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts) > 0 {
|
||||
opts = append(opts, hostcatalogs.WithAutomaticVersioning(true))
|
||||
hcrr, err := hcClient.Update(ctx, d.Id(), 0, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating host catalog: %v", err)
|
||||
}
|
||||
if hcrr == nil {
|
||||
return diag.Errorf("host catalog nil after update")
|
||||
}
|
||||
if err := setFromHostCatalogStaticResponseMap(d, hcrr.GetResponse().Map, hasTypeKey); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func resourceHostCatalogStaticDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
hcClient := hostcatalogs.NewClient(md.client)
|
||||
|
||||
_, err := hcClient.Delete(ctx, d.Id())
|
||||
if err != nil {
|
||||
return diag.Errorf("error deleting host catalog: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+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 `resource_host_set_static` instead.",
|
||||
Description: "Deprecated: use `resource_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 `resource_host_static` instead.",
|
||||
Description: "Deprecated: use `resource_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)
|
||||
|
||||
+61
-55
@@ -15,53 +15,59 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
||||
)
|
||||
|
||||
const (
|
||||
fooHostAddress = "10.0.0.1"
|
||||
fooHostAddressUpdate = "10.10.0.0"
|
||||
)
|
||||
|
||||
var (
|
||||
projHost = fmt.Sprintf(`
|
||||
resource "boundary_host_catalog" "foo" {
|
||||
name = "test"
|
||||
description = "test catalog"
|
||||
scope_id = boundary_scope.proj1.id
|
||||
type = "static"
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
}
|
||||
|
||||
resource "boundary_host" "foo" {
|
||||
type = "static"
|
||||
host_catalog_id = boundary_host_catalog.foo.id
|
||||
name = "test"
|
||||
description = "test host"
|
||||
address = "%s"
|
||||
}`, fooHostAddress)
|
||||
|
||||
projHostUpdate = fmt.Sprintf(`
|
||||
resource "boundary_host_catalog" "foo" {
|
||||
name = "test"
|
||||
description = "test catalog"
|
||||
scope_id = boundary_scope.proj1.id
|
||||
type = "static"
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
}
|
||||
|
||||
resource "boundary_host" "foo" {
|
||||
type = "static"
|
||||
host_catalog_id = boundary_host_catalog.foo.id
|
||||
name = "test"
|
||||
description = "test host"
|
||||
address = "%s"
|
||||
}`, fooHostAddressUpdate)
|
||||
)
|
||||
|
||||
func TestAccHost(t *testing.T) {
|
||||
t.Run("non-static", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testAccHost(t, false)
|
||||
})
|
||||
t.Run("static", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testAccHost(t, true)
|
||||
})
|
||||
}
|
||||
|
||||
func testAccHost(t *testing.T, static bool) {
|
||||
const (
|
||||
fooHostAddress = "10.0.0.1"
|
||||
fooHostAddressUpdate = "10.10.0.0"
|
||||
)
|
||||
|
||||
projCatalog := `
|
||||
resource "%s" "foo" {
|
||||
name = "test"
|
||||
description = "test catalog"
|
||||
%s
|
||||
scope_id = boundary_scope.proj1.id
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
}`
|
||||
|
||||
projHost := `
|
||||
resource "%s" "foo" {
|
||||
host_catalog_id = %s.foo.id
|
||||
name = "test"
|
||||
description = "test host"
|
||||
%s
|
||||
address = "%s"
|
||||
}`
|
||||
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
// org := iam.TestOrg(t, tc.IamRepo())
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
catalogName := "boundary_host_catalog"
|
||||
hostName := "boundary_host"
|
||||
typeStr := `type = "static"`
|
||||
if static {
|
||||
catalogName = "boundary_host_catalog_static"
|
||||
hostName = "boundary_host_static"
|
||||
typeStr = ""
|
||||
}
|
||||
fooHostName := fmt.Sprintf("%s.foo", hostName)
|
||||
hcBlock := fmt.Sprintf(projCatalog, catalogName, typeStr)
|
||||
hostBlock := fmt.Sprintf(projHost, hostName, catalogName, typeStr, fooHostAddress)
|
||||
hostUpdateBlock := fmt.Sprintf(projHost, hostName, catalogName, typeStr, fooHostAddressUpdate)
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
@@ -69,28 +75,28 @@ func TestAccHost(t *testing.T) {
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// test project host create
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, projHost),
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, hcBlock, hostBlock),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckHostAttributeSet(provider, "boundary_host.foo", "address", fooHostAddress),
|
||||
testAccCheckHostResourceExists(provider, "boundary_host.foo"),
|
||||
resource.TestCheckResourceAttr("boundary_host.foo", "name", "test"),
|
||||
resource.TestCheckResourceAttr("boundary_host.foo", "description", "test host"),
|
||||
resource.TestCheckResourceAttr("boundary_host.foo", "address", fooHostAddress),
|
||||
testAccCheckHostAttributeSet(provider, fooHostName, "address", fooHostAddress),
|
||||
testAccCheckHostResourceExists(provider, fooHostName),
|
||||
resource.TestCheckResourceAttr(fooHostName, "name", "test"),
|
||||
resource.TestCheckResourceAttr(fooHostName, "description", "test host"),
|
||||
resource.TestCheckResourceAttr(fooHostName, "address", fooHostAddress),
|
||||
),
|
||||
},
|
||||
importStep("boundary_host.foo"),
|
||||
importStep(fooHostName),
|
||||
{
|
||||
// test project host update
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, projHostUpdate),
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, hcBlock, hostUpdateBlock),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckHostAttributeSet(provider, "boundary_host.foo", "address", fooHostAddressUpdate),
|
||||
testAccCheckHostResourceExists(provider, "boundary_host.foo"),
|
||||
resource.TestCheckResourceAttr("boundary_host.foo", "name", "test"),
|
||||
resource.TestCheckResourceAttr("boundary_host.foo", "description", "test host"),
|
||||
resource.TestCheckResourceAttr("boundary_host.foo", "address", fooHostAddressUpdate),
|
||||
testAccCheckHostAttributeSet(provider, fooHostName, "address", fooHostAddressUpdate),
|
||||
testAccCheckHostResourceExists(provider, fooHostName),
|
||||
resource.TestCheckResourceAttr(fooHostName, "name", "test"),
|
||||
resource.TestCheckResourceAttr(fooHostName, "description", "test host"),
|
||||
resource.TestCheckResourceAttr(fooHostName, "address", fooHostAddressUpdate),
|
||||
),
|
||||
},
|
||||
importStep("boundary_host.foo"),
|
||||
importStep(fooHostName),
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user