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:
1 parent
79951499ec
commit
cfc57d5e25
6 files changed
+473
No files matched your search
@@ -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
|
||||
}
|
||||
@@ -94,6 +94,7 @@ func New() *schema.Provider {
|
||||
"boundary_scope": resourceScope(),
|
||||
"boundary_target": resourceTarget(),
|
||||
"boundary_user": resourceUser(),
|
||||
"boundary_worker": resourceWorker(),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user