Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99914e6317 | ||
|
|
336f68ba68 | ||
|
|
e1f8352fc4 | ||
|
|
4f0509c55d | ||
|
|
7c73927d17 | ||
|
|
3c5bbd53c6 | ||
|
|
2d2f825035 | ||
|
|
30b38ea89d | ||
|
|
1c1e593eab | ||
|
|
8bfac9f6fa | ||
|
|
3d8121234f | ||
|
|
549a843382 | ||
|
|
38f1cf40e9 | ||
|
|
4f4318d005 | ||
|
|
5af7f8b662 | ||
|
|
3d42195a26 | ||
|
|
9bc08d4c48 | ||
|
|
4de1075067 | ||
|
|
dc46913c63 | ||
|
|
bb85735e02 | ||
|
|
f49ee1d5bc | ||
|
|
b01f482fb2 | ||
|
|
b69e175687 |
No files matched your search
@@ -7,8 +7,8 @@ jobs:
|
||||
steps:
|
||||
- add_ssh_keys:
|
||||
fingerprints:
|
||||
# hashicorp-ci github "machine" user, for private modules
|
||||
- "c6:96:98:82:dc:04:6c:39:dd:ac:83:05:e3:15:1c:98"
|
||||
# hc-github-team-secure-boundary github user,TF_ACC=1 ~/.go/bin/go test ./... -v $(TESTARGS) -timeout 120m to read private modules
|
||||
- "d4:7f:b2:f7:6f:4d:e7:21:cc:d2:b7:6a:8b:68:e6:d4"
|
||||
- checkout
|
||||
- run:
|
||||
name: "Run Acceptance Tests"
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.15.2
|
||||
1.16.3
|
||||
+40
-4
@@ -1,9 +1,45 @@
|
||||
## 1.0.3 (June 30, 2021)
|
||||
|
||||
### New and Improved
|
||||
|
||||
* Adds credential library resource for Vault
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/114)).
|
||||
* Adds credential store resource for Vault
|
||||
([PR 1](https://github.com/hashicorp/terraform-provider-boundary/pull/114)),
|
||||
([PR 2](https://github.com/hashicorp/terraform-provider-boundary/pull/125)).
|
||||
* Adds claim scopes attribute to OIDC auth method
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/112)).
|
||||
* Adds account claim maps attribute to OIDC auth method
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/111)).
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Make OIDC account attribute for subject ForceNew
|
||||
([Issue](https://github.com/hashicorp/terraform-provider-boundary/issues/119)),
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/122)).
|
||||
* Update static type attribute for host catalog resource
|
||||
([Issue](https://github.com/hashicorp/terraform-provider-boundary/issues/115)),
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/121)).
|
||||
|
||||
## 1.0.2 (May 06, 2021)
|
||||
|
||||
### New and Improved
|
||||
|
||||
* Adds OIDC account resource
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/105)).
|
||||
* Adds OIDC auth method resource
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/105)).
|
||||
|
||||
### Deprecations/Changes
|
||||
|
||||
* Deprecates fields on `resource_auth_method` that will be replaced in the future with generic `attributes` attribute.
|
||||
|
||||
## 1.0.1 (February 02, 2021)
|
||||
|
||||
### New and Improved
|
||||
|
||||
* Adds worker filter to target resource
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/76))
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/76)).
|
||||
|
||||
## 1.0.0 (January 20, 2021)
|
||||
|
||||
@@ -12,14 +48,14 @@ We are bumping the version of the Boundary Terraform provider to v1.0.0 and will
|
||||
### Bug Fixes
|
||||
|
||||
* During `terraform apply`, do not update existing user account passwords when the password field is updated in the tf file.
|
||||
([Issue](https://github.com/hashicorp/terraform-provider-boundary/issues/71))
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/70))
|
||||
([Issue](https://github.com/hashicorp/terraform-provider-boundary/issues/71)),
|
||||
([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/70)).
|
||||
|
||||
## 0.1.4 (January 14, 2021)
|
||||
|
||||
### New and Improved
|
||||
|
||||
Update provider to handle new domain errors ([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/63))
|
||||
Update provider to handle new domain errors ([PR](https://github.com/hashicorp/terraform-provider-boundary/pull/63)).
|
||||
|
||||
## 0.1.0 (October 14, 2020)
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Contributing to Boundary
|
||||
|
||||
Thank you for contributing to Boundary! Here you can find common questions around reporting issues and opening
|
||||
pull requests to our project.
|
||||
|
||||
When contributing in any way to the Boundary project (new issue, PR, etc), please be aware that our team identifies with many gender pronouns and to use non-binary pronouns when addressing our team. For more reading on our code of conduct, please see the [HashiCorp community guidelines](https://www.hashicorp.com/community-guidelines).
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.15
|
||||
FROM golang:1.16
|
||||
|
||||
WORKDIR /go/src/terraform-provider-boundary
|
||||
COPY . .
|
||||
|
||||
@@ -41,7 +41,10 @@ all:
|
||||
GOOS=windows go build -o $(BUILD_ALL_PATH)/terraform-provider-boundary_windows-amd64 main.go
|
||||
GOOS=linux go build -o $(BUILD_ALL_PATH)/terraform-provider-boundary_linux-amd64 main.go
|
||||
|
||||
docs:
|
||||
go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs
|
||||
|
||||
rm-id-flag-from-docs:
|
||||
find docs/ -name "*.md" -type f | xargs sed -i -e '/- \*\*id\*\*/d'
|
||||
|
||||
.PHONY: testacc tools
|
||||
.PHONY: testacc tools docs
|
||||
@@ -9,7 +9,7 @@ Requirements
|
||||
------------
|
||||
|
||||
- [Terraform](https://www.terraform.io/downloads.html) >= 0.12.x
|
||||
- [Go](https://golang.org/doc/install) >= 1.15
|
||||
- [Go](https://golang.org/doc/install) >= 1.16
|
||||
|
||||
Building The Provider
|
||||
---------------------
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
VERSION="1.15"
|
||||
VERSION="1.16"
|
||||
|
||||
[ -z "$GOROOT" ] && GOROOT="$HOME/.go"
|
||||
[ -z "$GOPATH" ] && GOPATH="$HOME/go"
|
||||
|
||||
@@ -6,7 +6,7 @@ description: |-
|
||||
The account resource allows you to configure a Boundary account.
|
||||
---
|
||||
|
||||
# Resource `boundary_account`
|
||||
# boundary_account (Resource)
|
||||
|
||||
The account resource allows you to configure a Boundary account.
|
||||
|
||||
@@ -45,11 +45,11 @@ resource "boundary_account" "jeff" {
|
||||
### Optional
|
||||
|
||||
- **description** (String) The account description.
|
||||
- **login_name** (String) The login name for this account.
|
||||
- **login_name** (String, Deprecated) The login name for this account.
|
||||
- **name** (String) The account name. Defaults to the resource name.
|
||||
- **password** (String) The account password. Only set on create, changes will not be reflected when updating account.
|
||||
- **password** (String, Deprecated) The account password. Only set on create, changes will not be reflected when updating account.
|
||||
|
||||
### Read-only
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the account.
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_account_oidc Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The account resource allows you to configure a Boundary account.
|
||||
---
|
||||
|
||||
# boundary_account_oidc (Resource)
|
||||
|
||||
The account resource allows you to configure a Boundary account.
|
||||
|
||||
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- **auth_method_id** (String) The resource ID for the auth method.
|
||||
|
||||
### Optional
|
||||
|
||||
- **description** (String) The account description.
|
||||
- **issuer** (String) The OIDC issuer.
|
||||
- **name** (String) The account name. Defaults to the resource name.
|
||||
- **subject** (String) The OIDC subject.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the account.
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_account_password Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The account resource allows you to configure a Boundary account.
|
||||
---
|
||||
|
||||
# boundary_account_password (Resource)
|
||||
|
||||
The account resource allows you to configure a Boundary account.
|
||||
|
||||
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- **auth_method_id** (String) The resource ID for the auth method.
|
||||
- **type** (String) The resource type.
|
||||
|
||||
### Optional
|
||||
|
||||
- **description** (String) The account description.
|
||||
- **login_name** (String, Deprecated) The login name for this account.
|
||||
- **name** (String) The account name. Defaults to the resource name.
|
||||
- **password** (String, Deprecated) The account password. Only set on create, changes will not be reflected when updating account.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the account.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ description: |-
|
||||
The auth method resource allows you to configure a Boundary auth_method.
|
||||
---
|
||||
|
||||
# Resource `boundary_auth_method`
|
||||
# boundary_auth_method (Resource)
|
||||
|
||||
The auth method resource allows you to configure a Boundary auth_method.
|
||||
|
||||
@@ -38,11 +38,11 @@ resource "boundary_auth_method" "password" {
|
||||
### Optional
|
||||
|
||||
- **description** (String) The auth method description.
|
||||
- **min_login_name_length** (Number) The minimum login name length.
|
||||
- **min_password_length** (Number) The minimum password length.
|
||||
- **min_login_name_length** (Number, Deprecated) The minimum login name length.
|
||||
- **min_password_length** (Number, Deprecated) The minimum password length.
|
||||
- **name** (String) The auth method name. Defaults to the resource name.
|
||||
|
||||
### Read-only
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the account.
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_auth_method_oidc Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The OIDC auth method resource allows you to configure a Boundary authmethodoidc.
|
||||
---
|
||||
|
||||
# boundary_auth_method_oidc (Resource)
|
||||
|
||||
The OIDC auth method resource allows you to configure a Boundary auth_method_oidc.
|
||||
|
||||
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- **scope_id** (String) The scope ID.
|
||||
|
||||
### Optional
|
||||
|
||||
- **account_claim_maps** (List of String) Account claim maps for the to_claim of sub.
|
||||
- **allowed_audiences** (List of String) Audiences for which the provider responses will be allowed
|
||||
- **api_url_prefix** (String) The API prefix to use when generating callback URLs for the provider. Should be set to an address at which the provider can reach back to the controller.
|
||||
- **callback_url** (String) The URL that should be provided to the IdP for callbacks.
|
||||
- **claims_scopes** (List of String) Claims scopes.
|
||||
- **client_id** (String) The client ID assigned to this auth method from the provider.
|
||||
- **client_secret** (String) The secret key assigned to this auth method from the provider. Once set, only the hash will be kept and the original value can be removed from configuration.
|
||||
- **client_secret_hmac** (String) The HMAC of the client secret returned by the Boundary controller, which is used for comparison after initial setting of the value.
|
||||
- **description** (String) The auth method description.
|
||||
- **disable_discovered_config_validation** (Boolean) Disables validation logic ensuring that the OIDC provider's information from its discovery endpoint matches the information here. The validation is only performed at create or update time.
|
||||
- **idp_ca_certs** (List of String) A list of CA certificates to trust when validating the IdP's token signatures.
|
||||
- **is_primary_for_scope** (Boolean) When true, makes this auth method the primary auth method for the scope in which it resides. The primary auth method for a scope means the the user will be automatically created when they login using an OIDC account.
|
||||
- **issuer** (String) The issuer corresponding to the provider, which must match the issuer field in generated tokens.
|
||||
- **max_age** (Number) The max age to provide to the provider, indicating how much time is allowed to have passed since the last authentication before the user is challenged again.
|
||||
- **name** (String) The auth method name. Defaults to the resource name.
|
||||
- **signing_algorithms** (List of String) Allowed signing algorithms for the provider's issued tokens.
|
||||
- **state** (String) Can be one of 'inactive', 'active-private', or 'active-public'. Currently automatically set to active-public.
|
||||
- **type** (String) The type of auth method; hardcoded.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the auth method.
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_auth_method_password Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The auth method resource allows you to configure a Boundary authmethodpassword.
|
||||
---
|
||||
|
||||
# boundary_auth_method_password (Resource)
|
||||
|
||||
The auth method resource allows you to configure a Boundary auth_method_password.
|
||||
|
||||
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- **scope_id** (String) The scope ID.
|
||||
|
||||
### Optional
|
||||
|
||||
- **description** (String) The auth method description.
|
||||
- **min_login_name_length** (Number) The minimum login name length.
|
||||
- **min_password_length** (Number) The minimum password length.
|
||||
- **name** (String) The auth method name. Defaults to the resource name.
|
||||
- **type** (String) The resource type, hardcoded per resource
|
||||
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the account.
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_credential_library_vault Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The credential library for Vault resource allows you to configure a Boundary credential library for Vault.
|
||||
---
|
||||
|
||||
# boundary_credential_library_vault (Resource)
|
||||
|
||||
The credential library for Vault resource allows you to configure a Boundary credential library for Vault.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "My first scope!"
|
||||
scope_id = "global"
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_credential_store_vault" "foo" {
|
||||
name = "foo"
|
||||
description = "My first Vault credential store!"
|
||||
address = "http://127.0.0.1:8200" # change to Vault address
|
||||
token = "s.0ufRo6XEGU2jOqnIr7OlFYP5" # change to valid Vault token
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_credential_library_vault" "foo" {
|
||||
name = "foo"
|
||||
description = "My first Vault credential library!"
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "my/secret/foo" # change to Vault backend path
|
||||
http_method = "GET"
|
||||
}
|
||||
|
||||
resource "boundary_credential_library_vault" "bar" {
|
||||
name = "bar"
|
||||
description = "My second Vault credential library!"
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "my/secret/bar" # change to Vault backend path
|
||||
http_method = "POST"
|
||||
request_body = <<EOT
|
||||
{
|
||||
"key": "Value",
|
||||
}
|
||||
EOT
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- **credential_store_id** (String) The ID of the credential store that this library belongs to.
|
||||
- **path** (String) The path in Vault to request credentials from.
|
||||
|
||||
### Optional
|
||||
|
||||
- **description** (String) The Vault credential library description.
|
||||
- **http_method** (String) The HTTP method the library uses when requesting credentials from Vault. Defaults to 'GET'
|
||||
- **http_request_body** (String) The body of the HTTP request the library sends to Vault when requesting credentials. Only valid if `http_method` is set to `POST`.
|
||||
- **name** (String) The Vault credential library name. Defaults to the resource name.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the Vault credential library.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_credential_library_vault.foo <my-id>
|
||||
```
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "boundary_credential_store_vault Resource - terraform-provider-boundary"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
The credential store for Vault resource allows you to configure a Boundary credential store for Vault.
|
||||
---
|
||||
|
||||
# boundary_credential_store_vault (Resource)
|
||||
|
||||
The credential store for Vault resource allows you to configure a Boundary credential store for Vault.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "My first scope!"
|
||||
scope_id = "global"
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_credential_store_vault" "example" {
|
||||
name = "foo"
|
||||
description = "My first Vault credential store!"
|
||||
address = "http://127.0.0.1:8200" # change to Vault address
|
||||
token = "s.0ufRo6XEGU2jOqnIr7OlFYP5" # change to valid Vault token
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- **address** (String) The address to Vault server. This should be a complete URL such as 'https://127.0.0.1:8200'
|
||||
- **scope_id** (String) The scope for this credential store.
|
||||
- **token** (String, Sensitive) A token used for accessing Vault.
|
||||
|
||||
### Optional
|
||||
|
||||
- **ca_cert** (String) A PEM-encoded CA certificate to verify the Vault server's TLS certificate.
|
||||
- **client_certificate** (String) A PEM-encoded client certificate to use for TLS authentication to the Vault server.
|
||||
- **client_certificate_key** (String, Sensitive) A PEM-encoded private key matching the client certificate from 'client_certificate'.
|
||||
- **description** (String) The Vault credential store description.
|
||||
- **name** (String) The Vault credential store name. Defaults to the resource name.
|
||||
- **namespace** (String) The namespace within Vault to use.
|
||||
- **tls_server_name** (String) Name to use as the SNI host when connecting to Vault via TLS.
|
||||
- **tls_skip_verify** (Boolean) Whether or not to skip TLS verification.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- **client_certificate_key_hmac** (String) The Vault client certificate key hmac.
|
||||
- **id** (String) The ID of the Vault credential store.
|
||||
- **token_hmac** (String) The Vault token hmac.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import boundary_credential_store_vault.foo <my-id>
|
||||
```
|
||||
@@ -77,7 +77,7 @@ resource "boundary_group" "example" {
|
||||
- **member_ids** (Set of String) Resource IDs for group members, these are most likely boundary users.
|
||||
- **name** (String) The group name. Defaults to the resource name.
|
||||
|
||||
### Read-only
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the group.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ 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.
|
||||
---
|
||||
|
||||
# Resource `boundary_host`
|
||||
# boundary_host (Resource)
|
||||
|
||||
The host resource allows you to configure a Boundary static host. Hosts are always part of a project, so a project resource should be used inline or you should have the project ID in hand to successfully configure a host.
|
||||
|
||||
@@ -50,7 +50,7 @@ resource "boundary_host" "example" {
|
||||
- **description** (String) The host description.
|
||||
- **name** (String) The host name. Defaults to the resource name.
|
||||
|
||||
### Read-only
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the host.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ 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.
|
||||
---
|
||||
|
||||
# Resource `boundary_host_catalog`
|
||||
# boundary_host_catalog (Resource)
|
||||
|
||||
The host catalog resource allows you to configure a Boundary host catalog. Host catalogs are always part of a project, so a project resource should be used inline or you should have the project ID in hand to successfully configure a host catalog.
|
||||
|
||||
@@ -42,14 +42,14 @@ resource "boundary_host_catalog" "example" {
|
||||
### Required
|
||||
|
||||
- **scope_id** (String) The scope ID in which the resource is created.
|
||||
- **type** (String) The host catalog type. Only `Static` (yes, title case) is supported.
|
||||
- **type** (String) The host catalog type. Only `static` is supported.
|
||||
|
||||
### Optional
|
||||
|
||||
- **description** (String) The host catalog description.
|
||||
- **name** (String) The host catalog name. Defaults to the resource name.
|
||||
|
||||
### Read-only
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the host catalog.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ 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.
|
||||
---
|
||||
|
||||
# Resource `boundary_host_set`
|
||||
# boundary_host_set (Resource)
|
||||
|
||||
The host_set resource allows you to configure a Boundary host set. Host sets are always part of a host catalog, so a host catalog resource should be used inline or you should have the host catalog ID in hand to successfully configure a host set.
|
||||
|
||||
@@ -71,7 +71,7 @@ resource "boundary_host_set" "web" {
|
||||
- **host_ids** (Set of String) The list of host IDs contained in this set.
|
||||
- **name** (String) The hostset name. Defaults to the resource name.
|
||||
|
||||
### Read-only
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the hostset.
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ resource "boundary_role" "readonly" {
|
||||
- **name** (String) The role name. Defaults to the resource name.
|
||||
- **principal_ids** (Set of String) A list of principal (user or group) IDs to add as principals on the role.
|
||||
|
||||
### Read-only
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the role.
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ resource "boundary_role" "org_admin" {
|
||||
- **global_scope** (Boolean) Indicates that the scope containing this value is the global scope, which triggers some specialized behavior to allow it to be imported and managed.
|
||||
- **name** (String) The scope name. Defaults to the resource name.
|
||||
|
||||
### Read-only
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the scope.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ description: |-
|
||||
The target resource allows you to configure a Boundary target.
|
||||
---
|
||||
|
||||
# Resource `boundary_target`
|
||||
# boundary_target (Resource)
|
||||
|
||||
The target resource allows you to configure a Boundary target.
|
||||
|
||||
@@ -33,6 +33,22 @@ resource "boundary_scope" "project" {
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_credential_store_vault" "foo" {
|
||||
name = "vault_store"
|
||||
description = "My first Vault credential store!"
|
||||
address = "http://127.0.0.1:8200" # change to Vault address
|
||||
token = "s.0ufRo6XEGU2jOqnIr7OlFYP5" # change to valid Vault token
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_credential_library_vault" "foo" {
|
||||
name = "foo"
|
||||
description = "My first Vault credential library!"
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "my/secret/foo" # change to Vault backend path
|
||||
http_method = "GET"
|
||||
}
|
||||
|
||||
resource "boundary_host_catalog" "foo" {
|
||||
name = "test"
|
||||
description = "test catalog"
|
||||
@@ -72,6 +88,9 @@ resource "boundary_target" "foo" {
|
||||
host_set_ids = [
|
||||
boundary_host_set.foo.id
|
||||
]
|
||||
application_credential_library_ids = [
|
||||
boundary_credential_library_vault.foo.id
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -85,6 +104,7 @@ resource "boundary_target" "foo" {
|
||||
|
||||
### Optional
|
||||
|
||||
- **application_credential_library_ids** (Set of String) A list of application credential library ID's.
|
||||
- **default_port** (Number) The default port for this target.
|
||||
- **description** (String) The target description.
|
||||
- **host_set_ids** (Set of String) A list of host set ID's.
|
||||
@@ -93,7 +113,7 @@ resource "boundary_target" "foo" {
|
||||
- **session_max_seconds** (Number)
|
||||
- **worker_filter** (String) Boolean expression to filter the workers for this target
|
||||
|
||||
### Read-only
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the target.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ description: |-
|
||||
The user resource allows you to configure a Boundary user.
|
||||
---
|
||||
|
||||
# Resource `boundary_user`
|
||||
# boundary_user (Resource)
|
||||
|
||||
The user resource allows you to configure a Boundary user.
|
||||
|
||||
@@ -54,7 +54,7 @@ resource "boundary_user" "jeff" {
|
||||
- **description** (String) The user description.
|
||||
- **name** (String) The username. Defaults to the resource name.
|
||||
|
||||
### Read-only
|
||||
### Read-Only
|
||||
|
||||
- **id** (String) The ID of the user.
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_credential_library_vault.foo <my-id>
|
||||
@@ -0,0 +1,43 @@
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "My first scope!"
|
||||
scope_id = "global"
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_credential_store_vault" "foo" {
|
||||
name = "foo"
|
||||
description = "My first Vault credential store!"
|
||||
address = "http://127.0.0.1:8200" # change to Vault address
|
||||
token = "s.0ufRo6XEGU2jOqnIr7OlFYP5" # change to valid Vault token
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_credential_library_vault" "foo" {
|
||||
name = "foo"
|
||||
description = "My first Vault credential library!"
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "my/secret/foo" # change to Vault backend path
|
||||
http_method = "GET"
|
||||
}
|
||||
|
||||
resource "boundary_credential_library_vault" "bar" {
|
||||
name = "bar"
|
||||
description = "My second Vault credential library!"
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "my/secret/bar" # change to Vault backend path
|
||||
http_method = "POST"
|
||||
request_body = <<EOT
|
||||
{
|
||||
"key": "Value",
|
||||
}
|
||||
EOT
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
terraform import boundary_credential_store_vault.foo <my-id>
|
||||
@@ -0,0 +1,22 @@
|
||||
resource "boundary_scope" "org" {
|
||||
name = "organization_one"
|
||||
description = "My first scope!"
|
||||
scope_id = "global"
|
||||
auto_create_admin_role = true
|
||||
auto_create_default_role = true
|
||||
}
|
||||
|
||||
resource "boundary_scope" "project" {
|
||||
name = "project_one"
|
||||
description = "My first scope!"
|
||||
scope_id = boundary_scope.org.id
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_credential_store_vault" "example" {
|
||||
name = "foo"
|
||||
description = "My first Vault credential store!"
|
||||
address = "http://127.0.0.1:8200" # change to Vault address
|
||||
token = "s.0ufRo6XEGU2jOqnIr7OlFYP5" # change to valid Vault token
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
@@ -18,6 +18,22 @@ resource "boundary_scope" "project" {
|
||||
auto_create_admin_role = true
|
||||
}
|
||||
|
||||
resource "boundary_credential_store_vault" "foo" {
|
||||
name = "vault_store"
|
||||
description = "My first Vault credential store!"
|
||||
address = "http://127.0.0.1:8200" # change to Vault address
|
||||
token = "s.0ufRo6XEGU2jOqnIr7OlFYP5" # change to valid Vault token
|
||||
scope_id = boundary_scope.project.id
|
||||
}
|
||||
|
||||
resource "boundary_credential_library_vault" "foo" {
|
||||
name = "foo"
|
||||
description = "My first Vault credential library!"
|
||||
credential_store_id = boundary_credential_store_vault.foo.id
|
||||
path = "my/secret/foo" # change to Vault backend path
|
||||
http_method = "GET"
|
||||
}
|
||||
|
||||
resource "boundary_host_catalog" "foo" {
|
||||
name = "test"
|
||||
description = "test catalog"
|
||||
@@ -57,4 +73,7 @@ resource "boundary_target" "foo" {
|
||||
host_set_ids = [
|
||||
boundary_host_set.foo.id
|
||||
]
|
||||
application_credential_library_ids = [
|
||||
boundary_credential_library_vault.foo.id
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/hashicorp/terraform-provider-boundary
|
||||
|
||||
go 1.15
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.68.0 // indirect
|
||||
@@ -13,19 +13,20 @@ require (
|
||||
github.com/agext/levenshtein v1.2.3 // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.545 // indirect
|
||||
github.com/aws/aws-sdk-go v1.35.4 // indirect
|
||||
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
|
||||
github.com/circonus-labs/circonusllhist v0.1.4 // indirect
|
||||
github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe // indirect
|
||||
github.com/golang/snappy v0.0.2 // indirect
|
||||
github.com/hashicorp/boundary v0.1.5
|
||||
github.com/hashicorp/boundary/api v0.0.4
|
||||
github.com/hashicorp/boundary/sdk v0.0.2
|
||||
github.com/hashicorp/boundary v0.4.0
|
||||
github.com/hashicorp/boundary/api v0.0.13
|
||||
github.com/hashicorp/boundary/sdk v0.0.5
|
||||
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.0
|
||||
github.com/hashicorp/go-kms-wrapping v0.6.2
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/hashicorp/hcl/v2 v2.6.0 // indirect
|
||||
github.com/hashicorp/terraform-plugin-docs v0.3.1
|
||||
github.com/hashicorp/terraform-plugin-sdk/v2 v2.0.5-0.20201016174159-3a1c6c51f716
|
||||
github.com/hashicorp/terraform-json v0.12.0 // indirect
|
||||
github.com/hashicorp/terraform-plugin-docs v0.4.0
|
||||
github.com/hashicorp/terraform-plugin-sdk/v2 v2.6.1
|
||||
github.com/hashicorp/yamux v0.0.0-20200609203250-aecfd211c9ce // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
@@ -34,6 +35,7 @@ require (
|
||||
github.com/prometheus/common v0.14.0 // indirect
|
||||
github.com/prometheus/procfs v0.2.0 // indirect
|
||||
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c // indirect
|
||||
github.com/zclconf/go-cty v1.6.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
)
|
||||
@@ -1,18 +1,18 @@
|
||||
package provider
|
||||
|
||||
const (
|
||||
// Key used for common SDK ID resource attribute
|
||||
// IDKey is used for common SDK ID resource attribute
|
||||
IDKey = "id"
|
||||
// Key used for common "name" resource attribute
|
||||
// NameKey is used for common "name" resource attribute
|
||||
NameKey = "name"
|
||||
// Key used for common "description" resource attribute
|
||||
// DescriptionKey is used for common "description" resource attribute
|
||||
DescriptionKey = "description"
|
||||
// Key used for common "scope_id" resource attribute
|
||||
// ScopeIdKey is used for common "scope_id" resource attribute
|
||||
ScopeIdKey = "scope_id"
|
||||
// Key used for common "type" resource attribute
|
||||
// TypeKey is used for common "type" resource attribute
|
||||
TypeKey = "type"
|
||||
// Key used for common "host_catalog_id" resource attribute
|
||||
// HostCatalogIdKey is used for common "host_catalog_id" resource attribute
|
||||
HostCatalogIdKey = "host_catalog_id"
|
||||
// Key used for common "auth_method_id" resource attribute
|
||||
// AuthMethodIdKey is used for common "auth_method_id" resource attribute
|
||||
AuthMethodIdKey = "auth_method_id"
|
||||
)
|
||||
@@ -54,16 +54,22 @@ func New() *schema.Provider {
|
||||
},
|
||||
},
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
"boundary_account": resourceAccount(),
|
||||
"boundary_auth_method": resourceAuthMethod(),
|
||||
"boundary_group": resourceGroup(),
|
||||
"boundary_host": resourceHost(),
|
||||
"boundary_host_catalog": resourceHostCatalog(),
|
||||
"boundary_host_set": resourceHostset(),
|
||||
"boundary_role": resourceRole(),
|
||||
"boundary_scope": resourceScope(),
|
||||
"boundary_target": resourceTarget(),
|
||||
"boundary_user": resourceUser(),
|
||||
"boundary_account": resourceAccount(),
|
||||
"boundary_account_password": resourceAccountPassword(),
|
||||
"boundary_account_oidc": resourceAccountOidc(),
|
||||
"boundary_auth_method": resourceAuthMethod(),
|
||||
"boundary_auth_method_password": resourceAuthMethodPassword(),
|
||||
"boundary_auth_method_oidc": resourceAuthMethodOidc(),
|
||||
"boundary_credential_library_vault": resourceCredentialLibraryVault(),
|
||||
"boundary_credential_store_vault": resourceCredentialStoreVault(),
|
||||
"boundary_group": resourceGroup(),
|
||||
"boundary_host": resourceHost(),
|
||||
"boundary_host_catalog": resourceHostCatalog(),
|
||||
"boundary_host_set": resourceHostset(),
|
||||
"boundary_role": resourceRole(),
|
||||
"boundary_scope": resourceScope(),
|
||||
"boundary_target": resourceTarget(),
|
||||
"boundary_user": resourceUser(),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -133,11 +139,11 @@ func providerAuthenticate(ctx context.Context, d *schema.ResourceData, md *metaD
|
||||
|
||||
am := authmethods.NewClient(md.client)
|
||||
|
||||
at, err := am.Authenticate(ctx, authMethodId.(string), credentials)
|
||||
at, err := am.Authenticate(ctx, authMethodId.(string), "login", credentials)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
md.client.SetToken(at.Item.Token)
|
||||
md.client.SetToken(at.Attributes["token"].(string))
|
||||
|
||||
default:
|
||||
return errors.New("no suitable auth method information found")
|
||||
@@ -154,7 +160,9 @@ func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc {
|
||||
}
|
||||
|
||||
if url, ok := d.GetOk("addr"); ok {
|
||||
client.SetAddr(url.(string))
|
||||
if err := client.SetAddr(url.(string)); err != nil {
|
||||
return nil, diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
if client.Addr() == "" {
|
||||
return nil, diag.Errorf(`"no valid address could be determined from "addr" or "BOUNDARY_ADDR" env var`)
|
||||
|
||||
@@ -15,11 +15,11 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
tcLoginName = "user"
|
||||
tcLoginName = "testuser"
|
||||
tcPassword = "passpass"
|
||||
tcPAUM = "ampw_0000000000"
|
||||
tcConfig = []controller.Option{
|
||||
controller.WithDefaultAuthMethodId(tcPAUM),
|
||||
controller.WithDefaultPasswordAuthMethodId(tcPAUM),
|
||||
controller.WithDefaultLoginName(tcLoginName),
|
||||
controller.WithDefaultPassword(tcPassword),
|
||||
}
|
||||
|
||||
@@ -10,12 +10,6 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
accountTypePassword = "password"
|
||||
accountLoginNameKey = "login_name"
|
||||
accountPasswordKey = "password"
|
||||
)
|
||||
|
||||
func resourceAccount() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The account resource allows you to configure a Boundary account.",
|
||||
@@ -60,11 +54,14 @@ func resourceAccount() *schema.Resource {
|
||||
Description: "The login name for this account.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Deprecated: "Will be removed in favor of using attributes parameter",
|
||||
},
|
||||
accountPasswordKey: {
|
||||
Description: "The account password. Only set on create, changes will not be reflected when updating account.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Deprecated: "Will be removed in favor of using attributes parameter",
|
||||
|
||||
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
|
||||
if d.Id() == "" {
|
||||
// This is a new resource do not suppress password diff
|
||||
@@ -77,21 +74,33 @@ func resourceAccount() *schema.Resource {
|
||||
}
|
||||
}
|
||||
|
||||
func setFromAccountResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
|
||||
d.Set(NameKey, raw["name"])
|
||||
d.Set(DescriptionKey, raw["description"])
|
||||
d.Set(AuthMethodIdKey, raw["auth_method_id"])
|
||||
d.Set(TypeKey, raw["type"])
|
||||
func setFromAccountResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
|
||||
if err := d.Set(NameKey, raw["name"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(AuthMethodIdKey, raw["auth_method_id"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(TypeKey, raw["type"]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(malnick) - remove after deprecation cycle in favor of attributes
|
||||
switch raw["type"].(string) {
|
||||
case accountTypePassword:
|
||||
if attrsVal, ok := raw["attributes"]; ok {
|
||||
attrs := attrsVal.(map[string]interface{})
|
||||
d.Set(accountLoginNameKey, attrs["login_name"])
|
||||
if err := d.Set(accountLoginNameKey, attrs["login_name"]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
@@ -104,49 +113,24 @@ func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, meta int
|
||||
return diag.Errorf("no auth method ID provided")
|
||||
}
|
||||
|
||||
var loginName *string
|
||||
if keyVal, ok := d.GetOk(accountLoginNameKey); ok {
|
||||
key := keyVal.(string)
|
||||
loginName = &key
|
||||
}
|
||||
|
||||
var password *string
|
||||
if keyVal, ok := d.GetOk(accountPasswordKey); ok {
|
||||
key := keyVal.(string)
|
||||
password = &key
|
||||
}
|
||||
|
||||
opts := []accounts.Option{}
|
||||
|
||||
var typeStr string
|
||||
if typeVal, ok := d.GetOk(TypeKey); ok {
|
||||
typeStr = typeVal.(string)
|
||||
} else {
|
||||
return diag.Errorf("no type provided")
|
||||
}
|
||||
switch typeStr {
|
||||
case accountTypePassword:
|
||||
if loginName != nil {
|
||||
opts = append(opts, accounts.WithPasswordAccountLoginName(*loginName))
|
||||
}
|
||||
if password != nil {
|
||||
opts = append(opts, accounts.WithPasswordAccountPassword(*password))
|
||||
d.Set(accountPasswordKey, *password)
|
||||
}
|
||||
default:
|
||||
return diag.Errorf("invalid type provided")
|
||||
if nameVal, ok := d.GetOk(NameKey); ok {
|
||||
opts = append(opts, accounts.WithName(nameVal.(string)))
|
||||
}
|
||||
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
nameStr := nameVal.(string)
|
||||
opts = append(opts, accounts.WithName(nameStr))
|
||||
if descVal, ok := d.GetOk(DescriptionKey); ok {
|
||||
opts = append(opts, accounts.WithDescription(descVal.(string)))
|
||||
}
|
||||
|
||||
descVal, ok := d.GetOk(DescriptionKey)
|
||||
if ok {
|
||||
descStr := descVal.(string)
|
||||
opts = append(opts, accounts.WithDescription(descStr))
|
||||
// TODO(malnick) - remove after deprecation cycle
|
||||
if name, ok := d.GetOk(accountLoginNameKey); ok {
|
||||
opts = append(opts, accounts.WithPasswordAccountLoginName(name.(string)))
|
||||
}
|
||||
|
||||
// TODO(malnick) - remove after deprecation cycle
|
||||
if pass, ok := d.GetOk(accountPasswordKey); ok {
|
||||
opts = append(opts, accounts.WithPasswordAccountPassword(pass.(string)))
|
||||
}
|
||||
|
||||
aClient := accounts.NewClient(md.client)
|
||||
@@ -159,7 +143,9 @@ func resourceAccountCreate(ctx context.Context, d *schema.ResourceData, meta int
|
||||
return diag.Errorf("nil account after create")
|
||||
}
|
||||
|
||||
setFromAccountResponseMap(d, acr.GetResponseMap())
|
||||
if err := setFromAccountResponseMap(d, acr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -170,7 +156,7 @@ func resourceAccountRead(ctx context.Context, d *schema.ResourceData, meta inter
|
||||
|
||||
arr, err := aClient.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
@@ -180,7 +166,9 @@ func resourceAccountRead(ctx context.Context, d *schema.ResourceData, meta inter
|
||||
return diag.Errorf("account nil after read")
|
||||
}
|
||||
|
||||
setFromAccountResponseMap(d, arr.GetResponseMap())
|
||||
if err := setFromAccountResponseMap(d, arr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -191,60 +179,38 @@ func resourceAccountUpdate(ctx context.Context, d *schema.ResourceData, meta int
|
||||
|
||||
opts := []accounts.Option{}
|
||||
|
||||
var name *string
|
||||
if d.HasChange(NameKey) {
|
||||
opts = append(opts, accounts.DefaultName())
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
nameStr := nameVal.(string)
|
||||
name = &nameStr
|
||||
opts = append(opts, accounts.WithName(nameStr))
|
||||
opts = append(opts, accounts.WithName(nameVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
var desc *string
|
||||
if d.HasChange(DescriptionKey) {
|
||||
opts = append(opts, accounts.DefaultDescription())
|
||||
descVal, ok := d.GetOk(DescriptionKey)
|
||||
if ok {
|
||||
descStr := descVal.(string)
|
||||
desc = &descStr
|
||||
opts = append(opts, accounts.WithDescription(descStr))
|
||||
opts = append(opts, accounts.WithDescription(descVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
var loginName *string
|
||||
// TODO(malnick) - remove after deprecation cycle
|
||||
if d.HasChange(accountLoginNameKey) {
|
||||
switch d.Get(TypeKey).(string) {
|
||||
case accountTypePassword:
|
||||
opts = append(opts, accounts.DefaultPasswordAccountLoginName())
|
||||
keyVal, ok := d.GetOk(accountLoginNameKey)
|
||||
if ok {
|
||||
keyStr := keyVal.(string)
|
||||
loginName = &keyStr
|
||||
opts = append(opts, accounts.WithPasswordAccountLoginName(keyStr))
|
||||
}
|
||||
default:
|
||||
return diag.Errorf(`"login_name" cannot be used with this type of account`)
|
||||
opts = append(opts, accounts.DefaultPasswordAccountLoginName())
|
||||
if keyVal, ok := d.GetOk(accountLoginNameKey); ok {
|
||||
opts = append(opts, accounts.WithPasswordAccountLoginName(keyVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts) > 0 {
|
||||
opts = append(opts, accounts.WithAutomaticVersioning(true))
|
||||
_, err := aClient.Update(ctx, d.Id(), 0, opts...)
|
||||
aur, err := aClient.Update(ctx, d.Id(), 0, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating account: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(NameKey) {
|
||||
d.Set(NameKey, name)
|
||||
}
|
||||
if d.HasChange(DescriptionKey) {
|
||||
d.Set(DescriptionKey, desc)
|
||||
}
|
||||
if d.HasChange(accountLoginNameKey) {
|
||||
d.Set(accountLoginNameKey, loginName)
|
||||
setFromAccountResponseMap(d, aur.GetResponse().Map)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/accounts"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
accountTypeOidc = "password"
|
||||
accountOidcIssuerKey = "issuer"
|
||||
accountOidcSubjectKey = "subject"
|
||||
)
|
||||
|
||||
func resourceAccountOidc() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The account resource allows you to configure a Boundary account.",
|
||||
|
||||
CreateContext: resourceAccountOidcCreate,
|
||||
ReadContext: resourceAccountOidcRead,
|
||||
UpdateContext: resourceAccountOidcUpdate,
|
||||
DeleteContext: resourceAccountOidcDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: schema.ImportStatePassthroughContext,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
IDKey: {
|
||||
Description: "The ID of the account.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
NameKey: {
|
||||
Description: "The account name. Defaults to the resource name.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
DescriptionKey: {
|
||||
Description: "The account description.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
AuthMethodIdKey: {
|
||||
Description: "The resource ID for the auth method.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
accountOidcIssuerKey: {
|
||||
Description: "The OIDC issuer.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
accountOidcSubjectKey: {
|
||||
Description: "The OIDC subject.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setFromAccountOidcResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
|
||||
d.Set(NameKey, raw["name"])
|
||||
d.Set(DescriptionKey, raw["description"])
|
||||
d.Set(AuthMethodIdKey, raw["auth_method_id"])
|
||||
d.Set(accountOidcIssuerKey, raw["issuer"])
|
||||
d.Set(accountOidcSubjectKey, raw["subject"])
|
||||
d.SetId(raw["id"].(string))
|
||||
}
|
||||
|
||||
func resourceAccountOidcCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
|
||||
var authMethodId string
|
||||
if authMethodIdVal, ok := d.GetOk(AuthMethodIdKey); ok {
|
||||
authMethodId = authMethodIdVal.(string)
|
||||
} else {
|
||||
return diag.Errorf("no auth method ID provided")
|
||||
}
|
||||
|
||||
opts := []accounts.Option{}
|
||||
|
||||
if i, ok := d.GetOk(accountOidcIssuerKey); ok {
|
||||
opts = append(opts, accounts.WithOidcAccountIssuer(i.(string)))
|
||||
}
|
||||
|
||||
if s, ok := d.GetOk(accountOidcSubjectKey); ok {
|
||||
opts = append(opts, accounts.WithOidcAccountSubject(s.(string)))
|
||||
}
|
||||
|
||||
if n, ok := d.GetOk(NameKey); ok {
|
||||
opts = append(opts, accounts.WithName(n.(string)))
|
||||
}
|
||||
|
||||
if d, ok := d.GetOk(DescriptionKey); ok {
|
||||
opts = append(opts, accounts.WithDescription(d.(string)))
|
||||
}
|
||||
|
||||
aClient := accounts.NewClient(md.client)
|
||||
|
||||
acr, err := aClient.Create(ctx, authMethodId, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error creating account: %v", err)
|
||||
}
|
||||
if acr == nil {
|
||||
return diag.Errorf("nil account after create")
|
||||
}
|
||||
|
||||
setFromAccountOidcResponseMap(d, acr.GetResponse().Map)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceAccountOidcRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
aClient := accounts.NewClient(md.client)
|
||||
|
||||
arr, err := aClient.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
return diag.Errorf("error reading account: %v", err)
|
||||
}
|
||||
if arr == nil {
|
||||
return diag.Errorf("account nil after read")
|
||||
}
|
||||
|
||||
setFromAccountOidcResponseMap(d, arr.GetResponse().Map)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceAccountOidcUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
aClient := accounts.NewClient(md.client)
|
||||
|
||||
opts := []accounts.Option{}
|
||||
|
||||
if d.HasChange(NameKey) {
|
||||
opts = append(opts, accounts.DefaultName())
|
||||
if n, ok := d.GetOk(NameKey); ok {
|
||||
opts = append(opts, accounts.WithName(n.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(DescriptionKey) {
|
||||
opts = append(opts, accounts.DefaultDescription())
|
||||
if d, ok := d.GetOk(DescriptionKey); ok {
|
||||
opts = append(opts, accounts.WithDescription(d.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(accountOidcIssuerKey) {
|
||||
opts = append(opts, accounts.DefaultOidcAccountIssuer())
|
||||
if i, ok := d.GetOk(accountOidcIssuerKey); ok {
|
||||
opts = append(opts, accounts.WithOidcAccountIssuer(i.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(accountOidcSubjectKey) {
|
||||
opts = append(opts, accounts.DefaultOidcAccountSubject())
|
||||
if i, ok := d.GetOk(accountOidcSubjectKey); ok {
|
||||
opts = append(opts, accounts.WithOidcAccountSubject(i.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts) > 0 {
|
||||
opts = append(opts, accounts.WithAutomaticVersioning(true))
|
||||
aur, err := aClient.Update(ctx, d.Id(), 0, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating account: %v", err)
|
||||
}
|
||||
|
||||
setFromAccountOidcResponseMap(d, aur.GetResponse().Map)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceAccountOidcDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
aClient := accounts.NewClient(md.client)
|
||||
|
||||
_, err := aClient.Delete(ctx, d.Id())
|
||||
if err != nil {
|
||||
return diag.Errorf("error deleting account: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/accounts"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
accountTypePassword = "password"
|
||||
accountLoginNameKey = "login_name"
|
||||
accountPasswordKey = "password"
|
||||
)
|
||||
|
||||
func resourceAccountPassword() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The account resource allows you to configure a Boundary account.",
|
||||
|
||||
CreateContext: resourceAccountPasswordCreate,
|
||||
ReadContext: resourceAccountPasswordRead,
|
||||
UpdateContext: resourceAccountPasswordUpdate,
|
||||
DeleteContext: resourceAccountPasswordDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: schema.ImportStatePassthroughContext,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
IDKey: {
|
||||
Description: "The ID of the account.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
NameKey: {
|
||||
Description: "The account name. Defaults to the resource name.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
DescriptionKey: {
|
||||
Description: "The account description.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
AuthMethodIdKey: {
|
||||
Description: "The resource ID for the auth method.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
TypeKey: {
|
||||
Description: "The resource type.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
accountLoginNameKey: {
|
||||
Description: "The login name for this account.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Deprecated: "Will be removed in favor of using attributes parameter",
|
||||
},
|
||||
accountPasswordKey: {
|
||||
Description: "The account password. Only set on create, changes will not be reflected when updating account.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Deprecated: "Will be removed in favor of using attributes parameter",
|
||||
|
||||
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
|
||||
if d.Id() == "" {
|
||||
// This is a new resource do not suppress password diff
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setFromAccountPasswordResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
|
||||
d.Set(NameKey, raw["name"])
|
||||
d.Set(DescriptionKey, raw["description"])
|
||||
d.Set(AuthMethodIdKey, raw["auth_method_id"])
|
||||
d.Set(TypeKey, raw["type"])
|
||||
|
||||
switch raw["type"].(string) {
|
||||
case accountTypePassword:
|
||||
if attrsVal, ok := raw["attributes"]; ok {
|
||||
attrs := attrsVal.(map[string]interface{})
|
||||
d.Set(accountLoginNameKey, attrs["login_name"])
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
}
|
||||
|
||||
func resourceAccountPasswordCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
|
||||
var authMethodId string
|
||||
if authMethodIdVal, ok := d.GetOk(AuthMethodIdKey); ok {
|
||||
authMethodId = authMethodIdVal.(string)
|
||||
} else {
|
||||
return diag.Errorf("no auth method ID provided")
|
||||
}
|
||||
|
||||
var loginName *string
|
||||
if keyVal, ok := d.GetOk(accountLoginNameKey); ok {
|
||||
key := keyVal.(string)
|
||||
loginName = &key
|
||||
}
|
||||
|
||||
var password *string
|
||||
if keyVal, ok := d.GetOk(accountPasswordKey); ok {
|
||||
key := keyVal.(string)
|
||||
password = &key
|
||||
}
|
||||
|
||||
opts := []accounts.Option{}
|
||||
|
||||
var typeStr string
|
||||
if typeVal, ok := d.GetOk(TypeKey); ok {
|
||||
typeStr = typeVal.(string)
|
||||
} else {
|
||||
return diag.Errorf("no type provided")
|
||||
}
|
||||
switch typeStr {
|
||||
case accountTypePassword:
|
||||
if loginName != nil {
|
||||
opts = append(opts, accounts.WithPasswordAccountLoginName(*loginName))
|
||||
}
|
||||
if password != nil {
|
||||
opts = append(opts, accounts.WithPasswordAccountPassword(*password))
|
||||
d.Set(accountPasswordKey, *password)
|
||||
}
|
||||
default:
|
||||
return diag.Errorf("invalid type provided")
|
||||
}
|
||||
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
nameStr := nameVal.(string)
|
||||
opts = append(opts, accounts.WithName(nameStr))
|
||||
}
|
||||
|
||||
descVal, ok := d.GetOk(DescriptionKey)
|
||||
if ok {
|
||||
descStr := descVal.(string)
|
||||
opts = append(opts, accounts.WithDescription(descStr))
|
||||
}
|
||||
|
||||
aClient := accounts.NewClient(md.client)
|
||||
|
||||
acr, err := aClient.Create(ctx, authMethodId, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error creating account: %v", err)
|
||||
}
|
||||
if acr == nil {
|
||||
return diag.Errorf("nil account after create")
|
||||
}
|
||||
|
||||
setFromAccountPasswordResponseMap(d, acr.GetResponse().Map)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceAccountPasswordRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
aClient := accounts.NewClient(md.client)
|
||||
|
||||
arr, err := aClient.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
return diag.Errorf("error reading account: %v", err)
|
||||
}
|
||||
if arr == nil {
|
||||
return diag.Errorf("account nil after read")
|
||||
}
|
||||
|
||||
setFromAccountPasswordResponseMap(d, arr.GetResponse().Map)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceAccountPasswordUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
aClient := accounts.NewClient(md.client)
|
||||
|
||||
opts := []accounts.Option{}
|
||||
|
||||
var name *string
|
||||
if d.HasChange(NameKey) {
|
||||
opts = append(opts, accounts.DefaultName())
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
nameStr := nameVal.(string)
|
||||
name = &nameStr
|
||||
opts = append(opts, accounts.WithName(nameStr))
|
||||
}
|
||||
}
|
||||
|
||||
var desc *string
|
||||
if d.HasChange(DescriptionKey) {
|
||||
opts = append(opts, accounts.DefaultDescription())
|
||||
descVal, ok := d.GetOk(DescriptionKey)
|
||||
if ok {
|
||||
descStr := descVal.(string)
|
||||
desc = &descStr
|
||||
opts = append(opts, accounts.WithDescription(descStr))
|
||||
}
|
||||
}
|
||||
|
||||
var loginName *string
|
||||
if d.HasChange(accountLoginNameKey) {
|
||||
switch d.Get(TypeKey).(string) {
|
||||
case accountTypePassword:
|
||||
opts = append(opts, accounts.DefaultPasswordAccountLoginName())
|
||||
keyVal, ok := d.GetOk(accountLoginNameKey)
|
||||
if ok {
|
||||
keyStr := keyVal.(string)
|
||||
loginName = &keyStr
|
||||
opts = append(opts, accounts.WithPasswordAccountLoginName(keyStr))
|
||||
}
|
||||
default:
|
||||
return diag.Errorf(`"login_name" cannot be used with this type of account`)
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts) > 0 {
|
||||
opts = append(opts, accounts.WithAutomaticVersioning(true))
|
||||
_, err := aClient.Update(ctx, d.Id(), 0, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating account: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(NameKey) {
|
||||
d.Set(NameKey, name)
|
||||
}
|
||||
if d.HasChange(DescriptionKey) {
|
||||
d.Set(DescriptionKey, desc)
|
||||
}
|
||||
if d.HasChange(accountLoginNameKey) {
|
||||
d.Set(accountLoginNameKey, loginName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceAccountPasswordDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
aClient := accounts.NewClient(md.client)
|
||||
|
||||
_, err := aClient.Delete(ctx, d.Id())
|
||||
if err != nil {
|
||||
return diag.Errorf("error deleting account: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/accounts"
|
||||
"github.com/hashicorp/boundary/testing/controller"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
||||
)
|
||||
|
||||
const (
|
||||
fooAccountPasswordDesc = "test account"
|
||||
fooAccountPasswordDescUpdate = "test account update"
|
||||
)
|
||||
|
||||
var (
|
||||
fooAccountPassword = fmt.Sprintf(`
|
||||
resource "boundary_auth_method" "foo" {
|
||||
name = "test"
|
||||
description = "test account"
|
||||
type = "password"
|
||||
scope_id = boundary_scope.org1.id
|
||||
depends_on = [boundary_role.org1_admin]
|
||||
}
|
||||
|
||||
resource "boundary_account_password" "foo" {
|
||||
name = "test"
|
||||
description = "%s"
|
||||
type = "password"
|
||||
login_name = "foo"
|
||||
password = "foofoofoo"
|
||||
auth_method_id = boundary_auth_method.foo.id
|
||||
}`, fooAccountPasswordDesc)
|
||||
|
||||
fooAccountPasswordUpdate = fmt.Sprintf(`
|
||||
resource "boundary_auth_method" "foo" {
|
||||
name = "test"
|
||||
description = "test account"
|
||||
type = "password"
|
||||
scope_id = boundary_scope.org1.id
|
||||
depends_on = [boundary_role.org1_admin]
|
||||
}
|
||||
|
||||
resource "boundary_account_password" "foo" {
|
||||
name = "test"
|
||||
description = "%s"
|
||||
type = "password"
|
||||
login_name = "foo"
|
||||
password = "foofoofoo"
|
||||
auth_method_id = boundary_auth_method.foo.id
|
||||
}`, fooAccountPasswordDescUpdate)
|
||||
)
|
||||
|
||||
func TestAccAccount(t *testing.T) {
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
var provider *schema.Provider
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckAccountPasswordResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// create
|
||||
Config: testConfig(url, fooOrg, fooAccountPassword),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("boundary_account_password.foo", "description", fooAccountPasswordDesc),
|
||||
resource.TestCheckResourceAttr("boundary_account_password.foo", "name", "test"),
|
||||
resource.TestCheckResourceAttr("boundary_account_password.foo", "type", "password"),
|
||||
resource.TestCheckResourceAttr("boundary_account_password.foo", "login_name", "foo"),
|
||||
resource.TestCheckResourceAttr("boundary_account_password.foo", "password", "foofoofoo"),
|
||||
testAccCheckAccountPasswordResourceExists(provider, "boundary_account_password.foo"),
|
||||
),
|
||||
},
|
||||
importStep("boundary_account_password.foo", "password"),
|
||||
{
|
||||
// update
|
||||
Config: testConfig(url, fooOrg, fooAccountPasswordUpdate),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("boundary_account_password.foo", "description", fooAccountPasswordDescUpdate),
|
||||
resource.TestCheckResourceAttr("boundary_account_password.foo", "name", "test"),
|
||||
resource.TestCheckResourceAttr("boundary_account_password.foo", "type", "password"),
|
||||
resource.TestCheckResourceAttr("boundary_account_password.foo", "login_name", "foo"),
|
||||
resource.TestCheckResourceAttr("boundary_account_password.foo", "password", "foofoofoo"),
|
||||
testAccCheckAccountPasswordResourceExists(provider, "boundary_account_password.foo"),
|
||||
),
|
||||
},
|
||||
importStep("boundary_account_password.foo", "password"),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckAccountPasswordResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", name)
|
||||
}
|
||||
|
||||
id := rs.Primary.ID
|
||||
if id == "" {
|
||||
return fmt.Errorf("No ID is set")
|
||||
}
|
||||
|
||||
md := testProvider.Meta().(*metaData)
|
||||
|
||||
amClient := accounts.NewClient(md.client)
|
||||
|
||||
if _, err := amClient.Read(context.Background(), id); err != nil {
|
||||
return fmt.Errorf("Got an error when reading account %q: %v", id, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckAccountPasswordResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if testProvider.Meta() == nil {
|
||||
t.Fatal("got nil provider metadata")
|
||||
}
|
||||
md := testProvider.Meta().(*metaData)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
switch rs.Type {
|
||||
case "boundary_account_password":
|
||||
id := rs.Primary.ID
|
||||
|
||||
amClient := accounts.NewClient(md.client)
|
||||
|
||||
_, err := amClient.Read(context.Background(), id)
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||
return fmt.Errorf("didn't get a 404 when reading destroyed account %q: %v", id, err)
|
||||
}
|
||||
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ resource "boundary_account" "foo" {
|
||||
}`, fooAccountDescUpdate)
|
||||
)
|
||||
|
||||
func TestAccAccount(t *testing.T) {
|
||||
func TestAccAccountBase(t *testing.T) {
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
@@ -69,7 +69,7 @@ func TestAccAccount(t *testing.T) {
|
||||
CheckDestroy: testAccCheckAccountResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
//create
|
||||
// create
|
||||
Config: testConfig(url, fooOrg, fooAccount),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("boundary_account.foo", "description", fooAccountDesc),
|
||||
@@ -137,7 +137,7 @@ func testAccCheckAccountResourceDestroy(t *testing.T, testProvider *schema.Provi
|
||||
amClient := accounts.NewClient(md.client)
|
||||
|
||||
_, err := amClient.Read(context.Background(), id)
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.ResponseStatus() != http.StatusNotFound {
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||
return fmt.Errorf("didn't get a 404 when reading destroyed account %q: %v", id, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,12 +11,6 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
authmethodTypePassword = "password"
|
||||
authmethodMinLoginNameLengthKey = "min_login_name_length"
|
||||
authmethodMinPasswordLengthKey = "min_password_length"
|
||||
)
|
||||
|
||||
func resourceAuthMethod() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The auth method resource allows you to configure a Boundary auth_method.",
|
||||
@@ -62,22 +56,32 @@ func resourceAuthMethod() *schema.Resource {
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Deprecated: "Will be removed in favor of using attributes parameter",
|
||||
},
|
||||
authmethodMinPasswordLengthKey: {
|
||||
Description: "The minimum password length.",
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Deprecated: "Will be removed in favor of using attributes parameter",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setFromAuthMethodResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
|
||||
d.Set(NameKey, raw["name"])
|
||||
d.Set(DescriptionKey, raw["description"])
|
||||
d.Set(ScopeIdKey, raw["scope_id"])
|
||||
d.Set(TypeKey, raw["type"])
|
||||
func setFromAuthMethodResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
|
||||
if err := d.Set(NameKey, raw["name"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(ScopeIdKey, raw["scope_id"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(TypeKey, raw["type"]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch raw["type"].(string) {
|
||||
case authmethodTypePassword:
|
||||
@@ -86,15 +90,19 @@ func setFromAuthMethodResponseMap(d *schema.ResourceData, raw map[string]interfa
|
||||
|
||||
minLoginNameLength := attrs["min_login_name_length"].(json.Number)
|
||||
minLoginNameLengthInt, _ := minLoginNameLength.Int64()
|
||||
d.Set(authmethodMinLoginNameLengthKey, int(minLoginNameLengthInt))
|
||||
if err := d.Set(authmethodMinLoginNameLengthKey, int(minLoginNameLengthInt)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
minPasswordLength := attrs["min_password_length"].(json.Number)
|
||||
minPasswordLengthInt, _ := minPasswordLength.Int64()
|
||||
d.Set(authmethodMinPasswordLengthKey, int(minPasswordLengthInt))
|
||||
if err := d.Set(authmethodMinPasswordLengthKey, int(minPasswordLengthInt)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceAuthMethodCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
@@ -107,48 +115,31 @@ func resourceAuthMethodCreate(ctx context.Context, d *schema.ResourceData, meta
|
||||
return diag.Errorf("no scope ID provided")
|
||||
}
|
||||
|
||||
var minLoginNameLength *int
|
||||
if minLengthVal, ok := d.GetOk(authmethodMinLoginNameLengthKey); ok {
|
||||
minLength := minLengthVal.(int)
|
||||
minLoginNameLength = &minLength
|
||||
}
|
||||
|
||||
var minPasswordLength *int
|
||||
if minLengthVal, ok := d.GetOk(authmethodMinPasswordLengthKey); ok {
|
||||
minLength := minLengthVal.(int)
|
||||
minPasswordLength = &minLength
|
||||
}
|
||||
|
||||
opts := []authmethods.Option{}
|
||||
|
||||
var typeStr string
|
||||
if typeVal, ok := d.GetOk(TypeKey); ok {
|
||||
typeStr = typeVal.(string)
|
||||
} else {
|
||||
return diag.Errorf("no type provided")
|
||||
}
|
||||
switch typeStr {
|
||||
case authmethodTypePassword:
|
||||
if minLoginNameLength != nil {
|
||||
opts = append(opts, authmethods.WithPasswordAuthMethodMinLoginNameLength(uint32(*minLoginNameLength)))
|
||||
}
|
||||
if minPasswordLength != nil {
|
||||
opts = append(opts, authmethods.WithPasswordAuthMethodMinPasswordLength(uint32(*minPasswordLength)))
|
||||
}
|
||||
default:
|
||||
return diag.Errorf("invalid type provided")
|
||||
|
||||
opts := []authmethods.Option{}
|
||||
|
||||
if nameVal, ok := d.GetOk(NameKey); ok {
|
||||
opts = append(opts, authmethods.WithName(nameVal.(string)))
|
||||
}
|
||||
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
nameStr := nameVal.(string)
|
||||
opts = append(opts, authmethods.WithName(nameStr))
|
||||
if descVal, ok := d.GetOk(DescriptionKey); ok {
|
||||
opts = append(opts, authmethods.WithDescription(descVal.(string)))
|
||||
}
|
||||
|
||||
descVal, ok := d.GetOk(DescriptionKey)
|
||||
if ok {
|
||||
descStr := descVal.(string)
|
||||
opts = append(opts, authmethods.WithDescription(descStr))
|
||||
// TODO(malnick) - deprecate
|
||||
if minLengthVal, ok := d.GetOk(authmethodMinLoginNameLengthKey); ok {
|
||||
opts = append(opts, authmethods.WithPasswordAuthMethodMinLoginNameLength(uint32(minLengthVal.(int))))
|
||||
}
|
||||
|
||||
// TODO(malnick) - deprecate
|
||||
if minLengthVal, ok := d.GetOk(authmethodMinPasswordLengthKey); ok {
|
||||
opts = append(opts, authmethods.WithPasswordAuthMethodMinPasswordLength(uint32(minLengthVal.(int))))
|
||||
}
|
||||
|
||||
amClient := authmethods.NewClient(md.client)
|
||||
@@ -161,7 +152,9 @@ func resourceAuthMethodCreate(ctx context.Context, d *schema.ResourceData, meta
|
||||
return diag.Errorf("nil auth method after create")
|
||||
}
|
||||
|
||||
setFromAuthMethodResponseMap(d, amcr.GetResponseMap())
|
||||
if err := setFromAuthMethodResponseMap(d, amcr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -172,7 +165,7 @@ func resourceAuthMethodRead(ctx context.Context, d *schema.ResourceData, meta in
|
||||
|
||||
amrr, err := amClient.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr.ResponseStatus() == http.StatusNotFound {
|
||||
if apiErr := api.AsServerError(err); apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
@@ -182,7 +175,9 @@ func resourceAuthMethodRead(ctx context.Context, d *schema.ResourceData, meta in
|
||||
return diag.Errorf("auth method nil after read")
|
||||
}
|
||||
|
||||
setFromAuthMethodResponseMap(d, amrr.GetResponseMap())
|
||||
if err := setFromAuthMethodResponseMap(d, amrr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -193,81 +188,43 @@ func resourceAuthMethodUpdate(ctx context.Context, d *schema.ResourceData, meta
|
||||
|
||||
opts := []authmethods.Option{}
|
||||
|
||||
var name *string
|
||||
if d.HasChange(NameKey) {
|
||||
opts = append(opts, authmethods.DefaultName())
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
nameStr := nameVal.(string)
|
||||
name = &nameStr
|
||||
opts = append(opts, authmethods.WithName(nameStr))
|
||||
if nameVal, ok := d.GetOk(NameKey); ok {
|
||||
opts = append(opts, authmethods.WithName(nameVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
var desc *string
|
||||
if d.HasChange(DescriptionKey) {
|
||||
opts = append(opts, authmethods.DefaultDescription())
|
||||
descVal, ok := d.GetOk(DescriptionKey)
|
||||
if ok {
|
||||
descStr := descVal.(string)
|
||||
desc = &descStr
|
||||
opts = append(opts, authmethods.WithDescription(descStr))
|
||||
if descVal, ok := d.GetOk(DescriptionKey); ok {
|
||||
opts = append(opts, authmethods.WithDescription(descVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
var minLoginNameLength *int
|
||||
if d.HasChange(authmethodMinLoginNameLengthKey) {
|
||||
switch d.Get(TypeKey).(string) {
|
||||
case authmethodTypePassword:
|
||||
opts = append(opts, authmethods.DefaultPasswordAuthMethodMinLoginNameLength())
|
||||
minLengthVal, ok := d.GetOk(authmethodMinLoginNameLengthKey)
|
||||
if ok {
|
||||
minLengthInt := minLengthVal.(int)
|
||||
minLoginNameLength = &minLengthInt
|
||||
opts = append(opts, authmethods.WithPasswordAuthMethodMinLoginNameLength(uint32(minLengthInt)))
|
||||
}
|
||||
default:
|
||||
return diag.Errorf(`"min_login_name_length" cannot be used with this type of auth method`)
|
||||
}
|
||||
}
|
||||
|
||||
var minPasswordLength *int
|
||||
// TODO(malnick) - deprecate
|
||||
if d.HasChange(authmethodMinPasswordLengthKey) {
|
||||
switch d.Get(TypeKey).(string) {
|
||||
case authmethodTypePassword:
|
||||
opts = append(opts, authmethods.DefaultPasswordAuthMethodMinPasswordLength())
|
||||
minLengthVal, ok := d.GetOk(authmethodMinPasswordLengthKey)
|
||||
if ok {
|
||||
minLengthInt := minLengthVal.(int)
|
||||
minPasswordLength = &minLengthInt
|
||||
opts = append(opts, authmethods.WithPasswordAuthMethodMinPasswordLength(uint32(minLengthInt)))
|
||||
}
|
||||
default:
|
||||
return diag.Errorf(`"min_password_length" cannot be used with this type of auth method`)
|
||||
opts = append(opts, authmethods.DefaultPasswordAuthMethodMinPasswordLength())
|
||||
if minLengthVal, ok := d.GetOk(authmethodMinPasswordLengthKey); ok {
|
||||
opts = append(opts, authmethods.WithPasswordAuthMethodMinPasswordLength(uint32(minLengthVal.(int))))
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts) > 0 {
|
||||
opts = append(opts, authmethods.WithAutomaticVersioning(true))
|
||||
_, err := amClient.Update(ctx, d.Id(), 0, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating auth method: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(NameKey) {
|
||||
d.Set(NameKey, name)
|
||||
}
|
||||
if d.HasChange(DescriptionKey) {
|
||||
d.Set(DescriptionKey, desc)
|
||||
}
|
||||
// TODO(malnick) - deprecate
|
||||
if d.HasChange(authmethodMinLoginNameLengthKey) {
|
||||
d.Set(authmethodMinLoginNameLengthKey, minLoginNameLength)
|
||||
opts = append(opts, authmethods.DefaultPasswordAuthMethodMinLoginNameLength())
|
||||
if minLengthVal, ok := d.GetOk(authmethodMinLoginNameLengthKey); ok {
|
||||
opts = append(opts, authmethods.WithPasswordAuthMethodMinLoginNameLength(uint32(minLengthVal.(int))))
|
||||
}
|
||||
}
|
||||
if d.HasChange(authmethodMinPasswordLengthKey) {
|
||||
d.Set(authmethodMinPasswordLengthKey, minPasswordLength)
|
||||
|
||||
opts = append(opts, authmethods.WithAutomaticVersioning(true))
|
||||
amu, err := amClient.Update(ctx, d.Id(), 0, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating auth method: %v", err)
|
||||
}
|
||||
|
||||
setFromAuthMethodResponseMap(d, amu.GetResponse().Map)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,564 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/authmethods"
|
||||
"github.com/hashicorp/boundary/api/scopes"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
authmethodTypeOidc = "oidc"
|
||||
authmethodOidcIssuerKey = "issuer"
|
||||
authmethodOidcClientIdKey = "client_id"
|
||||
authmethodOidcClientSecretKey = "client_secret"
|
||||
authmethodOidcMaxAgeKey = "max_age"
|
||||
authmethodOidcApiUrlPrefixKey = "api_url_prefix"
|
||||
authmethodOidcIdpCaCertsKey = "idp_ca_certs"
|
||||
authmethodOidcAllowedAudiencesKey = "allowed_audiences"
|
||||
authmethodOidcDisableDiscoveredConfigValidationKey = "disable_discovered_config_validation"
|
||||
authmethodOidcSigningAlgorithmsKey = "signing_algorithms"
|
||||
authmethodOidcIsPrimaryAuthMethodForScope = "is_primary_for_scope"
|
||||
authmethodOidcAccountClaimMapsKey = "account_claim_maps"
|
||||
authmethodOidcClaimsScopesKey = "claims_scopes"
|
||||
|
||||
// computed-only parameters
|
||||
authmethodOidcCallbackUrlKey = "callback_url"
|
||||
authmethodOidcClientSecretHmacKey = "client_secret_hmac"
|
||||
authmethodOidcStateKey = "state"
|
||||
)
|
||||
|
||||
func resourceAuthMethodOidc() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The OIDC auth method resource allows you to configure a Boundary auth_method_oidc.",
|
||||
|
||||
CreateContext: resourceAuthMethodOidcCreate,
|
||||
ReadContext: resourceAuthMethodOidcRead,
|
||||
UpdateContext: resourceAuthMethodOidcUpdate,
|
||||
DeleteContext: resourceAuthMethodOidcDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: schema.ImportStatePassthroughContext,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
IDKey: {
|
||||
Description: "The ID of the auth method.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
NameKey: {
|
||||
Description: "The auth method name. Defaults to the resource name.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
DescriptionKey: {
|
||||
Description: "The auth method description.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
ScopeIdKey: {
|
||||
Description: "The scope ID.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
// OIDC specific configurable parameters
|
||||
authmethodOidcAllowedAudiencesKey: {
|
||||
Description: "Audiences for which the provider responses will be allowed",
|
||||
Type: schema.TypeList,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
authmethodOidcApiUrlPrefixKey: {
|
||||
Description: "The API prefix to use when generating callback URLs for the provider. Should be set to an address at which the provider can reach back to the controller.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
authmethodOidcIdpCaCertsKey: {
|
||||
Description: "A list of CA certificates to trust when validating the IdP's token signatures.",
|
||||
Type: schema.TypeList,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
authmethodOidcClientIdKey: {
|
||||
Description: "The client ID assigned to this auth method from the provider.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
authmethodOidcClientSecretKey: {
|
||||
Description: "The secret key assigned to this auth method from the provider. Once set, only the hash will be kept and the original value can be removed from configuration.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
authmethodOidcIssuerKey: {
|
||||
Description: "The issuer corresponding to the provider, which must match the issuer field in generated tokens.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
authmethodOidcDisableDiscoveredConfigValidationKey: {
|
||||
Description: "Disables validation logic ensuring that the OIDC provider's information from its discovery endpoint matches the information here. The validation is only performed at create or update time.",
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
},
|
||||
authmethodOidcMaxAgeKey: {
|
||||
Description: "The max age to provide to the provider, indicating how much time is allowed to have passed since the last authentication before the user is challenged again.",
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
},
|
||||
authmethodOidcSigningAlgorithmsKey: {
|
||||
Description: "Allowed signing algorithms for the provider's issued tokens.",
|
||||
Type: schema.TypeList,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
authmethodOidcAccountClaimMapsKey: {
|
||||
Description: "Account claim maps for the to_claim of sub.",
|
||||
Type: schema.TypeList,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
Optional: true,
|
||||
// per comment in https://github.com/hashicorp/boundary/pull/1186
|
||||
ForceNew: true,
|
||||
},
|
||||
authmethodOidcClaimsScopesKey: {
|
||||
Description: "Claims scopes.",
|
||||
Type: schema.TypeList,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
Optional: true,
|
||||
},
|
||||
|
||||
// OIDC specific immutable and computed parameters
|
||||
authmethodOidcClientSecretHmacKey: {
|
||||
Description: "The HMAC of the client secret returned by the Boundary controller, which is used for comparison after initial setting of the value.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
authmethodOidcStateKey: {
|
||||
Description: "Can be one of 'inactive', 'active-private', or 'active-public'. Currently automatically set to active-public.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
authmethodOidcCallbackUrlKey: {
|
||||
Description: "The URL that should be provided to the IdP for callbacks.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
authmethodOidcIsPrimaryAuthMethodForScope: {
|
||||
Description: "When true, makes this auth method the primary auth method for the scope in which it resides. The primary auth method for a scope means the the user will be automatically created when they login using an OIDC account.",
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
},
|
||||
|
||||
TypeKey: {
|
||||
Description: "The type of auth method; hardcoded.",
|
||||
Type: schema.TypeString,
|
||||
Default: authmethodTypeOidc,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setFromOidcAuthMethodResponseMap(d *schema.ResourceData, raw map[string]interface{}) diag.Diagnostics {
|
||||
d.Set(NameKey, raw[NameKey])
|
||||
d.Set(DescriptionKey, raw[DescriptionKey])
|
||||
d.Set(ScopeIdKey, raw[ScopeIdKey])
|
||||
d.Set(TypeKey, raw[TypeKey])
|
||||
|
||||
if attrsVal, ok := raw["attributes"]; ok {
|
||||
attrs := attrsVal.(map[string]interface{})
|
||||
|
||||
// these are always set
|
||||
d.Set(authmethodOidcStateKey, attrs[authmethodOidcStateKey].(string))
|
||||
d.Set(authmethodOidcIssuerKey, attrs[authmethodOidcIssuerKey].(string))
|
||||
d.Set(authmethodOidcClientIdKey, attrs[authmethodOidcClientIdKey].(string))
|
||||
d.Set(authmethodOidcClientSecretHmacKey, attrs[authmethodOidcClientSecretHmacKey].(string))
|
||||
|
||||
if certs, ok := attrs[authmethodOidcIdpCaCertsKey]; ok {
|
||||
d.Set(authmethodOidcIdpCaCertsKey, certs.([]interface{}))
|
||||
}
|
||||
|
||||
if aud, ok := attrs[authmethodOidcAllowedAudiencesKey]; ok {
|
||||
d.Set(authmethodOidcAllowedAudiencesKey, aud.([]interface{}))
|
||||
}
|
||||
|
||||
if m, ok := attrs[authmethodOidcMaxAgeKey]; ok {
|
||||
maxAge := m.(json.Number)
|
||||
maxAgeInt, _ := maxAge.Int64()
|
||||
d.Set(authmethodOidcMaxAgeKey, maxAgeInt)
|
||||
}
|
||||
|
||||
if val, ok := attrs[authmethodOidcApiUrlPrefixKey]; ok {
|
||||
d.Set(authmethodOidcApiUrlPrefixKey, val.(string))
|
||||
}
|
||||
|
||||
if val, ok := attrs[authmethodOidcCallbackUrlKey]; ok {
|
||||
d.Set(authmethodOidcCallbackUrlKey, val.(string))
|
||||
}
|
||||
|
||||
if val, ok := attrs[authmethodOidcSigningAlgorithmsKey]; ok {
|
||||
d.Set(authmethodOidcSigningAlgorithmsKey, val.([]interface{}))
|
||||
}
|
||||
|
||||
if p, ok := attrs[authmethodOidcIsPrimaryAuthMethodForScope]; ok {
|
||||
d.Set(authmethodOidcIsPrimaryAuthMethodForScope, p.(bool))
|
||||
}
|
||||
|
||||
if p, ok := attrs[authmethodOidcDisableDiscoveredConfigValidationKey]; ok {
|
||||
d.Set(authmethodOidcDisableDiscoveredConfigValidationKey, p.(bool))
|
||||
}
|
||||
|
||||
if p, ok := attrs[authmethodOidcAccountClaimMapsKey]; ok {
|
||||
d.Set(authmethodOidcAccountClaimMapsKey, p.([]interface{}))
|
||||
}
|
||||
|
||||
if p, ok := attrs[authmethodOidcClaimsScopesKey]; ok {
|
||||
d.Set(authmethodOidcClaimsScopesKey, p.([]interface{}))
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceAuthMethodOidcCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
|
||||
var typeStr string
|
||||
if typeVal, ok := d.GetOk(TypeKey); ok {
|
||||
typeStr = typeVal.(string)
|
||||
} else {
|
||||
return diag.Errorf("no type provided")
|
||||
}
|
||||
|
||||
opts := []authmethods.Option{}
|
||||
|
||||
opts = append(opts, authmethods.WithAutomaticVersioning(true))
|
||||
|
||||
if issuer, ok := d.GetOk(authmethodOidcIssuerKey); ok {
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodIssuer(issuer.(string)))
|
||||
}
|
||||
if clientId, ok := d.GetOk(authmethodOidcClientIdKey); ok {
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodClientId(clientId.(string)))
|
||||
}
|
||||
if clientSecret, ok := d.GetOk(authmethodOidcClientSecretKey); ok {
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodClientSecret(clientSecret.(string)))
|
||||
}
|
||||
if maxAge, ok := d.GetOk(authmethodOidcMaxAgeKey); ok {
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodMaxAge(uint32(maxAge.(int))))
|
||||
}
|
||||
if prefix, ok := d.GetOk(authmethodOidcApiUrlPrefixKey); ok {
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodApiUrlPrefix(prefix.(string)))
|
||||
}
|
||||
if certs, ok := d.GetOk(authmethodOidcIdpCaCertsKey); ok {
|
||||
certList := []string{}
|
||||
for _, c := range certs.([]interface{}) {
|
||||
certList = append(certList, c.(string))
|
||||
}
|
||||
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodIdpCaCerts(certList))
|
||||
}
|
||||
if aud, ok := d.GetOk(authmethodOidcAllowedAudiencesKey); ok {
|
||||
audList := []string{}
|
||||
for _, c := range aud.([]interface{}) {
|
||||
audList = append(audList, c.(string))
|
||||
}
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodAllowedAudiences(audList))
|
||||
}
|
||||
if dis, ok := d.GetOk(authmethodOidcDisableDiscoveredConfigValidationKey); ok {
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodDisableDiscoveredConfigValidation(dis.(bool)))
|
||||
}
|
||||
if algos, ok := d.GetOk(authmethodOidcSigningAlgorithmsKey); ok {
|
||||
algoList := []string{}
|
||||
for _, c := range algos.([]interface{}) {
|
||||
algoList = append(algoList, c.(string))
|
||||
}
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodSigningAlgorithms(algoList))
|
||||
}
|
||||
|
||||
if claims, ok := d.GetOk(authmethodOidcAccountClaimMapsKey); ok {
|
||||
cList := []string{}
|
||||
for _, c := range claims.([]interface{}) {
|
||||
cList = append(cList, c.(string))
|
||||
}
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodAccountClaimMaps(cList))
|
||||
}
|
||||
|
||||
if claimsScopes, ok := d.GetOk(authmethodOidcClaimsScopesKey); ok {
|
||||
cList := []string{}
|
||||
for _, c := range claimsScopes.([]interface{}) {
|
||||
cList = append(cList, c.(string))
|
||||
}
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodClaimsScopes(cList))
|
||||
}
|
||||
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
nameStr := nameVal.(string)
|
||||
opts = append(opts, authmethods.WithName(nameStr))
|
||||
}
|
||||
|
||||
descVal, ok := d.GetOk(DescriptionKey)
|
||||
if ok {
|
||||
descStr := descVal.(string)
|
||||
opts = append(opts, authmethods.WithDescription(descStr))
|
||||
}
|
||||
|
||||
var scopeId string
|
||||
if scopeIdVal, ok := d.GetOk(ScopeIdKey); ok {
|
||||
scopeId = scopeIdVal.(string)
|
||||
} else {
|
||||
return diag.Errorf("no scope ID provided")
|
||||
}
|
||||
|
||||
amClient := authmethods.NewClient(md.client)
|
||||
|
||||
amcr, err := amClient.Create(ctx, typeStr, scopeId, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error creating auth method: %v", err)
|
||||
}
|
||||
if amcr == nil {
|
||||
return diag.Errorf("nil auth method after create")
|
||||
}
|
||||
// need to update state is create was successful to avoid loosing state if the change state or update
|
||||
// scope step that follows errors out
|
||||
if err := setFromOidcAuthMethodResponseMap(d, amcr.GetResponse().Map); err != nil {
|
||||
return diag.Errorf("%v", err)
|
||||
}
|
||||
|
||||
amid := amcr.GetResponse().Map["id"].(string)
|
||||
|
||||
// auto set to active-public state
|
||||
_, err = amClient.ChangeState(ctx, amid, 0, "active-public", authmethods.WithAutomaticVersioning(true))
|
||||
if err != nil {
|
||||
return diag.Errorf("%v", err)
|
||||
}
|
||||
|
||||
// update scope when set to primary
|
||||
if p, ok := d.GetOk(authmethodOidcIsPrimaryAuthMethodForScope); ok {
|
||||
if p.(bool) {
|
||||
if err := updateScopeWithPrimaryAuthMethodId(ctx, scopeId, amid, meta); err != nil {
|
||||
return diag.Errorf("%v", err)
|
||||
}
|
||||
|
||||
amcr.GetResponse().Map[authmethodOidcIsPrimaryAuthMethodForScope] = true
|
||||
}
|
||||
}
|
||||
|
||||
return setFromOidcAuthMethodResponseMap(d, amcr.GetResponse().Map)
|
||||
}
|
||||
|
||||
func updateScopeWithPrimaryAuthMethodId(ctx context.Context, scopeId, authmethodId string, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
scp := scopes.NewClient(md.client)
|
||||
|
||||
opts := []scopes.Option{}
|
||||
opts = append(opts, scopes.WithAutomaticVersioning(true))
|
||||
opts = append(opts, scopes.WithPrimaryAuthMethodId(authmethodId))
|
||||
|
||||
_, err := scp.Update(ctx, scopeId, 0, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating scope: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readScopeIsPrimaryAuthMethodId(ctx context.Context, scopeId, authmethodId string, meta interface{}) (diag.Diagnostics, bool) {
|
||||
md := meta.(*metaData)
|
||||
scp := scopes.NewClient(md.client)
|
||||
|
||||
srr, err := scp.Read(ctx, scopeId)
|
||||
if err != nil {
|
||||
return diag.Errorf("%s", err), false
|
||||
}
|
||||
|
||||
if p, ok := srr.GetResponse().Map["primary_auth_method_id"]; ok {
|
||||
if p == authmethodId {
|
||||
return nil, true
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func resourceAuthMethodOidcRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
amClient := authmethods.NewClient(md.client)
|
||||
|
||||
amrr, err := amClient.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
return diag.Errorf("error reading auth method: %v", err)
|
||||
}
|
||||
if amrr == nil {
|
||||
return diag.Errorf("auth method nil after read")
|
||||
}
|
||||
|
||||
serr, isPrimary := readScopeIsPrimaryAuthMethodId(ctx, amrr.GetResponse().Map["scope_id"].(string), amrr.GetResponse().Map["id"].(string), meta)
|
||||
if err != nil {
|
||||
return diag.Errorf("%v", serr)
|
||||
}
|
||||
|
||||
if isPrimary {
|
||||
amrr.GetResponse().Map[authmethodOidcIsPrimaryAuthMethodForScope] = true
|
||||
}
|
||||
|
||||
return setFromOidcAuthMethodResponseMap(d, amrr.GetResponse().Map)
|
||||
}
|
||||
|
||||
func resourceAuthMethodOidcUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
amClient := authmethods.NewClient(md.client)
|
||||
|
||||
opts := []authmethods.Option{}
|
||||
|
||||
if d.HasChange(NameKey) {
|
||||
opts = append(opts, authmethods.DefaultName())
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
opts = append(opts, authmethods.WithName(nameVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(DescriptionKey) {
|
||||
opts = append(opts, authmethods.DefaultDescription())
|
||||
descVal, ok := d.GetOk(DescriptionKey)
|
||||
if ok {
|
||||
opts = append(opts, authmethods.WithDescription(descVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(authmethodOidcIssuerKey) {
|
||||
if issuer, ok := d.GetOk(authmethodOidcIssuerKey); ok {
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodIssuer(issuer.(string)))
|
||||
}
|
||||
}
|
||||
if d.HasChange(authmethodOidcClientIdKey) {
|
||||
if clientId, ok := d.GetOk(authmethodOidcClientIdKey); ok {
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodClientId(clientId.(string)))
|
||||
}
|
||||
}
|
||||
if d.HasChange(authmethodOidcClientSecretKey) {
|
||||
if clientSecret, ok := d.GetOk(authmethodOidcClientSecretKey); ok {
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodClientSecret(clientSecret.(string)))
|
||||
}
|
||||
}
|
||||
if d.HasChange(authmethodOidcMaxAgeKey) {
|
||||
if maxAge, ok := d.GetOk(authmethodOidcMaxAgeKey); ok {
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodMaxAge(uint32(maxAge.(int))))
|
||||
}
|
||||
}
|
||||
if d.HasChange(authmethodOidcSigningAlgorithmsKey) {
|
||||
if algos, ok := d.GetOk(authmethodOidcSigningAlgorithmsKey); ok {
|
||||
var signingAlgs []string
|
||||
for _, alg := range algos.([]interface{}) {
|
||||
signingAlgs = append(signingAlgs, alg.(string))
|
||||
}
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodSigningAlgorithms(signingAlgs))
|
||||
}
|
||||
}
|
||||
if d.HasChange(authmethodOidcApiUrlPrefixKey) {
|
||||
if prefix, ok := d.GetOk(authmethodOidcApiUrlPrefixKey); ok {
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodApiUrlPrefix(prefix.(string)))
|
||||
}
|
||||
}
|
||||
if d.HasChange(authmethodOidcClientSecretHmacKey) {
|
||||
if sec, ok := d.GetOk(authmethodOidcClientSecretHmacKey); ok {
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodClientSecret(sec.(string)))
|
||||
}
|
||||
}
|
||||
if d.HasChange(authmethodOidcAllowedAudiencesKey) {
|
||||
if val, ok := d.GetOk(authmethodOidcAllowedAudiencesKey); ok {
|
||||
var audiences []string
|
||||
for _, aud := range val.([]interface{}) {
|
||||
audiences = append(audiences, aud.(string))
|
||||
}
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodAllowedAudiences(audiences))
|
||||
}
|
||||
}
|
||||
if d.HasChange(authmethodOidcIdpCaCertsKey) {
|
||||
if val, ok := d.GetOk(authmethodOidcIdpCaCertsKey); ok {
|
||||
certs := []string{}
|
||||
for _, cert := range val.([]interface{}) {
|
||||
certs = append(certs, cert.(string))
|
||||
}
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodIdpCaCerts(certs))
|
||||
}
|
||||
}
|
||||
if d.HasChange(authmethodOidcDisableDiscoveredConfigValidationKey) {
|
||||
if val, ok := d.GetOk(authmethodOidcDisableDiscoveredConfigValidationKey); ok {
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodDisableDiscoveredConfigValidation(val.(bool)))
|
||||
}
|
||||
}
|
||||
if d.HasChange(authmethodOidcClaimsScopesKey) {
|
||||
if val, ok := d.GetOk(authmethodOidcClaimsScopesKey); ok {
|
||||
claimsScopes := []string{}
|
||||
for _, c := range val.([]interface{}) {
|
||||
claimsScopes = append(claimsScopes, c.(string))
|
||||
}
|
||||
opts = append(opts, authmethods.WithOidcAuthMethodClaimsScopes(claimsScopes))
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts) > 0 {
|
||||
opts = append(opts, authmethods.WithAutomaticVersioning(true))
|
||||
amur, err := amClient.Update(ctx, d.Id(), 0, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating auth method: %v", err)
|
||||
}
|
||||
|
||||
if d.HasChange(authmethodOidcIsPrimaryAuthMethodForScope) {
|
||||
if p, ok := d.GetOk(authmethodOidcIsPrimaryAuthMethodForScope); ok {
|
||||
if p.(bool) {
|
||||
if err := updateScopeWithPrimaryAuthMethodId(
|
||||
ctx,
|
||||
amur.GetResponse().Map["scope_id"].(string),
|
||||
amur.GetResponse().Map["id"].(string),
|
||||
meta); err != nil {
|
||||
return diag.Errorf("%v", err)
|
||||
}
|
||||
|
||||
amur.GetResponse().Map[authmethodOidcIsPrimaryAuthMethodForScope] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return setFromOidcAuthMethodResponseMap(d, amur.GetResponse().Map)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceAuthMethodOidcDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
amClient := authmethods.NewClient(md.client)
|
||||
|
||||
_, err := amClient.Delete(ctx, d.Id())
|
||||
if err != nil {
|
||||
return diag.Errorf("error deleting auth method: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/authmethods"
|
||||
"github.com/hashicorp/boundary/api/scopes"
|
||||
"github.com/hashicorp/boundary/testing/controller"
|
||||
"github.com/hashicorp/cap/oidc"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
||||
)
|
||||
|
||||
const (
|
||||
fooAuthMethodOidcDesc = "test auth method oidc"
|
||||
fooAuthMethodOidcDescUpdate = "test auth method oidc update"
|
||||
fooAuthMethodOidcCaCerts = `-----BEGIN CERTIFICATE-----
|
||||
MIIDsjCCApoCCQCslgm7fAu/VzANBgkqhkiG9w0BAQsFADCBmjELMAkGA1UEBhMC
|
||||
VVMxCzAJBgNVBAgMAldBMRMwEQYDVQQHDApCZWxsaW5naGFtMRIwEAYDVQQKDAlI
|
||||
YXNoaUNvcnAxETAPBgNVBAsMCEJvdW5kYXJ5MRswGQYDVQQDDBJib3VuZGFyeXBy
|
||||
b2plY3QuaW8xJTAjBgkqhkiG9w0BCQEWFmptYWxuaWNrQGhhc2hpY29ycC5jb20w
|
||||
HhcNMjEwNDA2MjMzNTIxWhcNMjYwNDA1MjMzNTIxWjCBmjELMAkGA1UEBhMCVVMx
|
||||
CzAJBgNVBAgMAldBMRMwEQYDVQQHDApCZWxsaW5naGFtMRIwEAYDVQQKDAlIYXNo
|
||||
aUNvcnAxETAPBgNVBAsMCEJvdW5kYXJ5MRswGQYDVQQDDBJib3VuZGFyeXByb2pl
|
||||
Y3QuaW8xJTAjBgkqhkiG9w0BCQEWFmptYWxuaWNrQGhhc2hpY29ycC5jb20wggEi
|
||||
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUa/ZLwYhTQ2hGzlwy/sB9xY9h
|
||||
dM4qzY8DUF3Sin/+J2j59q3/vXZA7PS+o1GhoG1nW3J51zgyNFOY/EKHdtxVBken
|
||||
YTTG+JswzrcTxsMV7/sYDgCLq6W8dPMV72gPH/3dRi3/0KUHtA6rBOf0Shf0f6Sz
|
||||
7VmgTWcNmLvXpHKOs4YkjOL/tyflTgNm5j1dOa53TtwtMyvCcpGrB7PGL8m5+E2U
|
||||
qxOzQ9kWfA6zr4Gl5rIm+Us8Ez3n1yGwjwFBteexk1Fot8zWKhoy7pZ3ZjWRpjwL
|
||||
hfGs5eJs4kERQVAGONt39ZIR6OzOFxAsvI9WrMvxAsdCK63RtF2k4r0X21yDAgMB
|
||||
AAEwDQYJKoZIhvcNAQELBQADggEBAJZcl7Zxjya23IcOV8jZDdCHtqnbcg9TcQb+
|
||||
kpX1uEKJMFJoNmNK1q//nJxG1YBn3G8t9XtO6Kc6egdGHXWnOsM37N9hbYPJ2kW1
|
||||
WWAwqWkQbV3wb0cc6MuU1S9xivOqwM046ZIcjrWR4T4tEUSUfYc3I+Yd8APdapn8
|
||||
vePgWnmi/aSsx9RxVOUrzmVhzgN7rQJZGwnYYnxl4cwy2jxpysmXzg/grfXCZs/V
|
||||
Kkc7Y5Ph6vRQ+vPCeB7QUxHxjlr8aq+rYDIaSiZ+/4+qyme0ergfvZmMSU8A3NNS
|
||||
tYIMds5s2lIqVwOoyzpBEOjWBhUThH+aZu1A5c7Cb7s1eLSRX70=
|
||||
-----END CERTIFICATE-----`
|
||||
)
|
||||
|
||||
var (
|
||||
fooAuthMethodOidc = `
|
||||
resource "boundary_auth_method_oidc" "foo" {
|
||||
name = "test"
|
||||
description = "%s"
|
||||
scope_id = boundary_scope.org1.id
|
||||
depends_on = [boundary_role.org1_admin]
|
||||
|
||||
issuer = "%s"
|
||||
client_id = "foo_id"
|
||||
client_secret = "foo_secret"
|
||||
max_age = 10
|
||||
api_url_prefix = "http://localhost:9200"
|
||||
idp_ca_certs = [
|
||||
<<EOT
|
||||
%s
|
||||
EOT
|
||||
]
|
||||
allowed_audiences = ["foo_aud"]
|
||||
signing_algorithms = ["ES256"]
|
||||
account_claim_maps = ["oid=sub"]
|
||||
claims_scopes = ["profile"]
|
||||
}`
|
||||
|
||||
fooAuthMethodOidcUpdate = `
|
||||
resource "boundary_auth_method_oidc" "foo" {
|
||||
name = "test"
|
||||
description = "%s"
|
||||
scope_id = boundary_scope.org1.id
|
||||
is_primary_for_scope = true
|
||||
depends_on = [boundary_role.org1_admin]
|
||||
|
||||
issuer = "https://test-update.com"
|
||||
client_id = "foo_id_update"
|
||||
client_secret = "foo_secret_update"
|
||||
max_age = 1
|
||||
api_url_prefix = "http://localhost:9200"
|
||||
idp_ca_certs = [
|
||||
<<EOT
|
||||
%s
|
||||
EOT
|
||||
]
|
||||
allowed_audiences = ["foo_aud_update"]
|
||||
signing_algorithms = ["ES256"]
|
||||
account_claim_maps = ["oid=sub"]
|
||||
claims_scopes = ["profile"]
|
||||
|
||||
// we need to disable this validatin, since the updated issuer isn't discoverable
|
||||
disable_discovered_config_validation = true
|
||||
}`
|
||||
)
|
||||
|
||||
func TestAccAuthMethodOidc(t *testing.T) {
|
||||
tp := oidc.StartTestProvider(t)
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
tpCert := strings.TrimSpace(tp.CACert())
|
||||
createConfig := fmt.Sprintf(fooAuthMethodOidc, fooAuthMethodOidcDesc, tp.Addr(), tpCert)
|
||||
updateConfig := fmt.Sprintf(fooAuthMethodOidcUpdate, fooAuthMethodOidcDescUpdate, fooAuthMethodOidcCaCerts)
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckAuthMethodOidcResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// create
|
||||
Config: testConfig(url, fooOrg, createConfig),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", "description", fooAuthMethodOidcDesc),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", "name", "test"),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", authmethodOidcIssuerKey, tp.Addr()),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", authmethodOidcClientIdKey, "foo_id"),
|
||||
testAccCheckAuthMethodOidcAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcIdpCaCertsKey, []string{tpCert}),
|
||||
testAccCheckAuthMethodOidcAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcAllowedAudiencesKey, []string{"foo_aud"}),
|
||||
testAccCheckAuthMethodOidcAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcSigningAlgorithmsKey, []string{"ES256"}),
|
||||
testAccCheckAuthMethodOidcAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcAccountClaimMapsKey, []string{"oid=sub"}),
|
||||
testAccCheckAuthMethodOidcAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcClaimsScopesKey, []string{"profile"}),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", authmethodOidcMaxAgeKey, "10"),
|
||||
testAccCheckAuthMethodOidcResourceExists(provider, "boundary_auth_method_oidc.foo"),
|
||||
testAccIsPrimaryForScope(provider, "boundary_auth_method_oidc.foo", false),
|
||||
),
|
||||
},
|
||||
importStep("boundary_auth_method_oidc.foo", "client_secret", "is_primary_for_scope"),
|
||||
{
|
||||
// update
|
||||
Config: testConfig(url, fooOrg, updateConfig),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", "description", fooAuthMethodOidcDescUpdate),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", "name", "test"),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", authmethodOidcIssuerKey, "https://test-update.com"),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", authmethodOidcClientIdKey, "foo_id_update"),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_oidc.foo", authmethodOidcMaxAgeKey, "1"),
|
||||
testAccCheckAuthMethodOidcAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcIdpCaCertsKey, []string{fooAuthMethodOidcCaCerts}),
|
||||
testAccCheckAuthMethodOidcAttrAryValueSet(provider, "boundary_auth_method_oidc.foo", authmethodOidcAllowedAudiencesKey, []string{"foo_aud_update"}),
|
||||
testAccCheckAuthMethodOidcResourceExists(provider, "boundary_auth_method_oidc.foo"),
|
||||
testAccIsPrimaryForScope(provider, "boundary_auth_method_oidc.foo", true),
|
||||
testAccCheckAuthMethodOidcResourceExists(provider, "boundary_auth_method_oidc.foo"),
|
||||
),
|
||||
},
|
||||
importStep("boundary_auth_method_oidc.foo", "client_secret", "is_primary_for_scope"),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckAuthMethodOidcResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", name)
|
||||
}
|
||||
|
||||
id := rs.Primary.ID
|
||||
if id == "" {
|
||||
return fmt.Errorf("No ID is set")
|
||||
}
|
||||
|
||||
md := testProvider.Meta().(*metaData)
|
||||
|
||||
amClient := authmethods.NewClient(md.client)
|
||||
|
||||
if _, err := amClient.Read(context.Background(), id); err != nil {
|
||||
return fmt.Errorf("Got an error when reading auth method %q: %w", id, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckAuthMethodOidcResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if testProvider.Meta() == nil {
|
||||
t.Fatal("got nil provider metadata")
|
||||
}
|
||||
md := testProvider.Meta().(*metaData)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
switch rs.Type {
|
||||
case "boundary_auth_method_oidc":
|
||||
id := rs.Primary.ID
|
||||
|
||||
amClient := authmethods.NewClient(md.client)
|
||||
|
||||
_, err := amClient.Read(context.Background(), id)
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||
return fmt.Errorf("didn't get a 404 when reading destroyed auth method %q: %v", id, err)
|
||||
}
|
||||
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckAuthMethodOidcAttrAryValueSet(testProvider *schema.Provider, name string, key string, strAry []string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("auth method resource not found: %s", name)
|
||||
}
|
||||
|
||||
id := rs.Primary.ID
|
||||
if id == "" {
|
||||
return fmt.Errorf("auth method resource ID is not set")
|
||||
}
|
||||
|
||||
md := testProvider.Meta().(*metaData)
|
||||
amClient := authmethods.NewClient(md.client)
|
||||
|
||||
amr, err := amClient.Read(context.Background(), id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Got an error when reading auth method %q: %v", id, err)
|
||||
}
|
||||
|
||||
for _, got := range amr.Item.Attributes[key].([]interface{}) {
|
||||
ok := false
|
||||
for _, expected := range strAry {
|
||||
if strings.TrimSpace(got.(string)) == strings.TrimSpace(expected) {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("value not found in boundary\n %s: %s\n", key, got.(string))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccIsPrimaryForScope(tp *schema.Provider, name string, shouldBePrimary bool) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("auth method resource not found: %s", name)
|
||||
}
|
||||
|
||||
amId := rs.Primary.ID
|
||||
if amId == "" {
|
||||
return fmt.Errorf("auth method resource ID is not set")
|
||||
}
|
||||
|
||||
md := tp.Meta().(*metaData)
|
||||
amClient := authmethods.NewClient(md.client)
|
||||
|
||||
amr, err := amClient.Read(context.Background(), amId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Got an error when reading auth method %q: %v", amId, err)
|
||||
}
|
||||
|
||||
amScopeId, ok := amr.GetResponse().Map["scope_id"]
|
||||
if !ok {
|
||||
return fmt.Errorf("scope_id unset on auth method resource")
|
||||
}
|
||||
|
||||
scp := scopes.NewClient(md.client)
|
||||
|
||||
srr, err := scp.Read(context.Background(), amScopeId.(string))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
primaryScopeAuthMethodId, ok := srr.GetResponse().Map["primary_auth_method_id"]
|
||||
if !ok && shouldBePrimary {
|
||||
return fmt.Errorf("primary_auth_method_id is not set on scope resource response")
|
||||
}
|
||||
|
||||
if shouldBePrimary {
|
||||
if primaryScopeAuthMethodId != amId {
|
||||
return fmt.Errorf("auth method ('%s') should be primary for scope but scope returned '%s' for primary_auth_method_id", amId, primaryScopeAuthMethodId)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/authmethods"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
authmethodTypePassword = "password"
|
||||
authmethodMinLoginNameLengthKey = "min_login_name_length"
|
||||
authmethodMinPasswordLengthKey = "min_password_length"
|
||||
)
|
||||
|
||||
func resourceAuthMethodPassword() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The auth method resource allows you to configure a Boundary auth_method_password.",
|
||||
|
||||
CreateContext: resourceAuthMethodPasswordCreate,
|
||||
ReadContext: resourceAuthMethodPasswordRead,
|
||||
UpdateContext: resourceAuthMethodPasswordUpdate,
|
||||
DeleteContext: resourceAuthMethodPasswordDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: schema.ImportStatePassthroughContext,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
IDKey: {
|
||||
Description: "The ID of the account.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
NameKey: {
|
||||
Description: "The auth method name. Defaults to the resource name.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
DescriptionKey: {
|
||||
Description: "The auth method description.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
ScopeIdKey: {
|
||||
Description: "The scope ID.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
TypeKey: {
|
||||
Description: "The resource type, hardcoded per resource",
|
||||
Type: schema.TypeString,
|
||||
Default: authmethodTypePassword,
|
||||
Optional: true,
|
||||
},
|
||||
authmethodMinLoginNameLengthKey: {
|
||||
Description: "The minimum login name length.",
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
authmethodMinPasswordLengthKey: {
|
||||
Description: "The minimum password length.",
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setFromPasswordAuthMethodResponseMap(d *schema.ResourceData, raw map[string]interface{}) diag.Diagnostics {
|
||||
d.Set(NameKey, raw[NameKey])
|
||||
d.Set(DescriptionKey, raw[DescriptionKey])
|
||||
d.Set(ScopeIdKey, raw[ScopeIdKey])
|
||||
d.Set(TypeKey, raw[TypeKey])
|
||||
|
||||
if attrsVal, ok := raw["attributes"]; ok {
|
||||
attrs := attrsVal.(map[string]interface{})
|
||||
|
||||
minLoginNameLength := attrs[authmethodMinLoginNameLengthKey].(json.Number)
|
||||
minLoginNameLengthInt, _ := minLoginNameLength.Int64()
|
||||
d.Set(authmethodMinLoginNameLengthKey, int(minLoginNameLengthInt))
|
||||
|
||||
minPasswordLength := attrs[authmethodMinPasswordLengthKey].(json.Number)
|
||||
minPasswordLengthInt, _ := minPasswordLength.Int64()
|
||||
d.Set(authmethodMinPasswordLengthKey, int(minPasswordLengthInt))
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceAuthMethodPasswordCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
|
||||
var typeStr string
|
||||
if typeVal, ok := d.GetOk(TypeKey); ok {
|
||||
typeStr = typeVal.(string)
|
||||
} else {
|
||||
return diag.Errorf("no type provided")
|
||||
}
|
||||
|
||||
opts := []authmethods.Option{}
|
||||
|
||||
var minLoginNameLength *int
|
||||
if minLengthVal, ok := d.GetOk(authmethodMinLoginNameLengthKey); ok {
|
||||
minLength := minLengthVal.(int)
|
||||
minLoginNameLength = &minLength
|
||||
}
|
||||
if minLoginNameLength != nil {
|
||||
opts = append(opts, authmethods.WithPasswordAuthMethodMinLoginNameLength(uint32(*minLoginNameLength)))
|
||||
}
|
||||
|
||||
var minPasswordLength *int
|
||||
if minLengthVal, ok := d.GetOk(authmethodMinPasswordLengthKey); ok {
|
||||
minLength := minLengthVal.(int)
|
||||
minPasswordLength = &minLength
|
||||
}
|
||||
if minPasswordLength != nil {
|
||||
opts = append(opts, authmethods.WithPasswordAuthMethodMinPasswordLength(uint32(*minPasswordLength)))
|
||||
}
|
||||
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
nameStr := nameVal.(string)
|
||||
opts = append(opts, authmethods.WithName(nameStr))
|
||||
}
|
||||
|
||||
descVal, ok := d.GetOk(DescriptionKey)
|
||||
if ok {
|
||||
descStr := descVal.(string)
|
||||
opts = append(opts, authmethods.WithDescription(descStr))
|
||||
}
|
||||
|
||||
var scopeId string
|
||||
if scopeIdVal, ok := d.GetOk(ScopeIdKey); ok {
|
||||
scopeId = scopeIdVal.(string)
|
||||
} else {
|
||||
return diag.Errorf("no scope ID provided")
|
||||
}
|
||||
|
||||
amClient := authmethods.NewClient(md.client)
|
||||
|
||||
amcr, err := amClient.Create(ctx, typeStr, scopeId, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error creating auth method: %v", err)
|
||||
}
|
||||
if amcr == nil {
|
||||
return diag.Errorf("nil auth method after create")
|
||||
}
|
||||
|
||||
return setFromPasswordAuthMethodResponseMap(d, amcr.GetResponse().Map)
|
||||
}
|
||||
|
||||
func resourceAuthMethodPasswordRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
amClient := authmethods.NewClient(md.client)
|
||||
|
||||
amrr, err := amClient.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
return diag.Errorf("error reading auth method: %v", err)
|
||||
}
|
||||
if amrr == nil {
|
||||
return diag.Errorf("auth method nil after read")
|
||||
}
|
||||
|
||||
return setFromPasswordAuthMethodResponseMap(d, amrr.GetResponse().Map)
|
||||
}
|
||||
|
||||
func resourceAuthMethodPasswordUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
amClient := authmethods.NewClient(md.client)
|
||||
|
||||
opts := []authmethods.Option{}
|
||||
|
||||
if d.HasChange(NameKey) {
|
||||
opts = append(opts, authmethods.DefaultName())
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
opts = append(opts, authmethods.WithName(nameVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(DescriptionKey) {
|
||||
opts = append(opts, authmethods.DefaultDescription())
|
||||
descVal, ok := d.GetOk(DescriptionKey)
|
||||
if ok {
|
||||
opts = append(opts, authmethods.WithDescription(descVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(authmethodMinLoginNameLengthKey) {
|
||||
opts = append(opts, authmethods.DefaultPasswordAuthMethodMinLoginNameLength())
|
||||
minLengthVal, ok := d.GetOk(authmethodMinLoginNameLengthKey)
|
||||
if ok {
|
||||
opts = append(opts, authmethods.WithPasswordAuthMethodMinLoginNameLength(uint32(minLengthVal.(int))))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(authmethodMinPasswordLengthKey) {
|
||||
opts = append(opts, authmethods.DefaultPasswordAuthMethodMinPasswordLength())
|
||||
minLengthVal, ok := d.GetOk(authmethodMinPasswordLengthKey)
|
||||
if ok {
|
||||
opts = append(opts, authmethods.WithPasswordAuthMethodMinPasswordLength(uint32(minLengthVal.(int))))
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts) > 0 {
|
||||
opts = append(opts, authmethods.WithAutomaticVersioning(true))
|
||||
amur, err := amClient.Update(ctx, d.Id(), 0, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating auth method: %v", err)
|
||||
}
|
||||
|
||||
return setFromPasswordAuthMethodResponseMap(d, amur.GetResponse().Map)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceAuthMethodPasswordDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
amClient := authmethods.NewClient(md.client)
|
||||
|
||||
_, err := amClient.Delete(ctx, d.Id())
|
||||
if err != nil {
|
||||
return diag.Errorf("error deleting auth method: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/authmethods"
|
||||
"github.com/hashicorp/boundary/testing/controller"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
||||
)
|
||||
|
||||
const (
|
||||
fooAuthMethodDesc = "test auth method"
|
||||
fooAuthMethodDescUpdate = "test auth method update"
|
||||
)
|
||||
|
||||
var (
|
||||
fooAuthMethod = fmt.Sprintf(`
|
||||
resource "boundary_auth_method_password" "foo" {
|
||||
name = "test"
|
||||
description = "%s"
|
||||
type = "password"
|
||||
scope_id = boundary_scope.org1.id
|
||||
depends_on = [boundary_role.org1_admin]
|
||||
}`, fooAuthMethodDesc)
|
||||
|
||||
fooAuthMethodUpdate = fmt.Sprintf(`
|
||||
resource "boundary_auth_method_password" "foo" {
|
||||
name = "test"
|
||||
description = "%s"
|
||||
type = "password"
|
||||
scope_id = boundary_scope.org1.id
|
||||
depends_on = [boundary_role.org1_admin]
|
||||
}`, fooAuthMethodDescUpdate)
|
||||
)
|
||||
|
||||
func TestAccAuthMethodPassword(t *testing.T) {
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckAuthMethodResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// create
|
||||
Config: testConfig(url, fooOrg, fooAuthMethod),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_password.foo", "description", fooAuthMethodDesc),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_password.foo", "name", "test"),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_password.foo", "type", "password"),
|
||||
testAccCheckAuthMethodResourceExists(provider, "boundary_auth_method_password.foo"),
|
||||
),
|
||||
},
|
||||
importStep("boundary_auth_method_password.foo"),
|
||||
{
|
||||
// update
|
||||
Config: testConfig(url, fooOrg, fooAuthMethodUpdate),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_password.foo", "description", fooAuthMethodDescUpdate),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_password.foo", "name", "test"),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method_password.foo", "type", "password"),
|
||||
testAccCheckAuthMethodResourceExists(provider, "boundary_auth_method_password.foo"),
|
||||
),
|
||||
},
|
||||
importStep("boundary_auth_method_password.foo"),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckAuthMethodResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", name)
|
||||
}
|
||||
|
||||
id := rs.Primary.ID
|
||||
if id == "" {
|
||||
return fmt.Errorf("No ID is set")
|
||||
}
|
||||
|
||||
md := testProvider.Meta().(*metaData)
|
||||
|
||||
amClient := authmethods.NewClient(md.client)
|
||||
|
||||
if _, err := amClient.Read(context.Background(), id); err != nil {
|
||||
return fmt.Errorf("Got an error when reading auth method %q: %w", id, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckAuthMethodResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if testProvider.Meta() == nil {
|
||||
t.Fatal("got nil provider metadata")
|
||||
}
|
||||
md := testProvider.Meta().(*metaData)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
switch rs.Type {
|
||||
case "boundary_auth_method_password":
|
||||
id := rs.Primary.ID
|
||||
|
||||
amClient := authmethods.NewClient(md.client)
|
||||
|
||||
_, err := amClient.Read(context.Background(), id)
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||
return fmt.Errorf("didn't get a 404 when reading destroyed auth method %q: %v", id, err)
|
||||
}
|
||||
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -15,31 +15,33 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
fooAuthMethodDesc = "test auth method"
|
||||
fooAuthMethodDescUpdate = "test auth method update"
|
||||
fooBaseAuthMethodDesc = "test auth method"
|
||||
fooBaseAuthMethodDescUpdate = "test auth method update"
|
||||
)
|
||||
|
||||
// TODO(malnick) - remove these tests after deprecating use of password params in
|
||||
// favor of attributes map
|
||||
var (
|
||||
fooAuthMethod = fmt.Sprintf(`
|
||||
fooBaseAuthMethod = fmt.Sprintf(`
|
||||
resource "boundary_auth_method" "foo" {
|
||||
name = "test"
|
||||
description = "%s"
|
||||
type = "password"
|
||||
scope_id = boundary_scope.org1.id
|
||||
depends_on = [boundary_role.org1_admin]
|
||||
}`, fooAuthMethodDesc)
|
||||
}`, fooBaseAuthMethodDesc)
|
||||
|
||||
fooAuthMethodUpdate = fmt.Sprintf(`
|
||||
fooBaseAuthMethodUpdate = fmt.Sprintf(`
|
||||
resource "boundary_auth_method" "foo" {
|
||||
name = "test"
|
||||
description = "%s"
|
||||
type = "password"
|
||||
scope_id = boundary_scope.org1.id
|
||||
depends_on = [boundary_role.org1_admin]
|
||||
}`, fooAuthMethodDescUpdate)
|
||||
}`, fooBaseAuthMethodDescUpdate)
|
||||
)
|
||||
|
||||
func TestAccAuthMethod(t *testing.T) {
|
||||
func TestAccBaseAuthMethodPassword(t *testing.T) {
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
@@ -50,24 +52,25 @@ func TestAccAuthMethod(t *testing.T) {
|
||||
CheckDestroy: testAccCheckAuthMethodResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
//create
|
||||
Config: testConfig(url, fooOrg, fooAuthMethod),
|
||||
// create
|
||||
Config: testConfig(url, fooOrg, fooBaseAuthMethod),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("boundary_auth_method.foo", "description", fooAuthMethodDesc),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method.foo", "description", fooBaseAuthMethodDesc),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method.foo", "name", "test"),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method.foo", "type", "password"),
|
||||
testAccCheckAuthMethodResourceExists(provider, "boundary_auth_method.foo"),
|
||||
testAccCheckBaseAuthMethodResourceExists(provider, "boundary_auth_method.foo"),
|
||||
),
|
||||
},
|
||||
importStep("boundary_auth_method.foo"),
|
||||
|
||||
{
|
||||
// update
|
||||
Config: testConfig(url, fooOrg, fooAuthMethodUpdate),
|
||||
Config: testConfig(url, fooOrg, fooBaseAuthMethodUpdate),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("boundary_auth_method.foo", "description", fooAuthMethodDescUpdate),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method.foo", "description", fooBaseAuthMethodDescUpdate),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method.foo", "name", "test"),
|
||||
resource.TestCheckResourceAttr("boundary_auth_method.foo", "type", "password"),
|
||||
testAccCheckAuthMethodResourceExists(provider, "boundary_auth_method.foo"),
|
||||
testAccCheckBaseAuthMethodResourceExists(provider, "boundary_auth_method.foo"),
|
||||
),
|
||||
},
|
||||
importStep("boundary_auth_method.foo"),
|
||||
@@ -75,7 +78,7 @@ func TestAccAuthMethod(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckAuthMethodResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
|
||||
func testAccCheckBaseAuthMethodResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[name]
|
||||
if !ok {
|
||||
@@ -99,7 +102,7 @@ func testAccCheckAuthMethodResourceExists(testProvider *schema.Provider, name st
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckAuthMethodResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
|
||||
func testAccCheckBaseAuthMethodResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if testProvider.Meta() == nil {
|
||||
t.Fatal("got nil provider metadata")
|
||||
@@ -114,7 +117,7 @@ func testAccCheckAuthMethodResourceDestroy(t *testing.T, testProvider *schema.Pr
|
||||
amClient := authmethods.NewClient(md.client)
|
||||
|
||||
_, err := amClient.Read(context.Background(), id)
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.ResponseStatus() != http.StatusNotFound {
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||
return fmt.Errorf("didn't get a 404 when reading destroyed auth method %q: %v", id, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentiallibraries"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
credentialStoreIdKey = "credential_store_id"
|
||||
credentialLibraryVaultHttpMethodKey = "http_method"
|
||||
credentialLibraryVaultHttpRequestBodyKey = "http_request_body"
|
||||
credentialLibraryVaultPathKey = "path"
|
||||
)
|
||||
|
||||
var libraryVaultAttrs = []string{
|
||||
credentialLibraryVaultHttpMethodKey,
|
||||
credentialLibraryVaultHttpRequestBodyKey,
|
||||
credentialLibraryVaultPathKey,
|
||||
}
|
||||
|
||||
func resourceCredentialLibraryVault() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The credential library for Vault resource allows you to configure a Boundary credential library for Vault.",
|
||||
|
||||
CreateContext: resourceCredentialLibraryCreateVault,
|
||||
ReadContext: resourceCredentialLibraryReadVault,
|
||||
UpdateContext: resourceCredentialLibraryUpdateVault,
|
||||
DeleteContext: resourceCredentialLibraryDeleteVault,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: schema.ImportStatePassthroughContext,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
IDKey: {
|
||||
Description: "The ID of the Vault credential library.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
NameKey: {
|
||||
Description: "The Vault credential library name. Defaults to the resource name.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
DescriptionKey: {
|
||||
Description: "The Vault credential library description.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialStoreIdKey: {
|
||||
Description: "The ID of the credential store that this library belongs to.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
credentialLibraryVaultHttpMethodKey: {
|
||||
Description: "The HTTP method the library uses when requesting credentials from Vault. Defaults to 'GET'",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialLibraryVaultHttpRequestBodyKey: {
|
||||
Description: "The body of the HTTP request the library sends to Vault when requesting credentials. Only valid if `http_method` is set to `POST`.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialLibraryVaultPathKey: {
|
||||
Description: "The path in Vault to request credentials from.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setFromVaultCredentialLibraryResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
|
||||
if err := d.Set(NameKey, raw[NameKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(DescriptionKey, raw[DescriptionKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(credentialStoreIdKey, raw[credentialStoreIdKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if attrsVal, ok := raw["attributes"]; ok {
|
||||
attrs := attrsVal.(map[string]interface{})
|
||||
for _, v := range libraryVaultAttrs {
|
||||
if err := d.Set(v, attrs[v]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialLibraryCreateVault(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
|
||||
var opts []credentiallibraries.Option
|
||||
if v, ok := d.GetOk(NameKey); ok {
|
||||
opts = append(opts, credentiallibraries.WithName(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(DescriptionKey); ok {
|
||||
opts = append(opts, credentiallibraries.WithDescription(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialLibraryVaultHttpMethodKey); ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryHttpMethod(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialLibraryVaultHttpRequestBodyKey); ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryHttpRequestBody(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialLibraryVaultPathKey); ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryPath(v.(string)))
|
||||
}
|
||||
|
||||
var credentialStoreId string
|
||||
cid, ok := d.GetOk(credentialStoreIdKey)
|
||||
if ok {
|
||||
credentialStoreId = cid.(string)
|
||||
} else {
|
||||
return diag.Errorf("no credential store ID is set")
|
||||
}
|
||||
|
||||
client := credentiallibraries.NewClient(md.client)
|
||||
cr, err := client.Create(ctx, credentialStoreId, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error creating credential library: %v", err)
|
||||
}
|
||||
if cr == nil {
|
||||
return diag.Errorf("nil credential library after create")
|
||||
}
|
||||
|
||||
if err := setFromVaultCredentialLibraryResponseMap(d, cr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialLibraryReadVault(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentiallibraries.NewClient(md.client)
|
||||
|
||||
cr, err := client.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
return diag.Errorf("error reading credential library: %v", err)
|
||||
}
|
||||
if cr == nil {
|
||||
return diag.Errorf("credential library nil after read")
|
||||
}
|
||||
|
||||
if err := setFromVaultCredentialLibraryResponseMap(d, cr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialLibraryUpdateVault(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentiallibraries.NewClient(md.client)
|
||||
|
||||
var opts []credentiallibraries.Option
|
||||
if d.HasChange(NameKey) {
|
||||
opts = append(opts, credentiallibraries.DefaultName())
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
opts = append(opts, credentiallibraries.WithName(nameVal.(string)))
|
||||
}
|
||||
}
|
||||
if d.HasChange(DescriptionKey) {
|
||||
opts = append(opts, credentiallibraries.DefaultDescription())
|
||||
descVal, ok := d.GetOk(DescriptionKey)
|
||||
if ok {
|
||||
opts = append(opts, credentiallibraries.WithDescription(descVal.(string)))
|
||||
}
|
||||
}
|
||||
if d.HasChange(credentialLibraryVaultHttpMethodKey) {
|
||||
opts = append(opts, credentiallibraries.DefaultVaultCredentialLibraryHttpMethod())
|
||||
v, ok := d.GetOk(credentialLibraryVaultHttpMethodKey)
|
||||
if ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryHttpMethod(v.(string)))
|
||||
}
|
||||
}
|
||||
if d.HasChange(credentialLibraryVaultHttpRequestBodyKey) {
|
||||
opts = append(opts, credentiallibraries.DefaultVaultCredentialLibraryHttpRequestBody())
|
||||
v, ok := d.GetOk(credentialLibraryVaultHttpRequestBodyKey)
|
||||
if ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryHttpRequestBody(v.(string)))
|
||||
}
|
||||
}
|
||||
if d.HasChange(credentialLibraryVaultPathKey) {
|
||||
v, ok := d.GetOk(credentialLibraryVaultPathKey)
|
||||
if ok {
|
||||
opts = append(opts, credentiallibraries.WithVaultCredentialLibraryPath(v.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts) > 0 {
|
||||
opts = append(opts, credentiallibraries.WithAutomaticVersioning(true))
|
||||
aur, err := client.Update(ctx, d.Id(), 0, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating credential library: %v", err)
|
||||
}
|
||||
|
||||
if err := setFromVaultCredentialLibraryResponseMap(d, aur.GetResponse().Map); err != nil {
|
||||
return diag.Errorf("error setting credential library from response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialLibraryDeleteVault(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentiallibraries.NewClient(md.client)
|
||||
|
||||
_, err := client.Delete(ctx, d.Id())
|
||||
if err != nil {
|
||||
return diag.Errorf("error deleting credential library: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentiallibraries"
|
||||
"github.com/hashicorp/boundary/testing/controller"
|
||||
"github.com/hashicorp/boundary/testing/vault"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
||||
)
|
||||
|
||||
const (
|
||||
vaultCredResc = "boundary_credential_library_vault.example"
|
||||
vaultCredLibName = "foo"
|
||||
vaultCredLibDesc = "the foo"
|
||||
vaultCredLibPath = "/foo/bar"
|
||||
vaultCredLibMethodGet = "GET"
|
||||
vaultCredLibMethodPost = "POST"
|
||||
vaultCredLibRequestBody = "foobar"
|
||||
vaultCredLibStringUpdate = "_random"
|
||||
)
|
||||
|
||||
var vaultCredLibResource = fmt.Sprintf(`
|
||||
resource "boundary_credential_library_vault" "example" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
credential_store_id = boundary_credential_store_vault.example.id
|
||||
path = "%s"
|
||||
http_method = "%s"
|
||||
}`, vaultCredLibName,
|
||||
vaultCredLibDesc,
|
||||
vaultCredLibPath,
|
||||
vaultCredLibMethodGet)
|
||||
|
||||
var vaultCredLibResourceUpdate = fmt.Sprintf(`
|
||||
resource "boundary_credential_library_vault" "example" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
credential_store_id = boundary_credential_store_vault.example.id
|
||||
path = "%s"
|
||||
http_method = "%s"
|
||||
http_request_body = "%s"
|
||||
}`, vaultCredLibName+vaultCredLibStringUpdate,
|
||||
vaultCredLibDesc+vaultCredLibStringUpdate,
|
||||
vaultCredLibPath+vaultCredLibStringUpdate,
|
||||
vaultCredLibMethodPost,
|
||||
vaultCredLibRequestBody)
|
||||
|
||||
func TestAccCredentialLibraryVault(t *testing.T) {
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
vc := vault.NewTestVaultServer(t)
|
||||
_, token := vc.CreateToken(t)
|
||||
credStoreRes := vaultCredStoreResource(vc,
|
||||
vaultCredStoreName,
|
||||
vaultCredStoreDesc,
|
||||
vaultCredStoreNamespace,
|
||||
"www.original.com",
|
||||
token,
|
||||
true)
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckAuthMethodResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// create
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, vaultCredLibResource),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(vaultCredResc, NameKey, vaultCredLibName),
|
||||
resource.TestCheckResourceAttr(vaultCredResc, DescriptionKey, vaultCredLibDesc),
|
||||
resource.TestCheckResourceAttr(vaultCredResc, credentialLibraryVaultPathKey, vaultCredLibPath),
|
||||
resource.TestCheckResourceAttr(vaultCredResc, credentialLibraryVaultHttpMethodKey, vaultCredLibMethodGet),
|
||||
resource.TestCheckResourceAttr(vaultCredResc, credentialLibraryVaultHttpRequestBodyKey, ""),
|
||||
|
||||
testAccCheckCredentialLibraryVaultResourceExists(provider, vaultCredResc),
|
||||
),
|
||||
},
|
||||
importStep(vaultCredResc),
|
||||
|
||||
{
|
||||
// update
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, vaultCredLibResourceUpdate),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(vaultCredResc, NameKey, vaultCredLibName+vaultCredLibStringUpdate),
|
||||
resource.TestCheckResourceAttr(vaultCredResc, DescriptionKey, vaultCredLibDesc+vaultCredLibStringUpdate),
|
||||
resource.TestCheckResourceAttr(vaultCredResc, credentialLibraryVaultPathKey, vaultCredLibPath+vaultCredLibStringUpdate),
|
||||
resource.TestCheckResourceAttr(vaultCredResc, credentialLibraryVaultHttpMethodKey, vaultCredLibMethodPost),
|
||||
resource.TestCheckResourceAttr(vaultCredResc, credentialLibraryVaultHttpRequestBodyKey, vaultCredLibRequestBody),
|
||||
|
||||
testAccCheckCredentialLibraryVaultResourceExists(provider, vaultCredResc),
|
||||
),
|
||||
},
|
||||
importStep(vaultCredResc),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckCredentialLibraryVaultResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("not found: %s", name)
|
||||
}
|
||||
|
||||
id := rs.Primary.ID
|
||||
if id == "" {
|
||||
return fmt.Errorf("no ID is set")
|
||||
}
|
||||
|
||||
md := testProvider.Meta().(*metaData)
|
||||
c := credentiallibraries.NewClient(md.client)
|
||||
if _, err := c.Read(context.Background(), id); err != nil {
|
||||
return fmt.Errorf("got an error reading %q: %w", id, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckCredentialLibraryVaultResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if testProvider.Meta() == nil {
|
||||
t.Fatal("got nil provider metadata")
|
||||
}
|
||||
md := testProvider.Meta().(*metaData)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
switch rs.Type {
|
||||
case "boundary_credential_library_vault":
|
||||
id := rs.Primary.ID
|
||||
|
||||
c := credentiallibraries.NewClient(md.client)
|
||||
_, err := c.Read(context.Background(), id)
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||
return fmt.Errorf("didn't get a 404 when reading destroyed vault credential library %q: %v", id, err)
|
||||
}
|
||||
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentialstores"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
credentialStoreVaultAddressKey = "address"
|
||||
credentialStoreVaultNamespaceKey = "namespace"
|
||||
credentialStoreVaultCaCertKey = "ca_cert"
|
||||
credentialStoreVaultTlsServerNameKey = "tls_server_name"
|
||||
credentialStoreVaultTlsSkipVerifyKey = "tls_skip_verify"
|
||||
credentialStoreVaultTokenKey = "token"
|
||||
credentialStoreVaultTokenHmacKey = "token_hmac"
|
||||
credentialStoreVaultClientCertificateKey = "client_certificate"
|
||||
credentialStoreVaultClientCertificateKeyKey = "client_certificate_key"
|
||||
credentialStoreVaultClientCertificateKeyHmacKey = "client_certificate_key_hmac"
|
||||
credentialStoreType = "vault"
|
||||
)
|
||||
|
||||
var storeVaultAttrs = []string{
|
||||
credentialStoreVaultAddressKey,
|
||||
credentialStoreVaultNamespaceKey,
|
||||
credentialStoreVaultCaCertKey,
|
||||
credentialStoreVaultTlsServerNameKey,
|
||||
credentialStoreVaultTlsSkipVerifyKey,
|
||||
credentialStoreVaultClientCertificateKey,
|
||||
}
|
||||
|
||||
func resourceCredentialStoreVault() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Description: "The credential store for Vault resource allows you to configure a Boundary credential store for Vault.",
|
||||
|
||||
CreateContext: resourceCredentialStoreVaultCreate,
|
||||
ReadContext: resourceCredentialStoreVaultRead,
|
||||
UpdateContext: resourceCredentialStoreVaultUpdate,
|
||||
DeleteContext: resourceCredentialStoreVaultDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: schema.ImportStatePassthroughContext,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
IDKey: {
|
||||
Description: "The ID of the Vault credential store.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
NameKey: {
|
||||
Description: "The Vault credential store name. Defaults to the resource name.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
DescriptionKey: {
|
||||
Description: "The Vault credential store description.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
ScopeIdKey: {
|
||||
Description: "The scope for this credential store.",
|
||||
Type: schema.TypeString,
|
||||
ForceNew: true,
|
||||
Required: true,
|
||||
},
|
||||
credentialStoreVaultAddressKey: {
|
||||
Description: "The address to Vault server. This should be a complete URL such as 'https://127.0.0.1:8200'",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
credentialStoreVaultNamespaceKey: {
|
||||
Description: "The namespace within Vault to use.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialStoreVaultCaCertKey: {
|
||||
Description: "A PEM-encoded CA certificate to verify the Vault server's TLS certificate.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialStoreVaultTlsServerNameKey: {
|
||||
Description: "Name to use as the SNI host when connecting to Vault via TLS.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialStoreVaultTlsSkipVerifyKey: {
|
||||
Description: "Whether or not to skip TLS verification.",
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
},
|
||||
credentialStoreVaultTokenKey: {
|
||||
Description: "A token used for accessing Vault.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
credentialStoreVaultTokenHmacKey: {
|
||||
Description: "The Vault token hmac.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
credentialStoreVaultClientCertificateKey: {
|
||||
Description: "A PEM-encoded client certificate to use for TLS authentication to the Vault server.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
credentialStoreVaultClientCertificateKeyKey: {
|
||||
Description: "A PEM-encoded private key matching the client certificate from 'client_certificate'.",
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
credentialStoreVaultClientCertificateKeyHmacKey: {
|
||||
Description: "The Vault client certificate key hmac.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setFromVaultCredentialStoreResponseMap(d *schema.ResourceData, raw map[string]interface{}, fromRead bool) error {
|
||||
if err := d.Set(NameKey, raw[NameKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(DescriptionKey, raw[DescriptionKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(ScopeIdKey, raw[ScopeIdKey]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if attrsVal, ok := raw["attributes"]; ok {
|
||||
attrs := attrsVal.(map[string]interface{})
|
||||
for _, v := range storeVaultAttrs {
|
||||
if err := d.Set(v, attrs[v]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
stateTokenHmac := d.Get(credentialStoreVaultTokenHmacKey)
|
||||
boundaryTokenHmac := attrs[credentialStoreVaultTokenHmacKey].(string)
|
||||
if stateTokenHmac.(string) != boundaryTokenHmac && fromRead {
|
||||
// TokenHmac has changed in Boundary, therefore the token has changed.
|
||||
// Update token value to force tf to attempt update.
|
||||
if err := d.Set(credentialStoreVaultTokenKey, "(changed in Boundary)"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := d.Set(credentialStoreVaultTokenHmacKey, boundaryTokenHmac); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stateClientKeyHmac := d.Get(credentialStoreVaultClientCertificateKeyHmacKey)
|
||||
var boundaryClientKeyHmac string
|
||||
if v, ok := attrs[credentialStoreVaultClientCertificateKeyHmacKey]; ok {
|
||||
boundaryClientKeyHmac = v.(string)
|
||||
}
|
||||
if stateClientKeyHmac.(string) != boundaryClientKeyHmac && fromRead {
|
||||
// ClientKeyHmac has changed in Boundary, therefore the ClientKey has changed.
|
||||
// Update ClientKey value to force tf to attempt update.
|
||||
if err := d.Set(credentialStoreVaultClientCertificateKeyKey, "(changed in Boundary)"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := d.Set(credentialStoreVaultClientCertificateKeyHmacKey, boundaryClientKeyHmac); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialStoreVaultCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
|
||||
var opts []credentialstores.Option
|
||||
if v, ok := d.GetOk(NameKey); ok {
|
||||
opts = append(opts, credentialstores.WithName(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(DescriptionKey); ok {
|
||||
opts = append(opts, credentialstores.WithDescription(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialStoreVaultAddressKey); ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreAddress(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialStoreVaultNamespaceKey); ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreNamespace(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialStoreVaultCaCertKey); ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreCaCert(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialStoreVaultTlsServerNameKey); ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreTlsServerName(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialStoreVaultTlsSkipVerifyKey); ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreTlsSkipVerify(v.(bool)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialStoreVaultClientCertificateKey); ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreClientCertificate(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialStoreVaultClientCertificateKeyKey); ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreClientCertificateKey(v.(string)))
|
||||
}
|
||||
if v, ok := d.GetOk(credentialStoreVaultTokenKey); ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreToken(v.(string)))
|
||||
}
|
||||
|
||||
var scope string
|
||||
gotScope, ok := d.GetOk(ScopeIdKey)
|
||||
if ok {
|
||||
scope = gotScope.(string)
|
||||
} else {
|
||||
return diag.Errorf("no scope is set")
|
||||
}
|
||||
|
||||
client := credentialstores.NewClient(md.client)
|
||||
cr, err := client.Create(ctx, credentialStoreType, scope, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error creating credential store: %v", err)
|
||||
}
|
||||
if cr == nil {
|
||||
return diag.Errorf("nil credential store after create")
|
||||
}
|
||||
|
||||
if err := setFromVaultCredentialStoreResponseMap(d, cr.GetResponse().Map, false); err != nil {
|
||||
return diag.Errorf("error generating credential store from response map: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialStoreVaultRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentialstores.NewClient(md.client)
|
||||
|
||||
cr, err := client.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
return diag.Errorf("error reading credential store: %v", err)
|
||||
}
|
||||
if cr == nil {
|
||||
return diag.Errorf("credential store nil after read")
|
||||
}
|
||||
|
||||
if err := setFromVaultCredentialStoreResponseMap(d, cr.GetResponse().Map, true); err != nil {
|
||||
return diag.Errorf("error generating credential store from response map: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialStoreVaultUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentialstores.NewClient(md.client)
|
||||
|
||||
var opts []credentialstores.Option
|
||||
if d.HasChange(NameKey) {
|
||||
opts = append(opts, credentialstores.DefaultName())
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
opts = append(opts, credentialstores.WithName(nameVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(DescriptionKey) {
|
||||
opts = append(opts, credentialstores.DefaultDescription())
|
||||
descVal, ok := d.GetOk(DescriptionKey)
|
||||
if ok {
|
||||
opts = append(opts, credentialstores.WithDescription(descVal.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialStoreVaultAddressKey) {
|
||||
v, ok := d.GetOk(credentialStoreVaultAddressKey)
|
||||
if ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreAddress(v.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialStoreVaultNamespaceKey) {
|
||||
opts = append(opts, credentialstores.DefaultVaultCredentialStoreNamespace())
|
||||
v, ok := d.GetOk(credentialStoreVaultNamespaceKey)
|
||||
if ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreNamespace(v.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialStoreVaultCaCertKey) {
|
||||
opts = append(opts, credentialstores.DefaultVaultCredentialStoreCaCert())
|
||||
v, ok := d.GetOk(credentialStoreVaultCaCertKey)
|
||||
if ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreCaCert(v.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialStoreVaultTlsServerNameKey) {
|
||||
opts = append(opts, credentialstores.DefaultVaultCredentialStoreTlsServerName())
|
||||
v, ok := d.GetOk(credentialStoreVaultTlsServerNameKey)
|
||||
if ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreTlsServerName(v.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialStoreVaultTlsSkipVerifyKey) {
|
||||
opts = append(opts, credentialstores.DefaultVaultCredentialStoreTlsSkipVerify())
|
||||
v, ok := d.GetOk(credentialStoreVaultTlsSkipVerifyKey)
|
||||
if ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreTlsSkipVerify(v.(bool)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialStoreVaultTokenKey) {
|
||||
v, ok := d.GetOk(credentialStoreVaultTokenKey)
|
||||
if ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreToken(v.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialStoreVaultClientCertificateKey) {
|
||||
opts = append(opts, credentialstores.DefaultVaultCredentialStoreClientCertificate())
|
||||
v, ok := d.GetOk(credentialStoreVaultClientCertificateKey)
|
||||
if ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreClientCertificate(v.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(credentialStoreVaultClientCertificateKeyKey) {
|
||||
opts = append(opts, credentialstores.DefaultVaultCredentialStoreClientCertificateKey())
|
||||
v, ok := d.GetOk(credentialStoreVaultClientCertificateKeyKey)
|
||||
if ok {
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreClientCertificateKey(v.(string)))
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts) > 0 {
|
||||
opts = append(opts, credentialstores.WithAutomaticVersioning(true))
|
||||
crUpdate, err := client.Update(ctx, d.Id(), 0, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating credential store: %v", err)
|
||||
}
|
||||
if crUpdate == nil {
|
||||
return diag.Errorf("credential store nil after update")
|
||||
}
|
||||
|
||||
if err = setFromVaultCredentialStoreResponseMap(d, crUpdate.GetResponse().Map, false); err != nil {
|
||||
return diag.Errorf("error generating credential store from response map: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceCredentialStoreVaultDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
md := meta.(*metaData)
|
||||
client := credentialstores.NewClient(md.client)
|
||||
|
||||
_, err := client.Delete(ctx, d.Id())
|
||||
if err != nil {
|
||||
return diag.Errorf("error deleting credential store: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/credentialstores"
|
||||
"github.com/hashicorp/boundary/testing/controller"
|
||||
"github.com/hashicorp/boundary/testing/vault"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
||||
"golang.org/x/crypto/blake2b"
|
||||
)
|
||||
|
||||
const (
|
||||
vaultCredStoreResc = "boundary_credential_store_vault.example"
|
||||
vaultCredStoreName = "foo"
|
||||
vaultCredStoreDesc = "the foo"
|
||||
vaultCredStoreNamespace = "default"
|
||||
vaultCredStoreUpdate = "_random"
|
||||
)
|
||||
|
||||
func vaultCredStoreResource(vc *vault.TestVaultServer, name, description, namespace, tlsServer, token string, skipVerify bool) string {
|
||||
caCert := fmt.Sprintf("\"%s\"", strings.Replace(string(vc.CaCert), "\n", `\n`, -1))
|
||||
clientCert := fmt.Sprintf("\"%s\"", strings.Replace(string(vc.ClientCert), "\n", `\n`, -1))
|
||||
clientKey := fmt.Sprintf("\"%s\"", strings.Replace(string(vc.ClientKey), "\n", `\n`, -1))
|
||||
|
||||
return fmt.Sprintf(`
|
||||
resource "boundary_credential_store_vault" "example" {
|
||||
name = "%s"
|
||||
description = "%s"
|
||||
scope_id = boundary_scope.proj1.id
|
||||
address = "%s"
|
||||
namespace = "%s"
|
||||
ca_cert = %s
|
||||
tls_server_name = "%s"
|
||||
tls_skip_verify = "%v"
|
||||
token = "%s"
|
||||
client_certificate = %s
|
||||
client_certificate_key = %s
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
}`, name,
|
||||
description,
|
||||
vc.Addr,
|
||||
namespace,
|
||||
caCert,
|
||||
tlsServer,
|
||||
skipVerify,
|
||||
token,
|
||||
clientCert,
|
||||
clientKey)
|
||||
}
|
||||
|
||||
func tokenHmac(token, accessor string) string {
|
||||
key := blake2b.Sum256([]byte(accessor))
|
||||
mac := hmac.New(sha256.New, key[:])
|
||||
_, _ = mac.Write([]byte(token))
|
||||
hmac := mac.Sum(nil)
|
||||
return base64.RawURLEncoding.EncodeToString(hmac)
|
||||
}
|
||||
|
||||
func TestAccCredentialStoreVault(t *testing.T) {
|
||||
tc := controller.NewTestController(t, tcConfig...)
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
vc := vault.NewTestVaultServer(t)
|
||||
secret, token := vc.CreateToken(t)
|
||||
tHmac := tokenHmac(token, secret.Auth.Accessor)
|
||||
res := vaultCredStoreResource(vc,
|
||||
vaultCredStoreName,
|
||||
vaultCredStoreDesc,
|
||||
vaultCredStoreNamespace,
|
||||
"www.original.com",
|
||||
token,
|
||||
true)
|
||||
|
||||
vcUpdate := vault.NewTestVaultServer(t, vault.WithTestVaultTLS(vault.TestClientTLS))
|
||||
secret, tokenUpdate := vcUpdate.CreateToken(t)
|
||||
tHmacUpdate := tokenHmac(tokenUpdate, secret.Auth.Accessor)
|
||||
resUpdate := vaultCredStoreResource(vcUpdate,
|
||||
vaultCredStoreName+vaultCredStoreUpdate,
|
||||
vaultCredStoreDesc+vaultCredStoreUpdate,
|
||||
vaultCredStoreNamespace+vaultCredStoreUpdate,
|
||||
"www.updated.com",
|
||||
tokenUpdate,
|
||||
false)
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
CheckDestroy: testAccCheckCredentialStoreVaultResourceDestroy(t, provider),
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// create
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, res),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, NameKey, vaultCredStoreName),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, DescriptionKey, vaultCredStoreDesc),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultAddressKey, vc.Addr),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultNamespaceKey, vaultCredStoreNamespace),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultCaCertKey, ""),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTlsServerNameKey, "www.original.com"),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTlsSkipVerifyKey, "true"),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTokenKey, token),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTokenHmacKey, tHmac),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultClientCertificateKey, ""),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultClientCertificateKeyKey, ""),
|
||||
|
||||
testAccCheckCredentialStoreVaultResourceExists(provider, vaultCredStoreResc),
|
||||
),
|
||||
},
|
||||
importStep(vaultCredStoreResc, credentialStoreVaultTokenKey, credentialStoreVaultClientCertificateKeyKey),
|
||||
{
|
||||
// update
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, NameKey, vaultCredStoreName+vaultCredStoreUpdate),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, DescriptionKey, vaultCredStoreDesc+vaultCredStoreUpdate),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultAddressKey, vcUpdate.Addr),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultNamespaceKey, vaultCredStoreNamespace+vaultCredStoreUpdate),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultCaCertKey, string(vcUpdate.CaCert)),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTlsServerNameKey, "www.updated.com"),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTlsSkipVerifyKey, "false"),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTokenKey, tokenUpdate),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultTokenHmacKey, tHmacUpdate),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultClientCertificateKey, string(vcUpdate.ClientCert)),
|
||||
resource.TestCheckResourceAttr(vaultCredStoreResc, credentialStoreVaultClientCertificateKeyKey, string(vcUpdate.ClientKey)),
|
||||
|
||||
testAccCheckCredentialStoreVaultResourceExists(provider, vaultCredStoreResc),
|
||||
),
|
||||
},
|
||||
importStep(vaultCredStoreResc, credentialStoreVaultTokenKey, credentialStoreVaultClientCertificateKeyKey),
|
||||
{
|
||||
// Run a plan only update and verify no changes
|
||||
PlanOnly: true,
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
},
|
||||
importStep(vaultCredStoreResc, credentialStoreVaultTokenKey, credentialStoreVaultClientCertificateKeyKey),
|
||||
{
|
||||
// update again but apply a preConfig to externally update resource
|
||||
// TODO: Boundary currently causes an error on moving back to a previously
|
||||
// used token, for now verify that a plan only step had changes
|
||||
PreConfig: func() { externalUpdate(t, provider) },
|
||||
PlanOnly: true,
|
||||
ExpectNonEmptyPlan: true,
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, resUpdate),
|
||||
},
|
||||
importStep(vaultCredStoreResc, credentialStoreVaultTokenKey, credentialStoreVaultClientCertificateKeyKey),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
var storeId string
|
||||
|
||||
// externalUpdate uses the global storeId, therefore this function cannot be called until
|
||||
// a previous test step that calls testAccCheckCredentialStoreVaultResourceExists has completed.
|
||||
func externalUpdate(t *testing.T, testProvider *schema.Provider) {
|
||||
if storeId == "" {
|
||||
t.Fatal("storeId must be set before testing an external update")
|
||||
}
|
||||
vs := vault.NewTestVaultServer(t, vault.WithTestVaultTLS(vault.TestClientTLS))
|
||||
_, token := vs.CreateToken(t)
|
||||
|
||||
md := testProvider.Meta().(*metaData)
|
||||
c := credentialstores.NewClient(md.client)
|
||||
cr, err := c.Read(context.Background(), storeId)
|
||||
if err != nil {
|
||||
t.Fatal(fmt.Errorf("got an error reading %q: %w", storeId, err))
|
||||
}
|
||||
|
||||
// update Vault server to existing store
|
||||
var opts []credentialstores.Option
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreToken(token))
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreAddress(vs.Addr))
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreCaCert(string(vs.CaCert)))
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreClientCertificate(string(vs.ClientCert)))
|
||||
opts = append(opts, credentialstores.WithVaultCredentialStoreClientCertificateKey(string(vs.ClientKey)))
|
||||
|
||||
_, err = c.Update(context.Background(), cr.Item.Id, cr.Item.Version, opts...)
|
||||
if err != nil {
|
||||
t.Fatal(fmt.Errorf("got an error updating %q: %w", cr.Item.Id, err))
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckCredentialStoreVaultResourceExists(testProvider *schema.Provider, name string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("not found: %s", name)
|
||||
}
|
||||
|
||||
id := rs.Primary.ID
|
||||
if id == "" {
|
||||
return fmt.Errorf("no ID is set")
|
||||
}
|
||||
storeId = id
|
||||
|
||||
md := testProvider.Meta().(*metaData)
|
||||
c := credentialstores.NewClient(md.client)
|
||||
if _, err := c.Read(context.Background(), id); err != nil {
|
||||
return fmt.Errorf("got an error reading %q: %w", id, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckCredentialStoreVaultResourceDestroy(t *testing.T, testProvider *schema.Provider) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
if testProvider.Meta() == nil {
|
||||
t.Fatal("got nil provider metadata")
|
||||
}
|
||||
md := testProvider.Meta().(*metaData)
|
||||
|
||||
for _, rs := range s.RootModule().Resources {
|
||||
switch rs.Type {
|
||||
case "boundary_credential_store_vault":
|
||||
id := rs.Primary.ID
|
||||
|
||||
c := credentialstores.NewClient(md.client)
|
||||
_, err := c.Read(context.Background(), id)
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||
return fmt.Errorf("didn't get a 404 when reading destroyed Vault credential store %q: %v", id, err)
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -58,12 +58,21 @@ func resourceGroup() *schema.Resource {
|
||||
}
|
||||
}
|
||||
|
||||
func setFromGroupResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
|
||||
d.Set(NameKey, raw["name"])
|
||||
d.Set(DescriptionKey, raw["description"])
|
||||
d.Set(ScopeIdKey, raw["scope_id"])
|
||||
d.Set(groupMemberIdsKey, raw["member_ids"])
|
||||
func setFromGroupResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
|
||||
if err := d.Set(NameKey, raw["name"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(ScopeIdKey, raw["scope_id"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(groupMemberIdsKey, raw["member_ids"]); err != nil {
|
||||
return err
|
||||
}
|
||||
d.SetId(raw["id"].(string))
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
@@ -99,7 +108,7 @@ func resourceGroupCreate(ctx context.Context, d *schema.ResourceData, meta inter
|
||||
if gcr == nil {
|
||||
return diag.Errorf("group nil after create")
|
||||
}
|
||||
raw := gcr.GetResponseMap()
|
||||
raw := gcr.GetResponse().Map
|
||||
|
||||
if val, ok := d.GetOk(groupMemberIdsKey); ok {
|
||||
list := val.(*schema.Set).List()
|
||||
@@ -114,10 +123,12 @@ func resourceGroupCreate(ctx context.Context, d *schema.ResourceData, meta inter
|
||||
if gcsmr == nil {
|
||||
return diag.Errorf("group nil after setting members")
|
||||
}
|
||||
raw = gcsmr.GetResponseMap()
|
||||
raw = gcsmr.GetResponse().Map
|
||||
}
|
||||
|
||||
setFromGroupResponseMap(d, raw)
|
||||
if err := setFromGroupResponseMap(d, raw); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -128,7 +139,7 @@ func resourceGroupRead(ctx context.Context, d *schema.ResourceData, meta interfa
|
||||
|
||||
g, err := grps.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr.ResponseStatus() == http.StatusNotFound {
|
||||
if apiErr := api.AsServerError(err); apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
@@ -138,7 +149,9 @@ func resourceGroupRead(ctx context.Context, d *schema.ResourceData, meta interfa
|
||||
return diag.Errorf("group nil after read")
|
||||
}
|
||||
|
||||
setFromGroupResponseMap(d, g.GetResponseMap())
|
||||
if err := setFromGroupResponseMap(d, g.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -180,10 +193,14 @@ func resourceGroupUpdate(ctx context.Context, d *schema.ResourceData, meta inter
|
||||
}
|
||||
|
||||
if d.HasChange(NameKey) {
|
||||
d.Set(NameKey, name)
|
||||
if err := d.Set(NameKey, name); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
if d.HasChange(DescriptionKey) {
|
||||
d.Set(DescriptionKey, desc)
|
||||
if err := d.Set(DescriptionKey, desc); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
// The above call may not actually happen, so we use d.Id() and automatic
|
||||
@@ -201,7 +218,10 @@ func resourceGroupUpdate(ctx context.Context, d *schema.ResourceData, meta inter
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating members in group: %v", err)
|
||||
}
|
||||
d.Set(groupMemberIdsKey, memberIds)
|
||||
if err := d.Set(groupMemberIdsKey, memberIds); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -365,7 +365,7 @@ func testAccCheckGroupResourceDestroy(t *testing.T, testProvider *schema.Provide
|
||||
id := rs.Primary.ID
|
||||
|
||||
_, err := grps.Read(context.Background(), id)
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.ResponseStatus() != http.StatusNotFound {
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||
return fmt.Errorf("didn't get a 404 when reading destroyed resource %q: %v", id, apiErr)
|
||||
}
|
||||
|
||||
|
||||
@@ -63,21 +63,32 @@ func resourceHost() *schema.Resource {
|
||||
}
|
||||
}
|
||||
|
||||
func setFromHostResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
|
||||
d.Set(NameKey, raw["name"])
|
||||
d.Set(DescriptionKey, raw["description"])
|
||||
d.Set(HostCatalogIdKey, raw["host_catalog_id"])
|
||||
d.Set(TypeKey, raw["type"])
|
||||
func setFromHostResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
|
||||
if err := d.Set(NameKey, raw["name"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(HostCatalogIdKey, raw["host_catalog_id"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(TypeKey, raw["type"]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch raw["type"].(string) {
|
||||
case hostTypeStatic:
|
||||
if attrsVal, ok := raw["attributes"]; ok {
|
||||
attrs := attrsVal.(map[string]interface{})
|
||||
d.Set(hostAddressKey, attrs["address"])
|
||||
if err := d.Set(hostAddressKey, attrs["address"]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceHostCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
@@ -140,7 +151,9 @@ func resourceHostCreate(ctx context.Context, d *schema.ResourceData, meta interf
|
||||
return diag.Errorf("host nil after create")
|
||||
}
|
||||
|
||||
setFromHostResponseMap(d, hcr.GetResponseMap())
|
||||
if err := setFromHostResponseMap(d, hcr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -151,7 +164,7 @@ func resourceHostRead(ctx context.Context, d *schema.ResourceData, meta interfac
|
||||
|
||||
hrr, err := hClient.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
@@ -161,7 +174,9 @@ func resourceHostRead(ctx context.Context, d *schema.ResourceData, meta interfac
|
||||
return diag.Errorf("host nil after read")
|
||||
}
|
||||
|
||||
setFromHostResponseMap(d, hrr.GetResponseMap())
|
||||
if err := setFromHostResponseMap(d, hrr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -219,13 +234,19 @@ func resourceHostUpdate(ctx context.Context, d *schema.ResourceData, meta interf
|
||||
}
|
||||
|
||||
if d.HasChange(NameKey) {
|
||||
d.Set(NameKey, name)
|
||||
if err := d.Set(NameKey, name); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
if d.HasChange(DescriptionKey) {
|
||||
d.Set(DescriptionKey, desc)
|
||||
if err := d.Set(DescriptionKey, desc); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
if d.HasChange(hostAddressKey) {
|
||||
d.Set(hostAddressKey, *address)
|
||||
if err := d.Set(hostAddressKey, *address); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -51,7 +51,7 @@ func resourceHostCatalog() *schema.Resource {
|
||||
ForceNew: true,
|
||||
},
|
||||
TypeKey: {
|
||||
Description: "The host catalog type. Only `Static` (yes, title case) is supported.",
|
||||
Description: "The host catalog type. Only `static` is supported.",
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
@@ -60,12 +60,21 @@ func resourceHostCatalog() *schema.Resource {
|
||||
}
|
||||
}
|
||||
|
||||
func setFromHostCatalogResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
|
||||
d.Set(NameKey, raw["name"])
|
||||
d.Set(DescriptionKey, raw["description"])
|
||||
d.Set(ScopeIdKey, raw["scope_id"])
|
||||
d.Set(TypeKey, raw["type"])
|
||||
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 {
|
||||
@@ -114,7 +123,9 @@ func resourceHostCatalogCreate(ctx context.Context, d *schema.ResourceData, meta
|
||||
return diag.Errorf("nil host catalog after create")
|
||||
}
|
||||
|
||||
setFromHostCatalogResponseMap(d, hccr.GetResponseMap())
|
||||
if err := setFromHostCatalogResponseMap(d, hccr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -125,7 +136,7 @@ func resourceHostCatalogRead(ctx context.Context, d *schema.ResourceData, meta i
|
||||
|
||||
hcrr, err := hcClient.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
@@ -135,7 +146,9 @@ func resourceHostCatalogRead(ctx context.Context, d *schema.ResourceData, meta i
|
||||
return diag.Errorf("host catalog nil after read")
|
||||
}
|
||||
|
||||
setFromHostCatalogResponseMap(d, hcrr.GetResponseMap())
|
||||
if err := setFromHostCatalogResponseMap(d, hcrr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -177,10 +190,14 @@ func resourceHostCatalogUpdate(ctx context.Context, d *schema.ResourceData, meta
|
||||
}
|
||||
|
||||
if d.HasChange(NameKey) {
|
||||
d.Set(NameKey, name)
|
||||
if err := d.Set(NameKey, name); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
if d.HasChange(DescriptionKey) {
|
||||
d.Set(DescriptionKey, desc)
|
||||
if err := d.Set(DescriptionKey, desc); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -108,7 +108,7 @@ func testAccCheckHostCatalogResourceDestroy(t *testing.T, testProvider *schema.P
|
||||
hcClient := hostcatalogs.NewClient(md.client)
|
||||
|
||||
_, err := hcClient.Read(context.Background(), id)
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.ResponseStatus() != http.StatusNotFound {
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||
return fmt.Errorf("didn't get a 404 when reading destroyed host catalog %q: %v", id, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -65,13 +65,24 @@ func resourceHostset() *schema.Resource {
|
||||
}
|
||||
}
|
||||
|
||||
func setFromHostSetResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
|
||||
d.Set(NameKey, raw["name"])
|
||||
d.Set(DescriptionKey, raw["description"])
|
||||
d.Set(HostCatalogIdKey, raw["host_catalog_id"])
|
||||
d.Set(TypeKey, raw["type"])
|
||||
d.Set(hostsetHostIdsKey, raw["host_ids"])
|
||||
func setFromHostSetResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
|
||||
if err := d.Set(NameKey, raw["name"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(HostCatalogIdKey, raw["host_catalog_id"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(TypeKey, raw["type"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(hostsetHostIdsKey, raw["host_ids"]); err != nil {
|
||||
return err
|
||||
}
|
||||
d.SetId(raw["id"].(string))
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceHostsetCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
@@ -130,7 +141,7 @@ func resourceHostsetCreate(ctx context.Context, d *schema.ResourceData, meta int
|
||||
if hscr == nil {
|
||||
return diag.Errorf("nil host set after create")
|
||||
}
|
||||
raw := hscr.GetResponseMap()
|
||||
raw := hscr.GetResponse().Map
|
||||
|
||||
if hostIds != nil {
|
||||
hsshr, err := hsClient.SetHosts(ctx, hscr.Item.Id, hscr.Item.Version, hostIds)
|
||||
@@ -140,10 +151,12 @@ func resourceHostsetCreate(ctx context.Context, d *schema.ResourceData, meta int
|
||||
if hsshr == nil {
|
||||
return diag.Errorf("nil host set after setting hosts")
|
||||
}
|
||||
raw = hsshr.GetResponseMap()
|
||||
raw = hsshr.GetResponse().Map
|
||||
}
|
||||
|
||||
setFromHostSetResponseMap(d, raw)
|
||||
if err := setFromHostSetResponseMap(d, raw); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -154,7 +167,7 @@ func resourceHostsetRead(ctx context.Context, d *schema.ResourceData, meta inter
|
||||
|
||||
hsrr, err := hsClient.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
@@ -164,7 +177,9 @@ func resourceHostsetRead(ctx context.Context, d *schema.ResourceData, meta inter
|
||||
return diag.Errorf("host set nil after read")
|
||||
}
|
||||
|
||||
setFromHostSetResponseMap(d, hsrr.GetResponseMap())
|
||||
if err := setFromHostSetResponseMap(d, hsrr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -206,10 +221,14 @@ func resourceHostsetUpdate(ctx context.Context, d *schema.ResourceData, meta int
|
||||
}
|
||||
|
||||
if d.HasChange(NameKey) {
|
||||
d.Set(NameKey, name)
|
||||
if err := d.Set(NameKey, name); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
if d.HasChange(DescriptionKey) {
|
||||
d.Set(DescriptionKey, desc)
|
||||
if err := d.Set(DescriptionKey, desc); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
// The above call may not actually happen, so we use d.Id() and automatic
|
||||
@@ -226,7 +245,9 @@ func resourceHostsetUpdate(ctx context.Context, d *schema.ResourceData, meta int
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating hosts in host set: %v", err)
|
||||
}
|
||||
d.Set(hostsetHostIdsKey, hostIds)
|
||||
if err := d.Set(hostsetHostIdsKey, hostIds); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -201,7 +201,7 @@ func testAccCheckHostsetResourceDestroy(t *testing.T, testProvider *schema.Provi
|
||||
hostsetsClient := hostsets.NewClient(md.client)
|
||||
|
||||
_, err := hostsetsClient.Read(context.Background(), id)
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.ResponseStatus() != http.StatusNotFound {
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||
return fmt.Errorf("didn't get a 404 when reading destroyed host set %q: %v", id, apiErr)
|
||||
}
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ func testAccCheckHostResourceDestroy(t *testing.T, testProvider *schema.Provider
|
||||
hostsClient := hosts.NewClient(md.client)
|
||||
|
||||
_, err := hostsClient.Read(context.Background(), id)
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.ResponseStatus() != http.StatusNotFound {
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||
return fmt.Errorf("didn't get a 404 when reading destroyed host %q: %v", id, apiErr)
|
||||
}
|
||||
|
||||
|
||||
@@ -77,14 +77,27 @@ func resourceRole() *schema.Resource {
|
||||
}
|
||||
}
|
||||
|
||||
func setFromRoleResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
|
||||
d.Set(NameKey, raw["name"])
|
||||
d.Set(DescriptionKey, raw["description"])
|
||||
d.Set(ScopeIdKey, raw["scope_id"])
|
||||
d.Set(rolePrincipalIdsKey, raw["principal_ids"])
|
||||
d.Set(roleGrantStringsKey, raw["grant_strings"])
|
||||
d.Set(roleGrantScopeIdKey, raw["grant_scope_id"])
|
||||
func setFromRoleResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
|
||||
if err := d.Set(NameKey, raw["name"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(ScopeIdKey, raw["scope_id"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(rolePrincipalIdsKey, raw["principal_ids"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(roleGrantStringsKey, raw["grant_strings"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(roleGrantScopeIdKey, raw["grant_scope_id"]); err != nil {
|
||||
return err
|
||||
}
|
||||
d.SetId(raw["id"].(string))
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceRoleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
@@ -149,7 +162,7 @@ func resourceRoleCreate(ctx context.Context, d *schema.ResourceData, meta interf
|
||||
if tcr == nil {
|
||||
return diag.Errorf("nil role after create")
|
||||
}
|
||||
raw := tcr.GetResponseMap()
|
||||
raw := tcr.GetResponse().Map
|
||||
|
||||
if principalIds != nil {
|
||||
tspr, err := rc.SetPrincipals(ctx, tcr.Item.Id, 0, principalIds, roles.WithAutomaticVersioning(true))
|
||||
@@ -159,7 +172,7 @@ func resourceRoleCreate(ctx context.Context, d *schema.ResourceData, meta interf
|
||||
if tspr == nil {
|
||||
return diag.Errorf("nil role after setting principal IDs")
|
||||
}
|
||||
raw = tspr.GetResponseMap()
|
||||
raw = tspr.GetResponse().Map
|
||||
}
|
||||
|
||||
if grantStrings != nil {
|
||||
@@ -170,10 +183,12 @@ func resourceRoleCreate(ctx context.Context, d *schema.ResourceData, meta interf
|
||||
if tsgr == nil {
|
||||
return diag.Errorf("nil role after setting grant strings")
|
||||
}
|
||||
raw = tsgr.GetResponseMap()
|
||||
raw = tsgr.GetResponse().Map
|
||||
}
|
||||
|
||||
setFromRoleResponseMap(d, raw)
|
||||
if err := setFromRoleResponseMap(d, raw); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -184,7 +199,7 @@ func resourceRoleRead(ctx context.Context, d *schema.ResourceData, meta interfac
|
||||
|
||||
trr, err := rc.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
@@ -194,7 +209,9 @@ func resourceRoleRead(ctx context.Context, d *schema.ResourceData, meta interfac
|
||||
return diag.Errorf("role nil after read")
|
||||
}
|
||||
|
||||
setFromRoleResponseMap(d, trr.GetResponseMap())
|
||||
if err := setFromRoleResponseMap(d, trr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -247,13 +264,19 @@ func resourceRoleUpdate(ctx context.Context, d *schema.ResourceData, meta interf
|
||||
}
|
||||
|
||||
if d.HasChange(NameKey) {
|
||||
d.Set(NameKey, name)
|
||||
if err := d.Set(NameKey, name); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
if d.HasChange(DescriptionKey) {
|
||||
d.Set(DescriptionKey, desc)
|
||||
if err := d.Set(DescriptionKey, desc); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
if d.HasChange(roleGrantScopeIdKey) {
|
||||
d.Set(roleGrantScopeIdKey, grantScopeId)
|
||||
if err := d.Set(roleGrantScopeIdKey, grantScopeId); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(roleGrantStringsKey) {
|
||||
@@ -268,7 +291,9 @@ func resourceRoleUpdate(ctx context.Context, d *schema.ResourceData, meta interf
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating grant strings on role: %v", err)
|
||||
}
|
||||
d.Set(roleGrantStringsKey, grantStrings)
|
||||
if err := d.Set(roleGrantStringsKey, grantStrings); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(rolePrincipalIdsKey) {
|
||||
@@ -283,7 +308,9 @@ func resourceRoleUpdate(ctx context.Context, d *schema.ResourceData, meta interf
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating grant strings on role: %v", err)
|
||||
}
|
||||
d.Set(rolePrincipalIdsKey, principalIds)
|
||||
if err := d.Set(rolePrincipalIdsKey, principalIds); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -345,7 +345,7 @@ func testAccCheckRoleDestroyed(testProvider *schema.Provider, name string) resou
|
||||
rolesClient := roles.NewClient(md.client)
|
||||
|
||||
_, err := rolesClient.Read(context.Background(), id)
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.ResponseStatus() != http.StatusNotFound {
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||
errs = append(errs, fmt.Sprintf("Role not destroyed %q: %v", id, apiErr))
|
||||
}
|
||||
|
||||
@@ -546,7 +546,7 @@ func testAccCheckRoleResourceDestroy(t *testing.T, testProvider *schema.Provider
|
||||
rolesClient := roles.NewClient(md.client)
|
||||
|
||||
_, err := rolesClient.Read(context.Background(), id)
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.ResponseStatus() != http.StatusNotFound {
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||
return fmt.Errorf("didn't get a 404 when reading destroyed role %q: %v", id, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -69,15 +69,25 @@ func resourceScope() *schema.Resource {
|
||||
}
|
||||
}
|
||||
|
||||
func setFromScopeResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
|
||||
d.Set(NameKey, raw["name"])
|
||||
d.Set(DescriptionKey, raw["description"])
|
||||
if d.Id() == "global" {
|
||||
d.Set(ScopeIdKey, "global")
|
||||
} else {
|
||||
d.Set(ScopeIdKey, raw["scope_id"])
|
||||
func setFromScopeResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
|
||||
if err := d.Set(NameKey, raw["name"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if d.Id() == "global" {
|
||||
if err := d.Set(ScopeIdKey, "global"); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := d.Set(ScopeIdKey, raw["scope_id"]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceScopeCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
@@ -136,7 +146,9 @@ func resourceScopeCreate(ctx context.Context, d *schema.ResourceData, meta inter
|
||||
return diag.Errorf("scope nil after create")
|
||||
}
|
||||
|
||||
setFromScopeResponseMap(d, scr.GetResponseMap())
|
||||
if err := setFromScopeResponseMap(d, scr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -147,7 +159,7 @@ func resourceScopeRead(ctx context.Context, d *schema.ResourceData, meta interfa
|
||||
|
||||
srr, err := scp.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
@@ -157,7 +169,9 @@ func resourceScopeRead(ctx context.Context, d *schema.ResourceData, meta interfa
|
||||
return diag.Errorf("scope nil after read")
|
||||
}
|
||||
|
||||
setFromScopeResponseMap(d, srr.GetResponseMap())
|
||||
if err := setFromScopeResponseMap(d, srr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -199,10 +213,14 @@ func resourceScopeUpdate(ctx context.Context, d *schema.ResourceData, meta inter
|
||||
}
|
||||
|
||||
if d.HasChange(NameKey) {
|
||||
d.Set(NameKey, name)
|
||||
if err := d.Set(NameKey, name); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
if d.HasChange(DescriptionKey) {
|
||||
d.Set(DescriptionKey, desc)
|
||||
if err := d.Set(DescriptionKey, desc); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -166,7 +166,7 @@ func testAccCheckScopeResourceDestroy(t *testing.T, testProvider *schema.Provide
|
||||
continue
|
||||
}
|
||||
_, err := scp.Read(context.Background(), id)
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.ResponseStatus() != http.StatusNotFound {
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||
return fmt.Errorf("didn't get a 404 when reading destroyed resource %q: %w", id, err)
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -12,11 +12,12 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
targetHostSetIdsKey = "host_set_ids"
|
||||
targetDefaultPortKey = "default_port"
|
||||
targetSessionMaxSecondsKey = "session_max_seconds"
|
||||
targetSessionConnectionLimitKey = "session_connection_limit"
|
||||
targetWorkerFilterKey = "worker_filter"
|
||||
targetHostSetIdsKey = "host_set_ids"
|
||||
targetApplicationCredentialLibraryIdsKey = "application_credential_library_ids"
|
||||
targetDefaultPortKey = "default_port"
|
||||
targetSessionMaxSecondsKey = "session_max_seconds"
|
||||
targetSessionConnectionLimitKey = "session_connection_limit"
|
||||
targetWorkerFilterKey = "worker_filter"
|
||||
|
||||
targetTypeTcp = "tcp"
|
||||
)
|
||||
@@ -72,6 +73,12 @@ func resourceTarget() *schema.Resource {
|
||||
Optional: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
targetApplicationCredentialLibraryIdsKey: {
|
||||
Description: "A list of application credential library ID's.",
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
},
|
||||
targetSessionMaxSecondsKey: {
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
@@ -91,15 +98,34 @@ func resourceTarget() *schema.Resource {
|
||||
}
|
||||
}
|
||||
|
||||
func setFromTargetResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
|
||||
d.Set(NameKey, raw["name"])
|
||||
d.Set(DescriptionKey, raw["description"])
|
||||
d.Set(ScopeIdKey, raw["scope_id"])
|
||||
d.Set(TypeKey, raw["type"])
|
||||
d.Set(targetHostSetIdsKey, raw["host_set_ids"])
|
||||
d.Set(targetSessionMaxSecondsKey, raw["session_max_seconds"])
|
||||
d.Set(targetSessionConnectionLimitKey, raw["session_connection_limit"])
|
||||
d.Set(targetWorkerFilterKey, raw["worker_filter"])
|
||||
func setFromTargetResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
|
||||
if err := d.Set(NameKey, raw["name"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(ScopeIdKey, raw["scope_id"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(TypeKey, raw["type"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(targetHostSetIdsKey, raw["host_set_ids"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(targetApplicationCredentialLibraryIdsKey, raw["application_credential_library_ids"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(targetSessionMaxSecondsKey, raw["session_max_seconds"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(targetSessionConnectionLimitKey, raw["session_connection_limit"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(targetWorkerFilterKey, raw["worker_filter"]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch raw["type"].(string) {
|
||||
case targetTypeTcp:
|
||||
@@ -107,12 +133,15 @@ func setFromTargetResponseMap(d *schema.ResourceData, raw map[string]interface{}
|
||||
attrs := attrsVal.(map[string]interface{})
|
||||
if defPort, ok := attrs["default_port"].(json.Number); ok {
|
||||
defPortInt, _ := defPort.Int64()
|
||||
d.Set(targetDefaultPortKey, int(defPortInt))
|
||||
if err := d.Set(targetDefaultPortKey, int(defPortInt)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(raw["id"].(string))
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceTargetCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
@@ -137,8 +166,7 @@ func resourceTargetCreate(ctx context.Context, d *schema.ResourceData, meta inte
|
||||
return diag.Errorf("invalid type provided")
|
||||
}
|
||||
|
||||
opts := []targets.Option{}
|
||||
|
||||
var opts []targets.Option
|
||||
nameVal, ok := d.GetOk(NameKey)
|
||||
if ok {
|
||||
nameStr := nameVal.(string)
|
||||
@@ -187,6 +215,15 @@ func resourceTargetCreate(ctx context.Context, d *schema.ResourceData, meta inte
|
||||
}
|
||||
}
|
||||
|
||||
var credentialLibraryIds []string
|
||||
if credentialLibraryIdsVal, ok := d.GetOk(targetApplicationCredentialLibraryIdsKey); ok {
|
||||
list := credentialLibraryIdsVal.(*schema.Set).List()
|
||||
credentialLibraryIds = make([]string, 0, len(list))
|
||||
for _, i := range list {
|
||||
credentialLibraryIds = append(credentialLibraryIds, i.(string))
|
||||
}
|
||||
}
|
||||
|
||||
workerFilterVal, ok := d.GetOk(targetWorkerFilterKey)
|
||||
if ok {
|
||||
workerFilterStr := workerFilterVal.(string)
|
||||
@@ -202,17 +239,29 @@ func resourceTargetCreate(ctx context.Context, d *schema.ResourceData, meta inte
|
||||
if tcr == nil {
|
||||
return diag.Errorf("target nil after create")
|
||||
}
|
||||
raw := tcr.GetResponseMap()
|
||||
raw := tcr.GetResponse().Map
|
||||
|
||||
version := tcr.Item.Version
|
||||
if hostSetIds != nil {
|
||||
tur, err := tc.SetHostSets(ctx, tcr.Item.Id, tcr.Item.Version, hostSetIds)
|
||||
tur, err := tc.SetHostSets(ctx, tcr.Item.Id, version, hostSetIds)
|
||||
if err != nil {
|
||||
return diag.Errorf("error setting host sets on target: %v", err)
|
||||
}
|
||||
raw = tur.GetResponseMap()
|
||||
raw = tur.GetResponse().Map
|
||||
version = tur.Item.Version
|
||||
}
|
||||
|
||||
setFromTargetResponseMap(d, raw)
|
||||
if credentialLibraryIds != nil {
|
||||
tur, err := tc.SetCredentialLibraries(ctx, tcr.Item.Id, version, targets.WithApplicationCredentialLibraryIds(credentialLibraryIds))
|
||||
if err != nil {
|
||||
return diag.Errorf("error setting credential libraries on target: %v", err)
|
||||
}
|
||||
raw = tur.GetResponse().Map
|
||||
}
|
||||
|
||||
if err := setFromTargetResponseMap(d, raw); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -223,7 +272,7 @@ func resourceTargetRead(ctx context.Context, d *schema.ResourceData, meta interf
|
||||
|
||||
trr, err := tc.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
@@ -233,7 +282,9 @@ func resourceTargetRead(ctx context.Context, d *schema.ResourceData, meta interf
|
||||
return diag.Errorf("target nil after read")
|
||||
}
|
||||
|
||||
setFromTargetResponseMap(d, trr.GetResponseMap())
|
||||
if err := setFromTargetResponseMap(d, trr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -242,7 +293,7 @@ func resourceTargetUpdate(ctx context.Context, d *schema.ResourceData, meta inte
|
||||
md := meta.(*metaData)
|
||||
tc := targets.NewClient(md.client)
|
||||
|
||||
opts := []targets.Option{}
|
||||
var opts []targets.Option
|
||||
|
||||
var name *string
|
||||
if d.HasChange(NameKey) {
|
||||
@@ -328,22 +379,34 @@ func resourceTargetUpdate(ctx context.Context, d *schema.ResourceData, meta inte
|
||||
}
|
||||
|
||||
if d.HasChange(NameKey) {
|
||||
d.Set(NameKey, name)
|
||||
if err := d.Set(NameKey, name); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
if d.HasChange(DescriptionKey) {
|
||||
d.Set(DescriptionKey, desc)
|
||||
if err := d.Set(DescriptionKey, desc); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
if d.HasChange(targetDefaultPortKey) {
|
||||
d.Set(targetDefaultPortKey, defaultPort)
|
||||
if err := d.Set(targetDefaultPortKey, defaultPort); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
if d.HasChange(targetSessionMaxSecondsKey) {
|
||||
d.Set(targetSessionMaxSecondsKey, sessionMaxSeconds)
|
||||
if err := d.Set(targetSessionMaxSecondsKey, sessionMaxSeconds); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
if d.HasChange(targetSessionConnectionLimitKey) {
|
||||
d.Set(targetSessionConnectionLimitKey, sessionConnectionLimit)
|
||||
if err := d.Set(targetSessionConnectionLimitKey, sessionConnectionLimit); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
if d.HasChange(targetWorkerFilterKey) {
|
||||
d.Set(targetWorkerFilterKey, workerFilter)
|
||||
if err := d.Set(targetWorkerFilterKey, workerFilter); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
// The above call may not actually happen, so we use d.Id() and automatic
|
||||
@@ -360,7 +423,37 @@ func resourceTargetUpdate(ctx context.Context, d *schema.ResourceData, meta inte
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating host sets in target: %v", err)
|
||||
}
|
||||
d.Set(targetHostSetIdsKey, hostSetIds)
|
||||
if err := d.Set(targetHostSetIdsKey, hostSetIds); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
// The above calls may not actually happen, so we use d.Id() and automatic
|
||||
// versioning here
|
||||
if d.HasChange(targetApplicationCredentialLibraryIdsKey) {
|
||||
var credentialLibraryIds []string
|
||||
if credentialLibraryIdsVal, ok := d.GetOk(targetApplicationCredentialLibraryIdsKey); ok {
|
||||
credLibsIds := credentialLibraryIdsVal.(*schema.Set).List()
|
||||
for _, credLibId := range credLibsIds {
|
||||
credentialLibraryIds = append(credentialLibraryIds, credLibId.(string))
|
||||
}
|
||||
}
|
||||
|
||||
opts := []targets.Option{
|
||||
targets.WithAutomaticVersioning(true),
|
||||
targets.DefaultApplicationCredentialLibraryIds(),
|
||||
}
|
||||
if len(credentialLibraryIds) > 0 {
|
||||
opts = append(opts, targets.WithApplicationCredentialLibraryIds(credentialLibraryIds))
|
||||
}
|
||||
|
||||
_, err := tc.SetCredentialLibraries(ctx, d.Id(), 0, opts...)
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating credential libraries in target: %v", err)
|
||||
}
|
||||
if err := d.Set(targetApplicationCredentialLibraryIdsKey, credentialLibraryIds); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/testing/vault"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/targets"
|
||||
"github.com/hashicorp/boundary/testing/controller"
|
||||
@@ -20,7 +22,7 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
fooHostSet = `
|
||||
fooBarHostSet = `
|
||||
resource "boundary_host_catalog" "foo" {
|
||||
type = "static"
|
||||
name = "test"
|
||||
@@ -49,10 +51,36 @@ resource "boundary_host_set" "foo" {
|
||||
host_catalog_id = boundary_host_catalog.foo.id
|
||||
host_ids = [
|
||||
boundary_host.foo.id,
|
||||
]
|
||||
}
|
||||
|
||||
resource "boundary_host_set" "bar" {
|
||||
name = "bar"
|
||||
type = "static"
|
||||
host_catalog_id = boundary_host_catalog.foo.id
|
||||
host_ids = [
|
||||
boundary_host.bar.id,
|
||||
]
|
||||
}`
|
||||
|
||||
fooBarCredLibs = `
|
||||
resource "boundary_credential_library_vault" "foo" {
|
||||
name = "foo"
|
||||
description = "foo library"
|
||||
credential_store_id = boundary_credential_store_vault.example.id
|
||||
path = "foo/bar"
|
||||
http_method = "GET"
|
||||
}
|
||||
|
||||
resource "boundary_credential_library_vault" "bar" {
|
||||
name = "bar"
|
||||
description = "bar library"
|
||||
credential_store_id = boundary_credential_store_vault.example.id
|
||||
path = "bar/foo"
|
||||
http_method = "GET"
|
||||
}
|
||||
`
|
||||
|
||||
fooTarget = fmt.Sprintf(`
|
||||
resource "boundary_target" "foo" {
|
||||
name = "test"
|
||||
@@ -62,6 +90,9 @@ resource "boundary_target" "foo" {
|
||||
host_set_ids = [
|
||||
boundary_host_set.foo.id
|
||||
]
|
||||
application_credential_library_ids = [
|
||||
boundary_credential_library_vault.foo.id
|
||||
]
|
||||
default_port = 22
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
session_max_seconds = 6000
|
||||
@@ -76,8 +107,24 @@ resource "boundary_target" "foo" {
|
||||
type = "tcp"
|
||||
scope_id = boundary_scope.proj1.id
|
||||
host_set_ids = [
|
||||
boundary_host_set.foo.id
|
||||
boundary_host_set.bar.id
|
||||
]
|
||||
application_credential_library_ids = [
|
||||
boundary_credential_library_vault.bar.id
|
||||
]
|
||||
default_port = 80
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
session_max_seconds = 7000
|
||||
session_connection_limit = 7
|
||||
worker_filter = "type == \"bar\""
|
||||
}`, fooTargetDescriptionUpdate)
|
||||
|
||||
fooTargetUpdateUnsetHostAndCredLibs = fmt.Sprintf(`
|
||||
resource "boundary_target" "foo" {
|
||||
name = "test"
|
||||
description = "%s"
|
||||
type = "tcp"
|
||||
scope_id = boundary_scope.proj1.id
|
||||
default_port = 80
|
||||
depends_on = [boundary_role.proj1_admin]
|
||||
session_max_seconds = 7000
|
||||
@@ -92,6 +139,16 @@ func TestAccTarget(t *testing.T) {
|
||||
defer tc.Shutdown()
|
||||
url := tc.ApiAddrs()[0]
|
||||
|
||||
vc := vault.NewTestVaultServer(t)
|
||||
_, token := vc.CreateToken(t)
|
||||
credStoreRes := vaultCredStoreResource(vc,
|
||||
vaultCredStoreName,
|
||||
vaultCredStoreDesc,
|
||||
vaultCredStoreNamespace,
|
||||
"www.original.com",
|
||||
token,
|
||||
true)
|
||||
|
||||
var provider *schema.Provider
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProviderFactories: providerFactories(&provider),
|
||||
@@ -99,7 +156,7 @@ func TestAccTarget(t *testing.T) {
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
// test create
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, fooHostSet, fooTarget),
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, fooBarCredLibs, fooBarHostSet, fooTarget),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckTargetResourceExists(provider, "boundary_target.foo"),
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", DescriptionKey, fooTargetDescription),
|
||||
@@ -108,12 +165,14 @@ func TestAccTarget(t *testing.T) {
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionMaxSecondsKey, "6000"),
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionConnectionLimitKey, "6"),
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", targetWorkerFilterKey, `type == "foo"`),
|
||||
testAccCheckTargetResourceHostSet(provider, "boundary_target.foo", []string{"boundary_host_set.foo"}),
|
||||
testAccCheckTargetResourceAppCredLibs(provider, "boundary_target.foo", []string{"boundary_credential_library_vault.foo"}),
|
||||
),
|
||||
},
|
||||
importStep("boundary_target.foo"),
|
||||
{
|
||||
// test update
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, fooHostSet, fooTargetUpdate),
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, fooBarCredLibs, fooBarHostSet, fooTargetUpdate),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckTargetResourceExists(provider, "boundary_target.foo"),
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", DescriptionKey, fooTargetDescriptionUpdate),
|
||||
@@ -121,6 +180,23 @@ func TestAccTarget(t *testing.T) {
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionMaxSecondsKey, "7000"),
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionConnectionLimitKey, "7"),
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", targetWorkerFilterKey, `type == "bar"`),
|
||||
testAccCheckTargetResourceHostSet(provider, "boundary_target.foo", []string{"boundary_host_set.bar"}),
|
||||
testAccCheckTargetResourceAppCredLibs(provider, "boundary_target.foo", []string{"boundary_credential_library_vault.bar"}),
|
||||
),
|
||||
},
|
||||
importStep("boundary_target.foo"),
|
||||
{
|
||||
// test unset hosts and cred libs
|
||||
Config: testConfig(url, fooOrg, firstProjectFoo, credStoreRes, fooBarCredLibs, fooBarHostSet, fooTargetUpdateUnsetHostAndCredLibs),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckTargetResourceExists(provider, "boundary_target.foo"),
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", DescriptionKey, fooTargetDescriptionUpdate),
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", targetDefaultPortKey, "80"),
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionMaxSecondsKey, "7000"),
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", targetSessionConnectionLimitKey, "7"),
|
||||
resource.TestCheckResourceAttr("boundary_target.foo", targetWorkerFilterKey, `type == "bar"`),
|
||||
testAccCheckTargetResourceHostSet(provider, "boundary_target.foo", nil),
|
||||
testAccCheckTargetResourceAppCredLibs(provider, "boundary_target.foo", nil),
|
||||
),
|
||||
},
|
||||
importStep("boundary_target.foo"),
|
||||
@@ -128,7 +204,7 @@ func TestAccTarget(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckTargetResourceMembersSet(testProvider *schema.Provider, name string, hostSets []string) resource.TestCheckFunc {
|
||||
func testAccCheckTargetResourceHostSet(testProvider *schema.Provider, name string, hostSets []string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[name]
|
||||
if !ok {
|
||||
@@ -141,7 +217,7 @@ func testAccCheckTargetResourceMembersSet(testProvider *schema.Provider, name st
|
||||
}
|
||||
|
||||
// ensure host sets are declared in state
|
||||
hostSetIDs := []string{}
|
||||
var hostSetIDs []string
|
||||
for _, hostSetResourceID := range hostSets {
|
||||
hs, ok := s.RootModule().Resources[hostSetResourceID]
|
||||
if !ok {
|
||||
@@ -165,19 +241,76 @@ func testAccCheckTargetResourceMembersSet(testProvider *schema.Provider, name st
|
||||
return fmt.Errorf("Got an error when reading target %q: %v", id, err)
|
||||
}
|
||||
|
||||
if len(t.Item.HostSetIds) == 0 {
|
||||
return fmt.Errorf("no hostSets found on target")
|
||||
if len(t.Item.HostSetIds) != len(hostSetIDs) {
|
||||
return fmt.Errorf("tf state and boundary have different number of host sets")
|
||||
}
|
||||
|
||||
for _, stateHostSet := range t.Item.HostSetIds {
|
||||
for _, stateHostSetId := range t.Item.HostSetIds {
|
||||
ok := false
|
||||
for _, gotHostSetID := range hostSetIDs {
|
||||
if gotHostSetID == stateHostSet {
|
||||
if gotHostSetID == stateHostSetId {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("host set in state not set in boundary: %s", stateHostSet)
|
||||
return fmt.Errorf("host set id in state not set in boundary: %s", stateHostSetId)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckTargetResourceAppCredLibs(testProvider *schema.Provider, name string, credLibs []string) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("target resource not found: %s", name)
|
||||
}
|
||||
|
||||
id := rs.Primary.ID
|
||||
if id == "" {
|
||||
return fmt.Errorf("target resource ID is not set")
|
||||
}
|
||||
|
||||
// ensure host sets are declared in state
|
||||
var credLibIDs []string
|
||||
for _, credLibResourceID := range credLibs {
|
||||
cl, ok := s.RootModule().Resources[credLibResourceID]
|
||||
if !ok {
|
||||
return fmt.Errorf("credential library resource not found: %s", credLibResourceID)
|
||||
}
|
||||
|
||||
credLibID := cl.Primary.ID
|
||||
if id == "" {
|
||||
return fmt.Errorf("credential library resource ID not set")
|
||||
}
|
||||
|
||||
credLibIDs = append(credLibIDs, credLibID)
|
||||
}
|
||||
|
||||
// check boundary to ensure it matches
|
||||
md := testProvider.Meta().(*metaData)
|
||||
tgtsClient := targets.NewClient(md.client)
|
||||
|
||||
t, err := tgtsClient.Read(context.Background(), id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Got an error when reading target %q: %v", id, err)
|
||||
}
|
||||
|
||||
if len(t.Item.ApplicationCredentialLibraryIds) != len(credLibIDs) {
|
||||
return fmt.Errorf("tf state and boundary have different number of application credential libraries")
|
||||
}
|
||||
|
||||
for _, stateCredLibId := range t.Item.ApplicationCredentialLibraryIds {
|
||||
ok := false
|
||||
for _, gotCredLibID := range credLibIDs {
|
||||
if gotCredLibID == stateCredLibId {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("application credential library id in state not set in boundary: %s", stateCredLibId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +357,7 @@ func testAccCheckTargetResourceDestroy(t *testing.T, testProvider *schema.Provid
|
||||
id := rs.Primary.ID
|
||||
|
||||
_, err := tgts.Read(context.Background(), id)
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.ResponseStatus() != http.StatusNotFound {
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||
return fmt.Errorf("didn't get a 404 when reading destroyed target %q: %v", id, apiErr)
|
||||
}
|
||||
|
||||
|
||||
@@ -56,12 +56,21 @@ func resourceUser() *schema.Resource {
|
||||
}
|
||||
}
|
||||
|
||||
func setFromUserResponseMap(d *schema.ResourceData, raw map[string]interface{}) {
|
||||
d.Set(NameKey, raw["name"])
|
||||
d.Set(DescriptionKey, raw["description"])
|
||||
d.Set(ScopeIdKey, raw["scope_id"])
|
||||
d.Set(userAccountIDsKey, raw["account_ids"])
|
||||
func setFromUserResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
|
||||
if err := d.Set(NameKey, raw["name"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(DescriptionKey, raw["description"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(ScopeIdKey, raw["scope_id"]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.Set(userAccountIDsKey, raw["account_ids"]); err != nil {
|
||||
return err
|
||||
}
|
||||
d.SetId(raw["id"].(string))
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
@@ -97,7 +106,7 @@ func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interf
|
||||
if ucr == nil {
|
||||
return diag.Errorf("user nil after create")
|
||||
}
|
||||
raw := ucr.GetResponseMap()
|
||||
raw := ucr.GetResponse().Map
|
||||
|
||||
if val, ok := d.GetOk(userAccountIDsKey); ok {
|
||||
list := val.(*schema.Set).List()
|
||||
@@ -112,10 +121,12 @@ func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interf
|
||||
if usrac == nil {
|
||||
return diag.Errorf("user nil after setting accounts")
|
||||
}
|
||||
raw = usrac.GetResponseMap()
|
||||
raw = usrac.GetResponse().Map
|
||||
}
|
||||
|
||||
setFromUserResponseMap(d, raw)
|
||||
if err := setFromUserResponseMap(d, raw); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -126,7 +137,7 @@ func resourceUserRead(ctx context.Context, d *schema.ResourceData, meta interfac
|
||||
|
||||
urr, err := usrs.Read(ctx, d.Id())
|
||||
if err != nil {
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.ResponseStatus() == http.StatusNotFound{
|
||||
if apiErr := api.AsServerError(err); apiErr != nil && apiErr.Response().StatusCode() == http.StatusNotFound {
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
@@ -136,7 +147,9 @@ func resourceUserRead(ctx context.Context, d *schema.ResourceData, meta interfac
|
||||
return diag.Errorf("user nil after read")
|
||||
}
|
||||
|
||||
setFromUserResponseMap(d, urr.GetResponseMap())
|
||||
if err := setFromUserResponseMap(d, urr.GetResponse().Map); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -178,10 +191,14 @@ func resourceUserUpdate(ctx context.Context, d *schema.ResourceData, meta interf
|
||||
}
|
||||
|
||||
if d.HasChange(NameKey) {
|
||||
d.Set(NameKey, name)
|
||||
if err := d.Set(NameKey, name); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
if d.HasChange(DescriptionKey) {
|
||||
d.Set(DescriptionKey, desc)
|
||||
if err := d.Set(DescriptionKey, desc); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
if d.HasChange(userAccountIDsKey) {
|
||||
@@ -197,7 +214,9 @@ func resourceUserUpdate(ctx context.Context, d *schema.ResourceData, meta interf
|
||||
if err != nil {
|
||||
return diag.Errorf("error updating accounts on user: %v", err)
|
||||
}
|
||||
d.Set(userAccountIDsKey, accountIds)
|
||||
if err := d.Set(userAccountIDsKey, accountIds); err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -232,7 +232,7 @@ func testAccCheckUserResourceDestroy(t *testing.T, testProvider *schema.Provider
|
||||
usrs := users.NewClient(md.client)
|
||||
|
||||
_, err := usrs.Read(context.Background(), id)
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.ResponseStatus() != http.StatusNotFound {
|
||||
if apiErr := api.AsServerError(err); apiErr == nil || apiErr.Response().StatusCode() != http.StatusNotFound {
|
||||
return fmt.Errorf("didn't get a 404 when reading destroyed user %q: %v", id, err)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user