Skip to content

Commit 8d6233d

Browse files
committed
feat: support auth0_organization_discovery_domains resource
1 parent 0a73ae7 commit 8d6233d

8 files changed

Lines changed: 5545 additions & 0 deletions

File tree

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
page_title: "Resource: auth0_organization_discovery_domains"
3+
description: |-
4+
With this resource, you can manage discovery domains on an organization.
5+
---
6+
7+
# Resource: auth0_organization_discovery_domains
8+
9+
With this resource, you can manage discovery domains on an organization.
10+
11+
12+
13+
<!-- schema generated by tfplugindocs -->
14+
## Schema
15+
16+
### Required
17+
18+
- `discovery_domains` (Block Set, Min: 1) Discovery domains that are configured for the organization. (see [below for nested schema](#nestedblock--discovery_domains))
19+
- `organization_id` (String) ID of the organization on which to manage the discovery domains.
20+
21+
### Read-Only
22+
23+
- `id` (String) The ID of this resource.
24+
25+
<a id="nestedblock--discovery_domains"></a>
26+
### Nested Schema for `discovery_domains`
27+
28+
Required:
29+
30+
- `domain` (String) The domain name for organization discovery.
31+
- `status` (String) Verification status. Must be either 'pending' or 'verified'.
32+
33+
Read-Only:
34+
35+
- `id` (String) The ID of the discovery domain.
36+
- `verification_host` (String) The full domain where the TXT record should be added.
37+
- `verification_txt` (String) TXT record value for domain verification.
38+
39+

internal/auth0/organization/expand.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,23 @@ func expandOrganizationDiscoveryDomain(data *schema.ResourceData) *management.Or
9090
// Note: ID, VerificationTXT, and VerificationHost are read-only and should not be sent to the API.
9191
}
9292
}
93+
94+
func expandOrganizationDiscoveryDomainFromConfig(domainCfg cty.Value) *management.OrganizationDiscoveryDomain {
95+
return &management.OrganizationDiscoveryDomain{
96+
Domain: value.String(domainCfg.GetAttr("domain")),
97+
Status: value.String(domainCfg.GetAttr("status")),
98+
// Note: ID, VerificationTXT, and VerificationHost are read-only and should not be sent to the API.
99+
}
100+
}
101+
102+
func expandOrganizationDiscoveryDomains(cfg cty.Value) []*management.OrganizationDiscoveryDomain {
103+
domains := make([]*management.OrganizationDiscoveryDomain, 0)
104+
105+
cfg.ForEachElement(func(_ cty.Value, domainCfg cty.Value) (stop bool) {
106+
domains = append(domains, expandOrganizationDiscoveryDomainFromConfig(domainCfg))
107+
108+
return stop
109+
})
110+
111+
return domains
112+
}

internal/auth0/organization/flatten.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,22 @@ func flattenOrganizationDiscoveryDomain(data *schema.ResourceData, discoveryDoma
142142

143143
return result.ErrorOrNil()
144144
}
145+
146+
func flattenOrganizationDiscoveryDomains(data *schema.ResourceData, domains []*management.OrganizationDiscoveryDomain) error {
147+
if len(domains) == 0 {
148+
return data.Set("discovery_domains", []interface{}{})
149+
}
150+
151+
var enabledDomains []interface{}
152+
for _, domain := range domains {
153+
enabledDomains = append(enabledDomains, map[string]interface{}{
154+
"id": domain.GetID(),
155+
"domain": domain.GetDomain(),
156+
"status": domain.GetStatus(),
157+
"verification_txt": domain.GetVerificationTXT(),
158+
"verification_host": domain.GetVerificationHost(),
159+
})
160+
}
161+
162+
return data.Set("discovery_domains", enabledDomains)
163+
}
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
package organization
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/auth0/go-auth0/management"
8+
"github.com/google/go-cmp/cmp"
9+
"github.com/hashicorp/go-multierror"
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
12+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
13+
14+
"github.com/auth0/terraform-provider-auth0/internal/config"
15+
internalError "github.com/auth0/terraform-provider-auth0/internal/error"
16+
"github.com/auth0/terraform-provider-auth0/internal/value"
17+
)
18+
19+
// NewDiscoveryDomainsResource will return a new auth0_organization_discovery_domains (1:many) resource.
20+
func NewDiscoveryDomainsResource() *schema.Resource {
21+
return &schema.Resource{
22+
Schema: map[string]*schema.Schema{
23+
"organization_id": {
24+
Type: schema.TypeString,
25+
Required: true,
26+
ForceNew: true,
27+
Description: "ID of the organization on which to manage the discovery domains.",
28+
},
29+
"discovery_domains": {
30+
Type: schema.TypeSet,
31+
Elem: &schema.Resource{
32+
Schema: map[string]*schema.Schema{
33+
"domain": {
34+
Type: schema.TypeString,
35+
Required: true,
36+
Description: "The domain name for organization discovery.",
37+
},
38+
"status": {
39+
Type: schema.TypeString,
40+
Required: true,
41+
ValidateFunc: validation.StringInSlice([]string{"pending", "verified"}, false),
42+
Description: "Verification status. Must be either 'pending' or 'verified'.",
43+
},
44+
"id": {
45+
Type: schema.TypeString,
46+
Computed: true,
47+
Description: "The ID of the discovery domain.",
48+
},
49+
"verification_txt": {
50+
Type: schema.TypeString,
51+
Computed: true,
52+
Description: "TXT record value for domain verification.",
53+
},
54+
"verification_host": {
55+
Type: schema.TypeString,
56+
Computed: true,
57+
Description: "The full domain where the TXT record should be added.",
58+
},
59+
},
60+
},
61+
Required: true,
62+
Description: "Discovery domains that are configured for the organization.",
63+
},
64+
},
65+
CreateContext: createOrganizationDiscoveryDomains,
66+
ReadContext: readOrganizationDiscoveryDomains,
67+
UpdateContext: updateOrganizationDiscoveryDomains,
68+
DeleteContext: deleteOrganizationDiscoveryDomains,
69+
Importer: &schema.ResourceImporter{
70+
StateContext: schema.ImportStatePassthroughContext,
71+
},
72+
Description: "With this resource, you can manage discovery domains on an organization.",
73+
}
74+
}
75+
76+
func createOrganizationDiscoveryDomains(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
77+
api := meta.(*config.Config).GetAPI()
78+
79+
organizationID := data.Get("organization_id").(string)
80+
81+
var alreadyEnabledDomains []*management.OrganizationDiscoveryDomain
82+
var checkpoint string
83+
for {
84+
var opts []management.RequestOption
85+
if checkpoint != "" {
86+
opts = append(opts, management.From(checkpoint))
87+
}
88+
opts = append(opts, management.Take(100))
89+
90+
domainList, err := api.Organization.DiscoveryDomains(
91+
ctx,
92+
organizationID,
93+
opts...,
94+
)
95+
if err != nil {
96+
return diag.FromErr(internalError.HandleAPIError(data, err))
97+
}
98+
99+
alreadyEnabledDomains = append(alreadyEnabledDomains, domainList.Domains...)
100+
101+
if !domainList.HasNext() {
102+
break
103+
}
104+
105+
checkpoint = domainList.Next
106+
}
107+
108+
data.SetId(organizationID)
109+
110+
domainsToAdd := expandOrganizationDiscoveryDomains(data.GetRawConfig().GetAttr("discovery_domains"))
111+
112+
if diagnostics := guardAgainstErasingUnwantedDiscoveryDomains(
113+
organizationID,
114+
alreadyEnabledDomains,
115+
domainsToAdd,
116+
); diagnostics.HasError() {
117+
data.SetId("")
118+
return diagnostics
119+
}
120+
121+
if len(domainsToAdd) > len(alreadyEnabledDomains) {
122+
var result *multierror.Error
123+
124+
for _, domain := range domainsToAdd {
125+
err := api.Organization.CreateDiscoveryDomain(ctx, organizationID, domain)
126+
result = multierror.Append(result, err)
127+
}
128+
129+
if result.ErrorOrNil() != nil {
130+
return diag.FromErr(result.ErrorOrNil())
131+
}
132+
}
133+
134+
return readOrganizationDiscoveryDomains(ctx, data, meta)
135+
}
136+
137+
func readOrganizationDiscoveryDomains(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
138+
api := meta.(*config.Config).GetAPI()
139+
140+
var domains []*management.OrganizationDiscoveryDomain
141+
var checkpoint string
142+
for {
143+
var opts []management.RequestOption
144+
if checkpoint != "" {
145+
opts = append(opts, management.From(checkpoint))
146+
}
147+
opts = append(opts, management.Take(100))
148+
149+
domainList, err := api.Organization.DiscoveryDomains(
150+
ctx,
151+
data.Id(),
152+
opts...,
153+
)
154+
if err != nil {
155+
return diag.FromErr(internalError.HandleAPIError(data, err))
156+
}
157+
158+
domains = append(domains, domainList.Domains...)
159+
160+
if !domainList.HasNext() {
161+
break
162+
}
163+
164+
checkpoint = domainList.Next
165+
}
166+
167+
result := multierror.Append(
168+
data.Set("organization_id", data.Id()),
169+
flattenOrganizationDiscoveryDomains(data, domains),
170+
)
171+
172+
return diag.FromErr(result.ErrorOrNil())
173+
}
174+
175+
func updateOrganizationDiscoveryDomains(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
176+
api := meta.(*config.Config).GetAPI()
177+
178+
organizationID := data.Id()
179+
180+
// We are using the data from expandOrganizationDiscoveryDomains and not value.Difference to preserve the nilness of the data.
181+
domains := expandOrganizationDiscoveryDomains(data.GetRawConfig().GetAttr("discovery_domains"))
182+
domainMap := make(map[string]*management.OrganizationDiscoveryDomain)
183+
for _, domain := range domains {
184+
domainMap[domain.GetDomain()] = domain
185+
}
186+
187+
toAdd, toRemove := value.Difference(data, "discovery_domains")
188+
var result *multierror.Error
189+
190+
for _, rmDomain := range toRemove {
191+
domain := rmDomain.(map[string]interface{})
192+
domainID := domain["id"].(string)
193+
194+
err := api.Organization.DeleteDiscoveryDomain(ctx, organizationID, domainID)
195+
if internalError.IsStatusNotFound(err) {
196+
err = nil
197+
}
198+
199+
result = multierror.Append(result, err)
200+
}
201+
202+
for _, addDomain := range toAdd {
203+
domain := addDomain.(map[string]interface{})
204+
domainName := domain["domain"].(string)
205+
206+
err := api.Organization.CreateDiscoveryDomain(ctx, organizationID, domainMap[domainName])
207+
result = multierror.Append(result, err)
208+
}
209+
210+
if result.ErrorOrNil() != nil {
211+
return diag.FromErr(result.ErrorOrNil())
212+
}
213+
214+
return readOrganizationDiscoveryDomains(ctx, data, meta)
215+
}
216+
217+
func deleteOrganizationDiscoveryDomains(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
218+
api := meta.(*config.Config).GetAPI()
219+
220+
// Fetch all current discovery domains to get their IDs.
221+
var domains []*management.OrganizationDiscoveryDomain
222+
var checkpoint string
223+
for {
224+
var opts []management.RequestOption
225+
if checkpoint != "" {
226+
opts = append(opts, management.From(checkpoint))
227+
}
228+
opts = append(opts, management.Take(100))
229+
230+
domainList, err := api.Organization.DiscoveryDomains(
231+
ctx,
232+
data.Id(),
233+
opts...,
234+
)
235+
if err != nil {
236+
return diag.FromErr(internalError.HandleAPIError(data, err))
237+
}
238+
239+
domains = append(domains, domainList.Domains...)
240+
241+
if !domainList.HasNext() {
242+
break
243+
}
244+
245+
checkpoint = domainList.Next
246+
}
247+
248+
var result *multierror.Error
249+
250+
for _, domain := range domains {
251+
err := api.Organization.DeleteDiscoveryDomain(ctx, data.Id(), domain.GetID())
252+
if internalError.IsStatusNotFound(err) {
253+
err = nil
254+
}
255+
256+
result = multierror.Append(result, err)
257+
}
258+
259+
return diag.FromErr(result.ErrorOrNil())
260+
}
261+
262+
func guardAgainstErasingUnwantedDiscoveryDomains(
263+
organizationID string,
264+
alreadyEnabled []*management.OrganizationDiscoveryDomain,
265+
desired []*management.OrganizationDiscoveryDomain,
266+
) diag.Diagnostics {
267+
if len(alreadyEnabled) == 0 {
268+
return nil
269+
}
270+
271+
alreadyEnabledDomains := make([]string, 0)
272+
for _, domain := range alreadyEnabled {
273+
alreadyEnabledDomains = append(alreadyEnabledDomains, domain.GetDomain())
274+
}
275+
276+
desiredDomains := make([]string, 0)
277+
for _, domain := range desired {
278+
desiredDomains = append(desiredDomains, domain.GetDomain())
279+
}
280+
281+
if !cmp.Equal(alreadyEnabledDomains, desiredDomains) {
282+
return diag.Diagnostics{
283+
diag.Diagnostic{
284+
Severity: diag.Error,
285+
Summary: "Organization with non empty enabled discovery domains",
286+
Detail: fmt.Sprintf("Detected an organization (%s) with already enabled discovery domains. Import the "+
287+
"auth0_organization_discovery_domains resource instead of creating it.", organizationID),
288+
},
289+
}
290+
}
291+
292+
return nil
293+
}

0 commit comments

Comments
 (0)