Skip to content

Commit

Permalink
DXCDT-364: Add organization data source (#475)
Browse files Browse the repository at this point in the history
  • Loading branch information
sergiught authored Feb 10, 2023
1 parent d96b8af commit 3c6d3be
Show file tree
Hide file tree
Showing 6 changed files with 2,352 additions and 0 deletions.
46 changes: 46 additions & 0 deletions docs/data-sources/organization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
page_title: "Data Source: auth0_organization"
description: |-
Data source to retrieve a specific Auth0 organization by organization_id or name.
---

# Data Source: auth0_organization

Data source to retrieve a specific Auth0 organization by `organization_id` or `name`.



<!-- schema generated by tfplugindocs -->
## Schema

### Optional

- `name` (String) The name of the organization. If not provided, `organization_id` must be set. For performance, it is advised to use the `organization_id` as a lookup if possible.
- `organization_id` (String) The ID of the organization. If not provided, `name` must be set.

### Read-Only

- `branding` (List of Object) Defines how to style the login pages. (see [below for nested schema](#nestedatt--branding))
- `connections` (Set of Object) (see [below for nested schema](#nestedatt--connections))
- `display_name` (String) Friendly name of this organization.
- `id` (String) The ID of this resource.
- `metadata` (Map of String) Metadata associated with the organization. Maximum of 10 metadata properties allowed.

<a id="nestedatt--branding"></a>
### Nested Schema for `branding`

Read-Only:

- `colors` (Map of String)
- `logo_url` (String)


<a id="nestedatt--connections"></a>
### Nested Schema for `connections`

Read-Only:

- `assign_membership_on_login` (Boolean)
- `connection_id` (String)


166 changes: 166 additions & 0 deletions internal/auth0/organization/data_source.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package organization

import (
"context"
"net/http"

"github.com/auth0/go-auth0/management"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

internalSchema "github.com/auth0/terraform-provider-auth0/internal/schema"
)

// NewDataSource will return a new auth0_organization data source.
func NewDataSource() *schema.Resource {
return &schema.Resource{
ReadContext: readOrganizationForDataSource,
Description: "Data source to retrieve a specific Auth0 organization by `organization_id` or `name`.",
Schema: dataSourceSchema(),
}
}

func dataSourceSchema() map[string]*schema.Schema {
dataSourceSchema := internalSchema.TransformResourceToDataSource(NewResource().Schema)
dataSourceSchema["organization_id"] = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Description: "The ID of the organization. If not provided, `name` must be set.",
AtLeastOneOf: []string{"organization_id", "name"},
}

internalSchema.SetExistingAttributesAsOptional(dataSourceSchema, "name")
dataSourceSchema["name"].Description = "The name of the organization. " +
"If not provided, `organization_id` must be set. " +
"For performance, it is advised to use the `organization_id` as a lookup if possible."
dataSourceSchema["name"].AtLeastOneOf = []string{"organization_id", "name"}

dataSourceSchema["connections"] = &schema.Schema{
Type: schema.TypeSet,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"connection_id": {
Type: schema.TypeString,
Computed: true,
Description: "The ID of the enabled connection on the organization.",
},
"assign_membership_on_login": {
Type: schema.TypeBool,
Computed: true,
Description: "When `true`, all users that log in with this connection will be " +
"automatically granted membership in the organization. When `false`, users must be " +
"granted membership in the organization before logging in with this connection.",
},
},
},
}

return dataSourceSchema
}

func readOrganizationForDataSource(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
api := meta.(*management.Management)
var foundOrganization *management.Organization
var err error

organizationID := data.Get("organization_id").(string)
if organizationID != "" {
foundOrganization, err = api.Organization.Read(organizationID)
if err != nil {
if mErr, ok := err.(management.Error); ok && mErr.Status() == http.StatusNotFound {
data.SetId("")
return nil
}
return diag.FromErr(err)
}
} else {
name := data.Get("name").(string)
page := 0

outerLoop:
for {
organizations, err := api.Organization.List(management.Page(page), management.PerPage(100))
if err != nil {
return diag.FromErr(err)
}

for _, organization := range organizations.Organizations {
if organization.GetName() == name {
foundOrganization = organization
break outerLoop
}
}

if !organizations.HasNext() {
break
}

page++
}

if foundOrganization == nil {
return diag.Errorf("No organization found with \"name\" = %q", name)
}
}

data.SetId(foundOrganization.GetID())

result := multierror.Append(
data.Set("name", foundOrganization.GetName()),
data.Set("display_name", foundOrganization.GetDisplayName()),
data.Set("branding", flattenOrganizationBranding(foundOrganization.GetBranding())),
data.Set("metadata", foundOrganization.GetMetadata()),
)

foundConnections, err := fetchAllOrganizationConnections(api, foundOrganization.GetID())
if err != nil {
return diag.FromErr(err)
}

result = multierror.Append(
result,
data.Set("connections", flattenOrganizationConnections(foundConnections)),
)

return diag.FromErr(result.ErrorOrNil())
}

func fetchAllOrganizationConnections(api *management.Management, organizationID string) ([]*management.OrganizationConnection, error) {
var foundConnections []*management.OrganizationConnection
var page int

for {
connections, err := api.Organization.Connections(organizationID, management.Page(page), management.PerPage(100))
if err != nil {
return nil, err
}

foundConnections = append(foundConnections, connections.OrganizationConnections...)

if !connections.HasNext() {
break
}

page++
}

return foundConnections, nil
}

func flattenOrganizationConnections(connections []*management.OrganizationConnection) []interface{} {
if connections == nil {
return nil
}

result := make([]interface{}, len(connections))
for index, connection := range connections {
result[index] = map[string]interface{}{
"connection_id": connection.GetConnectionID(),
"assign_membership_on_login": connection.GetAssignMembershipOnLogin(),
}
}

return result
}
123 changes: 123 additions & 0 deletions internal/auth0/organization/data_source_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package organization_test

import (
"fmt"
"regexp"
"strings"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"

"github.com/auth0/terraform-provider-auth0/internal/provider"
"github.com/auth0/terraform-provider-auth0/internal/recorder"
"github.com/auth0/terraform-provider-auth0/internal/template"
)

const testAccGivenAnOrganizationWithConnectionsAndMembers = `
resource "auth0_connection" "my_connection" {
name = "Acceptance-Test-Connection-{{.testName}}"
strategy = "auth0"
}
resource "auth0_organization" "my_organization" {
depends_on = [auth0_connection.my_connection]
name = "test-{{.testName}}"
display_name = "Acme Inc. {{.testName}}"
}
resource "auth0_organization_connection" "my_org_conn" {
depends_on = [auth0_organization.my_organization]
organization_id = auth0_organization.my_organization.id
connection_id = auth0_connection.my_connection.id
}
`

const testAccDataSourceOrganizationConfigByName = testAccGivenAnOrganizationWithConnectionsAndMembers + `
data "auth0_organization" "test" {
name = "test-{{.testName}}"
}
`

const testAccDataSourceOrganizationConfigByID = testAccGivenAnOrganizationWithConnectionsAndMembers + `
data "auth0_organization" "test" {
organization_id = auth0_organization.my_organization.id
}
`

func TestAccDataSourceOrganizationRequiredArguments(t *testing.T) {
resource.Test(t, resource.TestCase{
ProviderFactories: provider.TestFactories(nil),
Steps: []resource.TestStep{
{
Config: `data "auth0_organization" "test" { }`,
ExpectError: regexp.MustCompile("one of `name,organization_id` must be specified"),
},
},
})
}

func TestAccDataSourceOrganizationByName(t *testing.T) {
httpRecorder := recorder.New(t)
testName := strings.ToLower(t.Name())

resource.Test(t, resource.TestCase{
ProviderFactories: provider.TestFactories(httpRecorder),
PreventPostDestroyRefresh: true,
Steps: []resource.TestStep{
{
Config: template.ParseTestName(testAccGivenAnOrganizationWithConnectionsAndMembers, testName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("auth0_connection.my_connection", "name", fmt.Sprintf("Acceptance-Test-Connection-%s", testName)),
resource.TestCheckResourceAttr("auth0_organization.my_organization", "name", fmt.Sprintf("test-%s", testName)),
resource.TestCheckResourceAttrSet("auth0_organization_connection.my_org_conn", "connection_id"),
resource.TestCheckResourceAttrSet("auth0_organization_connection.my_org_conn", "organization_id"),
resource.TestCheckResourceAttr("auth0_organization_connection.my_org_conn", "name", fmt.Sprintf("Acceptance-Test-Connection-%s", testName)),
resource.TestCheckResourceAttr("auth0_organization_connection.my_org_conn", "strategy", "auth0"),
),
},
{
Config: template.ParseTestName(testAccDataSourceOrganizationConfigByName, testName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet("data.auth0_organization.test", "id"),
resource.TestCheckResourceAttr("data.auth0_organization.test", "name", fmt.Sprintf("test-%s", testName)),
resource.TestCheckResourceAttr("data.auth0_organization.test", "connections.#", "1"),
resource.TestCheckResourceAttrSet("data.auth0_organization.test", "connections.0.connection_id"),
),
},
},
})
}

func TestAccDataSourceOrganizationByID(t *testing.T) {
httpRecorder := recorder.New(t)
testName := strings.ToLower(t.Name())

resource.Test(t, resource.TestCase{
ProviderFactories: provider.TestFactories(httpRecorder),
PreventPostDestroyRefresh: true,
Steps: []resource.TestStep{
{
Config: template.ParseTestName(testAccGivenAnOrganizationWithConnectionsAndMembers, testName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("auth0_connection.my_connection", "name", fmt.Sprintf("Acceptance-Test-Connection-%s", testName)),
resource.TestCheckResourceAttr("auth0_organization.my_organization", "name", fmt.Sprintf("test-%s", testName)),
resource.TestCheckResourceAttrSet("auth0_organization_connection.my_org_conn", "connection_id"),
resource.TestCheckResourceAttrSet("auth0_organization_connection.my_org_conn", "organization_id"),
resource.TestCheckResourceAttr("auth0_organization_connection.my_org_conn", "name", fmt.Sprintf("Acceptance-Test-Connection-%s", testName)),
resource.TestCheckResourceAttr("auth0_organization_connection.my_org_conn", "strategy", "auth0"),
),
},
{
Config: template.ParseTestName(testAccDataSourceOrganizationConfigByID, testName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet("data.auth0_organization.test", "id"),
resource.TestCheckResourceAttr("data.auth0_organization.test", "name", fmt.Sprintf("test-%s", testName)),
resource.TestCheckResourceAttr("data.auth0_organization.test", "connections.#", "1"),
resource.TestCheckResourceAttrSet("data.auth0_organization.test", "connections.0.connection_id"),
),
},
},
})
}
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func New() *schema.Provider {
"auth0_client": client.NewDataSource(),
"auth0_global_client": client.NewGlobalDataSource(),
"auth0_connection": connection.NewDataSource(),
"auth0_organization": organization.NewDataSource(),
"auth0_resource_server": resourceserver.NewDataSource(),
"auth0_tenant": newDataTenant(),
},
Expand Down
Loading

0 comments on commit 3c6d3be

Please sign in to comment.