Skip to content

Commit

Permalink
Added domain resource (#530)
Browse files Browse the repository at this point in the history
  • Loading branch information
bogdanprodan-okta authored Jul 14, 2021
1 parent 15f311f commit 920bfbe
Show file tree
Hide file tree
Showing 7 changed files with 359 additions and 0 deletions.
4 changes: 4 additions & 0 deletions examples/okta_domain/basic.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
resource "okta_domain" "test" {
name = "example.com"
verify = false
}
2 changes: 2 additions & 0 deletions okta/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const (
authServerScope = "okta_auth_server_scope"
behavior = "okta_behavior"
behaviors = "okta_behaviors"
domain = "okta_domain"
eventHook = "okta_event_hook"
factor = "okta_factor"
factorTotp = "okta_factor_totp"
Expand Down Expand Up @@ -197,6 +198,7 @@ func Provider() *schema.Provider {
authServerPolicy: resourceAuthServerPolicy(),
authServerPolicyRule: resourceAuthServerPolicyRule(),
authServerScope: resourceAuthServerScope(),
domain: resourceDomain(),
eventHook: resourceEventHook(),
factor: resourceFactor(),
factorTotp: resourceFactorTOTP(),
Expand Down
168 changes: 168 additions & 0 deletions okta/resource_okta_domain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package okta

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/okta/terraform-provider-okta/sdk"
)

func resourceDomain() *schema.Resource {
return &schema.Resource{
CreateContext: resourceDomainCreate,
ReadContext: resourceDomainRead,
UpdateContext: resourceDomainUpdate,
DeleteContext: resourceDomainDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
Description: "Custom Domain name",
ForceNew: true,
},
"verify": {
Type: schema.TypeBool,
Optional: true,
Description: "Indicates whether the domain should be verified during creation",
Default: false,
},
"validation_status": {
Type: schema.TypeString,
Computed: true,
Description: "Status of the domain",
},
"dns_records": {
Type: schema.TypeList,
Computed: true,
Description: "TXT and CNAME records to be registered for the Domain",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"expiration": {
Type: schema.TypeString,
Computed: true,
Description: "TXT record expiration",
},
"fqdn": {
Type: schema.TypeString,
Computed: true,
Description: "DNS record name",
},
"record_type": {
Type: schema.TypeString,
Computed: true,
Description: "Record type can be TXT or CNAME",
},
"values": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Description: "DNS verification value",
},
},
},
},
},
}
}

func resourceDomainCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
domain, _, err := getSupplementFromMetadata(m).CreateDomain(ctx, buildDomain(d))
if err != nil {
return diag.Errorf("failed to create domain: %v", err)
}
d.SetId(domain.ID)
if d.Get("verify").(bool) {
_, _, err := getSupplementFromMetadata(m).VerifyDomain(ctx, domain.ID)
if err != nil {
return diag.Errorf("failed to verify domain: %v", err)
}
}
return resourceDomainRead(ctx, d, m)
}

func resourceDomainRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
domain, resp, err := getSupplementFromMetadata(m).GetDomain(ctx, d.Id())
if err := suppressErrorOn404(resp, err); err != nil {
return diag.Errorf("failed to get domain: %v", err)
}
if domain == nil {
d.SetId("")
return nil
}
vd, err := validateDomain(ctx, d, m, domain.ValidationStatus)
if err != nil {
return diag.FromErr(err)
}
if vd != nil {
_ = d.Set("validation_status", vd.ValidationStatus)
} else {
_ = d.Set("validation_status", domain.ValidationStatus)
}
arr := make([]map[string]interface{}, len(domain.DNSRecords))
for i := range domain.DNSRecords {
arr[i] = map[string]interface{}{
"expiration": domain.DNSRecords[i].Expiration,
"fqdn": domain.DNSRecords[i].Fqdn,
"record_type": domain.DNSRecords[i].RecordType,
"values": convertStringArrToInterface(domain.DNSRecords[i].Values),
}
}
err = setNonPrimitives(d, map[string]interface{}{"dns_records": arr})
if err != nil {
return diag.Errorf("failed to set OAuth application properties: %v", err)
}
if domain.ValidationStatus == "IN_PROGRESS" || domain.ValidationStatus == "VERIFIED" || domain.ValidationStatus == "COMPLETED" {
return nil
}
if d.Get("verify").(bool) {
_, _, err := getSupplementFromMetadata(m).VerifyDomain(ctx, d.Id())
if err != nil {
return diag.Errorf("failed to verify domain: %v", err)
}
}

return nil
}

func resourceDomainDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
logger(m).Info("deleting domain", "id", d.Id())
_, err := getSupplementFromMetadata(m).DeleteDomain(ctx, d.Id())
if err != nil {
return diag.Errorf("failed to delete domain: %v", err)
}
return nil
}

func resourceDomainUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
_, err := validateDomain(ctx, d, m, d.Get("validation_status").(string))
if err != nil {
return diag.FromErr(err)
}
return resourceDomainRead(ctx, d, m)
}

func validateDomain(ctx context.Context, d *schema.ResourceData, m interface{}, validationStatus string) (*sdk.Domain, error) {
if validationStatus == "IN_PROGRESS" || validationStatus == "VERIFIED" || validationStatus == "COMPLETED" {
return nil, nil
}
if !d.Get("verify").(bool) {
return nil, nil
}
domain, _, err := getSupplementFromMetadata(m).VerifyDomain(ctx, d.Id())
if err != nil {
return nil, fmt.Errorf("failed to verify domain: %v", err)
}
return domain, nil
}

func buildDomain(d *schema.ResourceData) sdk.Domain {
return sdk.Domain{
Domain: d.Get("name").(string),
CertificateSourceType: "MANUAL",
}
}
41 changes: 41 additions & 0 deletions okta/resource_okta_domain_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package okta

import (
"context"
"fmt"
"testing"

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

func TestAccOktaDomain(t *testing.T) {
ri := acctest.RandInt()
mgr := newFixtureManager(domain)
config := mgr.GetFixtures("basic.tf", ri, t)
resourceName := fmt.Sprintf("%s.test", domain)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProvidersFactories,
CheckDestroy: createCheckResourceDestroy(domain, domainExists),
Steps: []resource.TestStep{
{
Config: config,
Check: resource.ComposeTestCheckFunc(
ensureResourceExists(resourceName, domainExists),
resource.TestCheckResourceAttr(resourceName, "name", "example.com"),
resource.TestCheckResourceAttr(resourceName, "dns_records.#", "2"),
),
},
},
})
}

func domainExists(id string) (bool, error) {
domain, resp, err := getSupplementFromMetadata(testAccProvider.Meta()).GetDomain(context.Background(), id)
if err := suppressErrorOn404(resp, err); err != nil {
return false, err
}
return domain != nil, nil
}
93 changes: 93 additions & 0 deletions sdk/domain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package sdk

import (
"context"
"fmt"
"net/http"
"time"

"github.com/okta/okta-sdk-golang/v2/okta"
)

type Domain struct {
ID string `json:"id,omitempty"`
Domain string `json:"domain"`
CertificateSourceType string `json:"certificateSourceType"`
ValidationStatus string `json:"validationStatus,omitempty"`
DNSRecords []struct {
Expiration string `json:"expiration",omitempty"`
Fqdn string `json:"fqdn,omitempty"`
Values []string `json:"values,omitempty"`
RecordType string `json:"recordType,omitempty"`
} `json:"dnsRecords,omitempty"`
PublicCertificate struct {
Subject string `json:"subject,omitempty"`
Fingerprint string `json:"fingerprint,omitempty"`
Expiration time.Time `json:"expiration,omitempty"`
} `json:"publicCertificate,omitempty"`
}

type Certificate struct {
Type string `json:"type"`
PrivateKey string `json:"privateKey"`
Certificate string `json:"certificate"`
CertificateChain string `json:"certificateChain,omitempty"`
}

func (m *ApiSupplement) CreateDomain(ctx context.Context, body Domain) (*Domain, *okta.Response, error) {
url := "/api/v1/domains"
req, err := m.RequestExecutor.
WithAccept("application/json").
WithContentType("application/json").
NewRequest(http.MethodPost, url, body)
if err != nil {
return nil, nil, err
}
var domain Domain
resp, err := m.RequestExecutor.Do(ctx, req, &domain)
if err != nil {
return nil, resp, err
}
return &domain, resp, nil
}

func (m *ApiSupplement) VerifyDomain(ctx context.Context, id string) (*Domain, *okta.Response, error) {
url := fmt.Sprintf("/api/v1/domains/%s/verify", id)
req, err := m.RequestExecutor.NewRequest(http.MethodPost, url, nil)
if err != nil {
return nil, nil, err
}
var domain Domain
resp, err := m.RequestExecutor.Do(ctx, req, &domain)
if err != nil {
return nil, resp, err
}
return &domain, resp, nil
}

func (m *ApiSupplement) GetDomain(ctx context.Context, id string) (*Domain, *okta.Response, error) {
url := fmt.Sprintf("/api/v1/domains/%v", id)
req, err := m.RequestExecutor.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, nil, err
}
var domain Domain
resp, err := m.RequestExecutor.Do(ctx, req, &domain)
if err != nil {
return nil, resp, err
}
return &domain, resp, nil
}

func (m *ApiSupplement) DeleteDomain(ctx context.Context, id string) (*okta.Response, error) {
url := fmt.Sprintf("/api/v1/domains/%v", id)
req, err := m.RequestExecutor.NewRequest("DELETE", url, nil)
if err != nil {
return nil, err
}
resp, err := m.RequestExecutor.Do(ctx, req, nil)
if err != nil {
return resp, err
}
return resp, nil
}
48 changes: 48 additions & 0 deletions website/docs/r/domain.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
layout: 'okta'
page_title: 'Okta: okta_domain'
sidebar_current: 'docs-okta-resource-domain'
description: |-
Manages custom domain for your organization.
---

# okta_domain

Manages custom domain for your organization.

## Example Usage

```hcl
resource "okta_domain" "example" {
name = "www.example.com"
verify = true
}
```

## Argument Reference

The following arguments are supported:

- `name` - (Required) Custom Domain name.

- `verify` - (Optional) Indicates whether the domain should be verified.

## Attributes Reference

- `id` - Domain ID

- `validation_status` - Status of the domain.

- `dns_records` - TXT and CNAME records to be registered for the Domain.
- `expiration` - TXT record expiration.
- `fqdn` - DNS record name.
- `record_type` - Record type can be TXT or CNAME.
- `values` - DNS verification value

## Import

Okta Admin Role Targets can be imported via the Okta ID.

```
$ terraform import okta_domain.example <domain_id>
```
3 changes: 3 additions & 0 deletions website/okta.erb
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@
<li<%= sidebar_current("docs-okta-resource-auth-server-scope") %>>
<a href="/docs/providers/okta/r/auth_server_scope.html">okta_auth_server_scope</a>
</li>
<li<%= sidebar_current("docs-okta-resource-domain") %>>
<a href="/docs/providers/okta/r/domain.html">okta_domain</a>
</li>
<li<%= sidebar_current("docs-okta-resource-event-hook") %>>
<a href="/docs/providers/okta/r/event_hook.html">okta_event_hook</a>
</li>
Expand Down

0 comments on commit 920bfbe

Please sign in to comment.