Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added domain resource #530

Merged
merged 1 commit into from
Jul 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -196,6 +197,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