Add support for boundary_self_managed_worker resource. (#293)

* add initial prototype of self managed worker resource and associated tests

* Add self managed worker resource and associated tests

* Update internal/provider/resource_self_managed_worker.go

Accept suggested schema changes for tags

Co-authored-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>

* add environment variable check for worker led auth testing

* change resource name from self_managed_worker to worker for consistency with the api

* update tag schemas, ensure all attributes have descriptions

* add generated docs for worker

* add examples for worker resource

* add examples for worker resource

* remove merge conflict

* Remove funtionality for reflecting tags in the schema

* remove unused attributes (all tags) from schema, updates tests to use 'boundary_worker' on resource checks

Co-authored-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
This commit is contained in:
grantorchardandJohan Brandhorst-Satzkorn authored and GitHub committed 2022-11-24 11:28:27 -05:00
1 parent 79951499ec
commit cfc57d5e25
6 files changed
+473

No files matched your search

+39
View File
@@ -0,0 +1,39 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "boundary_worker Resource - terraform-provider-boundary"
subcategory: ""
description: |-
The resource allows you to create a self-managed worker object.
---
# boundary_worker (Resource)
The resource allows you to create a self-managed worker object.
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `scope_id` (String) The scope for the worker.
### Optional
- `api_tags` (Map of List of String) API tags applied to the worker.
- `description` (String) The description for the worker.
- `name` (String) The name for the worker.
- `worker_generated_auth_token` (String) The worker authentication token required to register the worker for the worker-led authentication flow. Leaving this blank will result in a controller generated token.
### Read-Only
- `address` (String) The accessible address of the self managed worker.
- `authorized_actions` (List of String) A list of actions that the worker is entitled to perform.
- `canonical_tags` (Map of List of String) The aggregated view of worker tags and API tags.
- `config_tags` (Map of List of String) Tags as configured in the worker's HCL file.
- `controller_generated_activation_token` (String) A single use token generated by the controller to be passed to the self-managed worker.
- `id` (String) The ID of the worker.
- `release_version` (Number) The version of the Boundary binary running on the self managed worker.
@@ -0,0 +1 @@
terraform import boundary_worker.foo <my-id>
@@ -0,0 +1,13 @@
resource "boundary_worker" "controller_led" {
scope_id = "global"
name = "worker 1"
description = "self managed worker with controlled led auth"
worker_generated_auth_token = var.worker_generated_auth_token
}
resource "boundary_self_managed_worker" "worker_led" {
scope_id = "global"
name = "worker 2"
description = "self managed worker with controlled led auth"
worker_generated_auth_token = var.worker_generated_auth_token
}
+1
View File
@@ -94,6 +94,7 @@ func New() *schema.Provider {
"boundary_scope": resourceScope(),
"boundary_target": resourceTarget(),
"boundary_user": resourceUser(),
"boundary_worker": resourceWorker(),
},
}
+239
View File
@@ -0,0 +1,239 @@
package provider
import (
"context"
"net/http"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/workers"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
const (
scope = "scope"
scopeId = "global"
version = "version"
address = "address"
canonicalTags = "canonical_tags"
configTags = "config_tags"
workerGeneratedAuthToken = "worker_generated_auth_token"
controllerGeneratedActivationToken = "controller_generated_activation_token"
apiTags = "api_tags"
releaseVersion = "release_version"
authorizedActions = "authorized_actions"
)
func resourceWorker() *schema.Resource {
return &schema.Resource{
Description: "The resource allows you to create a self-managed worker object.",
CreateContext: resourceWorkerCreate,
ReadContext: resourceWorkerRead,
UpdateContext: resourceWorkerUpdate,
DeleteContext: resourceWorkerDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
IDKey: {
Description: "The ID of the worker.",
Type: schema.TypeString,
Computed: true,
},
ScopeIdKey: {
Description: "The scope for the worker.",
Type: schema.TypeString,
Required: true,
},
NameKey: {
Description: "The name for the worker.",
Type: schema.TypeString,
Optional: true,
},
DescriptionKey: {
Description: "The description for the worker.",
Type: schema.TypeString,
Optional: true,
},
address: {
Description: "The accessible address of the self managed worker.",
Type: schema.TypeString,
Computed: true,
},
workerGeneratedAuthToken: {
Description: "The worker authentication token required to register the worker for the worker-led authentication flow. Leaving this blank will result in a controller generated token.",
Type: schema.TypeString,
Optional: true,
},
controllerGeneratedActivationToken: {
Description: "A single use token generated by the controller to be passed to the self-managed worker.",
Type: schema.TypeString,
Computed: true,
},
releaseVersion: {
Description: "The version of the Boundary binary running on the self managed worker.",
Type: schema.TypeInt,
Computed: true,
},
authorizedActions: {
Description: "A list of actions that the worker is entitled to perform.",
Type: schema.TypeList,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Computed: true,
},
},
}
}
func setFromWorkerResponseMap(d *schema.ResourceData, raw map[string]interface{}) error {
d.SetId(raw["id"].(string))
d.Set(ScopeIdKey, raw["scope_id"])
d.Set(NameKey, raw["name"])
d.Set(DescriptionKey, raw["description"])
d.Set(address, raw["address"])
d.Set(workerGeneratedAuthToken, raw["worker_generated_auth_token"])
d.Set(controllerGeneratedActivationToken, raw["controller_generated_activation_token"])
d.Set(releaseVersion, raw["release_version"])
d.Set(authorizedActions, raw["authorized_actions"])
return nil
}
func resourceWorkerRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
wkrs := workers.NewClient(md.client)
wrr, err := wkrs.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 calling read worker: %v", err)
}
if wrr == nil {
return diag.Errorf("worker nil after read")
}
if err := setFromWorkerResponseMap(d, wrr.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
return nil
}
func resourceWorkerCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
opts := []workers.Option{}
if v, ok := d.GetOk(NameKey); ok {
opts = append(opts, workers.WithName(v.(string)))
}
if v, ok := d.GetOk(DescriptionKey); ok {
opts = append(opts, workers.WithDescription(v.(string)))
}
var workerAuthToken string
if v, ok := d.GetOk(workerGeneratedAuthToken); ok {
workerAuthToken = v.(string)
}
wkr := workers.NewClient(md.client)
if len(workerAuthToken) > 0 {
wkrc, err := wkr.CreateWorkerLed(ctx, workerAuthToken, scopeId, opts...)
if err != nil {
return diag.Errorf("error creating worker: %v", err)
}
if wkrc == nil {
return diag.Errorf("worker nil after create")
}
if err := setFromWorkerResponseMap(d, wkrc.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
} else {
wkrc, err := wkr.CreateControllerLed(ctx, scopeId, opts...)
if err != nil {
return diag.Errorf("error creating worker: %v", err)
}
if wkrc == nil {
return diag.Errorf("worker nil after create")
}
if err := setFromWorkerResponseMap(d, wkrc.GetResponse().Map); err != nil {
return diag.FromErr(err)
}
}
return nil
}
func resourceWorkerUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
wkr := workers.NewClient(md.client)
opts := []workers.Option{}
var name *string
if d.HasChange(NameKey) {
opts = append(opts, workers.DefaultName())
nameVal, ok := d.GetOk(NameKey)
if ok {
nameStr := nameVal.(string)
name = &nameStr
opts = append(opts, workers.WithName(nameStr))
}
}
var desc *string
if d.HasChange(DescriptionKey) {
opts = append(opts, workers.DefaultDescription())
descVal, ok := d.GetOk(DescriptionKey)
if ok {
descStr := descVal.(string)
desc = &descStr
opts = append(opts, workers.WithDescription(descStr))
}
}
var versionInt int
if versionVal, ok := d.GetOk(version); ok {
versionInt = versionVal.(int)
}
if len(opts) > 0 {
opts = append(opts, workers.WithAutomaticVersioning(true))
_, err := wkr.Update(ctx, d.Id(), uint32(versionInt), opts...)
if err != nil {
return diag.Errorf("error updating worker: %v", err)
}
}
if d.HasChange(NameKey) {
if err := d.Set(NameKey, name); err != nil {
return diag.FromErr(err)
}
}
if d.HasChange(DescriptionKey) {
if err := d.Set(DescriptionKey, desc); err != nil {
return diag.FromErr(err)
}
}
return nil
}
func resourceWorkerDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
md := meta.(*metaData)
wClient := workers.NewClient(md.client)
_, err := wClient.Delete(ctx, d.Id())
if err != nil {
return diag.Errorf("error deleting worker: %v", err)
}
return nil
}
+180
View File
@@ -0,0 +1,180 @@
package provider
import (
"context"
"fmt"
"net/http"
"os"
"testing"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/workers"
"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 (
workerName = "self managed worker"
workerNameUpdate = "self managed worker update"
workerDesc = "self managed worker description"
workerDescUpdate = "self managed worker description update"
workerToken = "GzusqckarbczHoLGQ4UA25uSRGy7e7hUk4Qgz8T3NmSZF4oXBc1dKMRyH7Mr6W5u2QWggkZi1wsnfYMufb5LZJJnxEdywUxpE8RgAbVTahMhJm8oeH3nagrAKfWJkrTt8QuoqiupcTrFmJtDPZ5WBtqKSXiW3jdG8esCt7ZNKVaKV1Hq6MBXUf7duj9KfAy4Y3B31jDTzoF1uVnK1AsaLkEhgbHbuAH33L2KG5ivo7YeFeE6PknNoVavPSRSkoEpcXSfjvoPMAz9ttpNvH7jGWPwLti8r48NcVj41ftXWg"
)
var (
workerLedCreate = fmt.Sprintf(`
resource "boundary_worker" "worker_led" {
scope_id = "global"
name = "%s"
description = "%s"
worker_generated_auth_token = "%s"
}`, workerName, workerDesc, workerToken)
workerLedUpdate = fmt.Sprintf(`
resource "boundary_worker" "worker_led" {
scope_id = "global"
name = "%s"
description = "%s"
worker_generated_auth_token = "%s"
}`, workerNameUpdate, workerDescUpdate, workerToken)
controllerLedCreate = fmt.Sprintf(`
resource "boundary_worker" "controller_led" {
scope_id = "global"
name = "%s"
description = "%s"
}`, workerName, workerDesc)
controllerLedUpdate = fmt.Sprintf(`
resource "boundary_worker" "controller_led" {
scope_id = "global"
name = "%s"
description = "%s"
}`, workerNameUpdate, workerDescUpdate)
)
func TestWorkerWorkerLed(t *testing.T) {
token := os.Getenv("BOUNDARY_TF_PROVIDER_TEST_WORKER_LED_TOKEN")
if token == "" {
t.Skip("Not running worker led activation test without worker led token present")
}
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: testAccCheckworkerResourceDestroy(t, provider),
Steps: []resource.TestStep{
{
// create
Config: testConfig(url, workerLedCreate),
Check: resource.ComposeTestCheckFunc(
testAccCheckworkerResourceExists(provider, "boundary_worker.worker_led"),
resource.TestCheckResourceAttr("boundary_worker.worker_led", "description", workerDesc),
resource.TestCheckResourceAttr("boundary_worker.worker_led", "name", workerName),
),
},
importStep("boundary_worker.worker_led"),
{
// update
Config: testConfig(url, workerLedUpdate),
Check: resource.ComposeTestCheckFunc(
testAccCheckworkerResourceExists(provider, "boundary_worker.worker_led"),
resource.TestCheckResourceAttr("boundary_worker.worker_led", "description", workerDescUpdate),
resource.TestCheckResourceAttr("boundary_worker.worker_led", "name", workerNameUpdate),
),
},
importStep("boundary_worker.worker_led"),
},
})
}
func TestWorkerControllerLed(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: testAccCheckworkerResourceDestroy(t, provider),
Steps: []resource.TestStep{
{
// create
Config: testConfig(url, controllerLedCreate),
Check: resource.ComposeTestCheckFunc(
testAccCheckworkerResourceExists(provider, "boundary_worker.controller_led"),
resource.TestCheckResourceAttr("boundary_worker.controller_led", "description", workerDesc),
resource.TestCheckResourceAttr("boundary_worker.controller_led", "name", workerName),
resource.TestCheckResourceAttrSet("boundary_worker.controller_led", "controller_generated_activation_token"),
),
},
importStep("boundary_worker.controller_led"),
{
// update
Config: testConfig(url, controllerLedUpdate),
Check: resource.ComposeTestCheckFunc(
testAccCheckworkerResourceExists(provider, "boundary_worker.controller_led"),
resource.TestCheckResourceAttr("boundary_worker.controller_led", "description", workerDescUpdate),
resource.TestCheckResourceAttr("boundary_worker.controller_led", "name", workerNameUpdate),
),
},
importStep("boundary_worker.controller_led"),
},
})
}
func testAccCheckworkerResourceExists(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)
wkrClient := workers.NewClient(md.client)
if _, err := wkrClient.Read(context.Background(), id); err != nil {
return fmt.Errorf("Got an error when reading worker %q: %v", id, err)
}
return nil
}
}
func testAccCheckworkerResourceDestroy(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_worker":
id := rs.Primary.ID
wkrClient := workers.NewClient(md.client)
_, err := wkrClient.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 worker %q: %v", id, err)
}
default:
continue
}
}
return nil
}
}