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

Add support for creating external resources associated with self-managed certificates #118

Merged
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: 2 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
run: make lint

- name: Start docker containers
run: docker-compose up -d --build
run: make dev-up

- name: Run tests
run: make test
Expand All @@ -38,4 +38,4 @@ jobs:

- name: Stop docker containers
if: always()
run: docker-compose down
run: make dev-down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ test-sweep: ## Clean up test tenant
dev-up: ## Bootstrap the development containers
${call print, "Starting development containers"}
@docker-compose up -d
@sleep 2 # Let's make sure the container is fully up.

dev-down: ## Bring down the development containers
${call print, "Bringing the development containers down"}
Expand Down
106 changes: 106 additions & 0 deletions auth0/internal/wiremock/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package wiremock

import (
"context"
"fmt"
"log"
"net/http"
"net/url"

"github.com/auth0/go-auth0/management"
"github.com/hashicorp/go-multierror"
)

const (
resetMappingsURI = "/__admin/mappings/reset"
resetScenariosURI = "/__admin/scenarios/reset"
)

// Client provides communication with the
// admin API of a WireMock server instance.
type Client struct {
baseURL *url.URL
httpClient *http.Client
}

// NewClient constructs a WireMock httpClient
// that communicates with the WireMock
// server on the given baseURL and port.
func NewClient(host string) *Client {
return &Client{
baseURL: &url.URL{
Scheme: "http",
Host: host,
},
httpClient: http.DefaultClient,
}
}

// NewManagementAPIClient creates a Go Auth0 management API httpClient configured to
// talk to this WireMock server instance.
func (c *Client) NewManagementAPIClient() (*management.Management, error) {
return management.New(c.baseURL.String(), management.WithInsecure())
}

// Reset calls ResetAllMappings and ResetAllScenarios
// to provide us with a clean WireMock state.
func (c *Client) Reset(ctx context.Context) error {
return multierror.Append(
c.ResetAllMappings(ctx),
c.ResetAllScenarios(ctx),
).ErrorOrNil()
}

// ResetAllMappings causes the WireMock server to remove transient mappings and
// reload static mappings from disk.
func (c *Client) ResetAllMappings(ctx context.Context) error {
response, err := c.doRequest(ctx, http.MethodPost, resetMappingsURI)
if err != nil {
return fmt.Errorf("failed to reset WireMock mappings: %w", err)
}
defer func() {
if err := response.Body.Close(); err != nil {
log.Printf("failed to close response body when resetting mappings: %+v", err)
}
}()

if response.StatusCode != http.StatusOK {
return fmt.Errorf("the WireMock server returned %s when trying to reset mappings", response.Status)
}

return nil
}

// ResetAllScenarios causes the WireMock server to put all currently defined
// mappings' scenarios back into their initial state.
func (c *Client) ResetAllScenarios(ctx context.Context) error {
response, err := c.doRequest(ctx, http.MethodPost, resetScenariosURI)
if err != nil {
return fmt.Errorf("failed to reset WireMock scenarios: %w", err)
}
defer func() {
if err := response.Body.Close(); err != nil {
log.Printf("failed to close response body when resetting scenarios: %+v", err)
}
}()

if response.StatusCode != http.StatusOK {
return fmt.Errorf("the WireMock server returned %s when trying to reset scenarios", response.Status)
}

return nil
}

func (c *Client) doRequest(ctx context.Context, method, path string) (*http.Response, error) {
request, err := http.NewRequestWithContext(ctx, method, c.baseURL.String()+path, nil)
if err != nil {
return nil, fmt.Errorf("failed to create HTTP request: %w", err)
}

response, err := c.httpClient.Do(request)
if err != nil {
return nil, fmt.Errorf("failed to send HTTP request to WireMock: %w", err)
}

return response, nil
}
42 changes: 33 additions & 9 deletions auth0/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,54 @@ import (
"os"
"sort"
"strings"
"sync"
"testing"

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

"github.com/auth0/terraform-provider-auth0/auth0/internal/wiremock"
)

var testProviderFactories = map[string]func() (*schema.Provider, error){
"auth0": func() (*schema.Provider, error) {
return Provider(), nil
},
}
const wireMockURL = "localhost:8080"

func providerWithMockedAPI() *schema.Provider {
const wiremockHost = "localhost:8080"
var (
testProviderFactories = map[string]func() (*schema.Provider, error){
"auth0": func() (*schema.Provider, error) {
return Provider(), nil
},
}

testProviderFactoriesWithMockedAPI = map[string]func() (*schema.Provider, error){
"auth0": func() (*schema.Provider, error) {
return providerWithMockedAPI(), nil
},
}

wireMockReset sync.Once
)

func providerWithMockedAPI() *schema.Provider {
provider := Provider()
provider.ConfigureContextFunc = func(_ context.Context, data *schema.ResourceData) (interface{}, diag.Diagnostics) {
apiClient, err := management.New(wiremockHost, management.WithInsecure())
provider.ConfigureContextFunc = func(ctx context.Context, _ *schema.ResourceData) (interface{}, diag.Diagnostics) {
var err error

wireMockClient := wiremock.NewClient(wireMockURL)
wireMockReset.Do(func() {
err = wireMockClient.Reset(ctx)
})
if err != nil {
return nil, diag.FromErr(err)
}

apiClient, err := wireMockClient.NewManagementAPIClient()
if err != nil {
return nil, diag.FromErr(err)
}

return apiClient, nil
}
return provider
Expand Down
5 changes: 5 additions & 0 deletions auth0/resource_auth0_custom_domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ func newCustomDomain() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"origin_domain_name": {
Type: schema.TypeString,
Computed: true,
},
"verification_method": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -97,6 +101,7 @@ func readCustomDomain(ctx context.Context, d *schema.ResourceData, m interface{}
d.Set("type", customDomain.Type),
d.Set("primary", customDomain.Primary),
d.Set("status", customDomain.Status),
d.Set("origin_domain_name", customDomain.OriginDomainName),
)

if customDomain.Verification != nil {
Expand Down
29 changes: 25 additions & 4 deletions auth0/resource_auth0_custom_domain_verification.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

"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/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand All @@ -27,6 +28,15 @@ func newCustomDomainVerification() *schema.Resource {
Required: true,
ForceNew: true,
},
"origin_domain_name": {
Type: schema.TypeString,
Computed: true,
},
"cname_api_key": {
Type: schema.TypeString,
Computed: true,
Sensitive: true,
},
},
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(5 * time.Minute),
Expand All @@ -52,14 +62,20 @@ func createCustomDomainVerification(ctx context.Context, d *schema.ResourceData,

d.SetId(customDomainVerification.GetID())

if result := readCustomDomainVerification(ctx, d, m); result.HasError() {
return resource.NonRetryableError(fmt.Errorf("failed to read custom domain verification"))
// The cname_api_key field is only given once: when verification
// succeeds for the first time. Therefore, we set it on the resource in
// the creation routine only, and never touch it again.
impl marked this conversation as resolved.
Show resolved Hide resolved
if err := d.Set("cname_api_key", customDomainVerification.CNAMEAPIKey); err != nil {
impl marked this conversation as resolved.
Show resolved Hide resolved
return resource.NonRetryableError(err)
}

return nil
})
if err != nil {
return diag.FromErr(err)
}

return diag.FromErr(err)
return readCustomDomainVerification(ctx, d, m)
impl marked this conversation as resolved.
Show resolved Hide resolved
}

func readCustomDomainVerification(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
Expand All @@ -75,7 +91,12 @@ func readCustomDomainVerification(ctx context.Context, d *schema.ResourceData, m
return diag.FromErr(err)
}

return diag.FromErr(d.Set("custom_domain_id", customDomain.GetID()))
result := multierror.Append(
d.Set("custom_domain_id", customDomain.GetID()),
d.Set("origin_domain_name", customDomain.OriginDomainName),
)

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

func deleteCustomDomainVerification(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
Expand Down
84 changes: 74 additions & 10 deletions auth0/resource_auth0_custom_domain_verification_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,46 @@ import (
"testing"

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

func TestAccCustomDomainVerification(t *testing.T) {
func TestAccCustomDomainVerificationWithAuth0ManagedCerts(t *testing.T) {
resource.Test(t, resource.TestCase{
ProviderFactories: map[string]func() (*schema.Provider, error){
"auth0": func() (*schema.Provider, error) {
return providerWithMockedAPI(), nil
},
},
ProviderFactories: testProviderFactoriesWithMockedAPI,
Steps: []resource.TestStep{
{
Config: testAccCustomDomainVerification,
Config: testAccCustomDomainVerificationWithAuth0ManagedCerts,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("auth0_custom_domain.my_custom_domain", "domain", "terraform-provider.auth0.com"),
resource.TestCheckResourceAttr("auth0_custom_domain.my_custom_domain", "type", "auth0_managed_certs"),
resource.TestCheckResourceAttrSet("auth0_custom_domain_verification.my_custom_domain_verification", "custom_domain_id"),
// The status attribute is set to "pending_verification"
// here because Terraform has settled its state before
// attempting the custom domain verification. We need to
// refresh the state to move it along.
resource.TestCheckResourceAttr("auth0_custom_domain.my_custom_domain", "status", "pending_verification"),
resource.TestCheckResourceAttrPair(
"auth0_custom_domain.my_custom_domain", "id",
"auth0_custom_domain_verification.my_custom_domain_verification", "custom_domain_id",
),
resource.TestCheckResourceAttrSet("auth0_custom_domain_verification.my_custom_domain_verification", "origin_domain_name"),
),
},
{
Config: testAccCustomDomainVerificationWithAuth0ManagedCerts,
Check: resource.ComposeTestCheckFunc(
// By applying an identical plan, we can reconcile the
// status attribute.
resource.TestCheckResourceAttr("auth0_custom_domain.my_custom_domain", "status", "ready"),
resource.TestCheckResourceAttrPair(
"auth0_custom_domain.my_custom_domain", "origin_domain_name",
"auth0_custom_domain_verification.my_custom_domain_verification", "origin_domain_name",
),
),
},
},
})
}

const testAccCustomDomainVerification = `
const testAccCustomDomainVerificationWithAuth0ManagedCerts = `
resource "auth0_custom_domain" "my_custom_domain" {
domain = "terraform-provider.auth0.com"
type = "auth0_managed_certs"
Expand All @@ -38,3 +54,51 @@ resource "auth0_custom_domain_verification" "my_custom_domain_verification" {
timeouts { create = "15m" }
}
`

func TestAccCustomDomainVerificationWithSelfManagedCerts(t *testing.T) {
resource.Test(t, resource.TestCase{
ProviderFactories: testProviderFactoriesWithMockedAPI,
Steps: []resource.TestStep{
{
Config: testAccCustomDomainVerificationWithSelfManagedCerts,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("auth0_custom_domain.my_custom_domain", "domain", "terraform-provider.auth0.com"),
resource.TestCheckResourceAttr("auth0_custom_domain.my_custom_domain", "type", "self_managed_certs"),
resource.TestCheckResourceAttr("auth0_custom_domain.my_custom_domain", "status", "pending_verification"),
resource.TestCheckResourceAttrPair(
"auth0_custom_domain.my_custom_domain", "id",
"auth0_custom_domain_verification.my_custom_domain_verification", "custom_domain_id",
),
resource.TestCheckResourceAttrSet("auth0_custom_domain_verification.my_custom_domain_verification", "origin_domain_name"),
resource.TestCheckResourceAttrSet("auth0_custom_domain_verification.my_custom_domain_verification", "cname_api_key"),
),
},
{
Config: testAccCustomDomainVerificationWithSelfManagedCerts,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("auth0_custom_domain.my_custom_domain", "status", "ready"),
resource.TestCheckResourceAttrPair(
"auth0_custom_domain.my_custom_domain", "origin_domain_name",
"auth0_custom_domain_verification.my_custom_domain_verification", "origin_domain_name",
),
// Even though we can no longer read this from the API, it
// should remain set after refresh as we won't clear it out
// in the read operation.
resource.TestCheckResourceAttrSet("auth0_custom_domain_verification.my_custom_domain_verification", "cname_api_key"),
),
},
},
})
}

const testAccCustomDomainVerificationWithSelfManagedCerts = `
resource "auth0_custom_domain" "my_custom_domain" {
domain = "terraform-provider.auth0.com"
type = "self_managed_certs"
}

resource "auth0_custom_domain_verification" "my_custom_domain_verification" {
custom_domain_id = auth0_custom_domain.my_custom_domain.id
timeouts { create = "15m" }
}
`
Loading