From 0018735bffd62643e8e05fe23d6cb3effe46172b Mon Sep 17 00:00:00 2001 From: Grant Orchard Date: Mon, 9 Oct 2023 11:54:19 +1100 Subject: [PATCH 1/2] add scope datasource, tests, and docs --- docs/data-sources/scope.md | 41 +++++++ examples/data-sources/scope/data-source.tf | 10 ++ internal/provider/data_source_scope.go | 112 ++++++++++++++++++++ internal/provider/data_source_scope_test.go | 95 +++++++++++++++++ internal/provider/provider.go | 3 + 5 files changed, 261 insertions(+) create mode 100644 docs/data-sources/scope.md create mode 100644 examples/data-sources/scope/data-source.tf create mode 100644 internal/provider/data_source_scope.go create mode 100644 internal/provider/data_source_scope_test.go diff --git a/docs/data-sources/scope.md b/docs/data-sources/scope.md new file mode 100644 index 00000000..75321898 --- /dev/null +++ b/docs/data-sources/scope.md @@ -0,0 +1,41 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "boundary_scope Data Source - terraform-provider-boundary" +subcategory: "" +description: |- + The scope data source allows you to discover an existing Boundary scope by name. +--- + +# boundary_scope (Data Source) + +The scope data source allows you to discover an existing Boundary scope by name. + +## Example Usage + +Basic usage: + +```terraform +data "boundary_scope" "org" { + name = "SecOps" + parent_scope_id = "global" +} + +data "boundary_scope" "project" { + name = "2111" + parent_scope_id = data.boundary_scope.id +} +``` + + + +## Schema + +### Required + +- `name` (String) The name of the scope to retrieve. +- `scope_id` (String) The parent scope ID that will be queried for the scope. + +### Read-Only + +- `description` (String) The description of the retrieved scope. +- `id` (String) The ID of the retrieved scope. diff --git a/examples/data-sources/scope/data-source.tf b/examples/data-sources/scope/data-source.tf new file mode 100644 index 00000000..049ce21c --- /dev/null +++ b/examples/data-sources/scope/data-source.tf @@ -0,0 +1,10 @@ +# Retrieve the ID of a Boundary project +data "boundary_scope" "org" { + name = "SecOps" + parent_scope_id = "global" +} + +data "boundary_scope" "project" { + name = "2111" + parent_scope_id = data.boundary_scope.id +} \ No newline at end of file diff --git a/internal/provider/data_source_scope.go b/internal/provider/data_source_scope.go new file mode 100644 index 00000000..d0ce2e0d --- /dev/null +++ b/internal/provider/data_source_scope.go @@ -0,0 +1,112 @@ +package provider + +import ( + "context" + "net/http" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/scopes" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceScope() *schema.Resource { + return &schema.Resource{ + Description: "The scope data source allows you to discover an existing Boundary scope by name.", + ReadContext: dataSourceScopeRead, + + Schema: map[string]*schema.Schema{ + IDKey: { + Description: "The ID of the retrieved scope.", + Type: schema.TypeString, + Computed: true, + }, + NameKey: { + Description: "The name of the scope to retrieve.", + Type: schema.TypeString, + Required: true, + }, + DescriptionKey: { + Description: "The description of the retrieved scope.", + Type: schema.TypeString, + Computed: true, + }, + ScopeIdKey: { + Description: "The parent scope ID that will be queried for the scope.", + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func dataSourceScopeRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + md := meta.(*metaData) + opts := []scopes.Option{} + + var name string + if v, ok := d.GetOk(NameKey); ok { + name = v.(string) + } else { + return diag.Errorf("no name provided") + } + + var scopeId string + if scopeIdVal, ok := d.GetOk(ScopeIdKey); ok { + scopeId = scopeIdVal.(string) + } else { + return diag.Errorf("no parent scope ID provided") + } + + scp := scopes.NewClient(md.client) + + scpls, err := scp.List(ctx, scopeId, opts...) + if err != nil { + return diag.Errorf("error calling read scope: %v", err) + } + if scpls == nil { + return diag.Errorf("no scopes found") + } + + var scopeIdRead string + for _, scopeItem := range scpls.GetItems() { + if scopeItem.Name == name { + scopeIdRead = scopeItem.Id + break + } + } + + if scopeIdRead == "" { + return diag.Errorf("scope name %v not found in scope list", err) + } + + srr, err := scp.Read(ctx, scopeIdRead) + 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 scope: %v", err) + } + if srr == nil { + return diag.Errorf("scope nil after read") + } + + if err := setFromScopeReadResponseMap(d, srr.GetResponse().Map); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func setFromScopeReadResponseMap(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 + } + + d.SetId(raw["id"].(string)) + return nil +} \ No newline at end of file diff --git a/internal/provider/data_source_scope_test.go b/internal/provider/data_source_scope_test.go new file mode 100644 index 00000000..4bdf19e4 --- /dev/null +++ b/internal/provider/data_source_scope_test.go @@ -0,0 +1,95 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/boundary/testing/controller" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +const ( + orgName = "test org scope" + projectName = "test project scope" + notProjectName = "test project scope with wrong name" + scopeDesc = "created to test the scope datasource" +) + +var ( + scopeCreateAndRead = fmt.Sprintf(` +resource "boundary_scope" "global" { + global_scope = true + name = "global" + description = "Global Scope" + scope_id = "global" +} + +resource "boundary_scope" "org" { + scope_id = boundary_scope.global.id + name = "%s" + description = "%s" +} + +resource "boundary_scope" "project" { + depends_on = [boundary_role.org_admin] + scope_id = boundary_scope.org.id + name = "%s" + description = "%s" +} + +resource "boundary_role" "org_admin" { + scope_id = "global" + grant_scope_id = boundary_scope.org.id + grant_strings = ["id=*;type=*;actions=*"] + principal_ids = ["u_auth"] +} + +data "boundary_scope" "org" { + depends_on = [boundary_scope.org] + scope_id = "global" + name = "%s" +} + +data "boundary_scope" "project" { + depends_on = [boundary_scope.project] + scope_id = data.boundary_scope.org.id + name = "%s" +}`, orgName, scopeDesc, projectName, scopeDesc, orgName, projectName) +) + +func TestAccScopeRead(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: testAccCheckScopeResourceDestroy(t, provider), + Steps: []resource.TestStep{ + { + // create and read + Config: testConfig(url, scopeCreateAndRead), + Check: resource.ComposeTestCheckFunc( + testAccCheckScopeResourceExists(provider, "boundary_scope.org"), + resource.TestCheckResourceAttr("boundary_scope.org", "description", scopeDesc), + resource.TestCheckResourceAttr("boundary_scope.org", "name", orgName), + testAccCheckScopeResourceExists(provider, "boundary_scope.project"), + resource.TestCheckResourceAttr("boundary_scope.project", "description", scopeDesc), + resource.TestCheckResourceAttr("boundary_scope.project", "name", projectName), + // Check attributes on the org datasource + resource.TestCheckResourceAttrSet("data.boundary_scope.org", "scope_id"), + resource.TestCheckResourceAttrSet("data.boundary_scope.org", "id"), + resource.TestCheckResourceAttr("data.boundary_scope.org", "name", orgName), + resource.TestCheckResourceAttr("data.boundary_scope.org", "description", scopeDesc), + // Check attributes on the project datasource + resource.TestCheckResourceAttrSet("data.boundary_scope.project", "scope_id"), + resource.TestCheckResourceAttrSet("data.boundary_scope.project", "id"), + resource.TestCheckResourceAttr("data.boundary_scope.project", "name", projectName), + resource.TestCheckResourceAttr("data.boundary_scope.project", "description", scopeDesc), + ), + }, + }, + }) +} \ No newline at end of file diff --git a/internal/provider/provider.go b/internal/provider/provider.go index b35b5301..24f8c45b 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -127,6 +127,9 @@ func New() *schema.Provider { "boundary_user": resourceUser(), "boundary_worker": resourceWorker(), }, + DataSourcesMap: map[string]*schema.Resource{ + "boundary_scope": dataSourceScope(), + }, } p.ConfigureContextFunc = providerConfigure(p) From f5fa67c1277d396f1284727e632d5c534cec9633 Mon Sep 17 00:00:00 2001 From: Grant Orchard Date: Wed, 11 Oct 2023 17:39:07 +1100 Subject: [PATCH 2/2] add details on the global scope to docs --- docs/data-sources/scope.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/data-sources/scope.md b/docs/data-sources/scope.md index 75321898..76e4f733 100644 --- a/docs/data-sources/scope.md +++ b/docs/data-sources/scope.md @@ -9,6 +9,7 @@ description: |- # boundary_scope (Data Source) The scope data source allows you to discover an existing Boundary scope by name. +Please note that the Global scope will always have an id of "global", and does not need to be discovered with this data source. ## Example Usage