Skip to content

Commit

Permalink
Merge pull request #118 from puppetlabs/custom-domain-self-managed-ce…
Browse files Browse the repository at this point in the history
…rtificates

Add support for creating external resources associated with self-managed certificates
  • Loading branch information
sergiught authored Apr 12, 2022
2 parents ea56d99 + 8c3db6c commit f24d568
Show file tree
Hide file tree
Showing 35 changed files with 598 additions and 84 deletions.
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.
if err := d.Set("cname_api_key", customDomainVerification.CNAMEAPIKey); err != nil {
return resource.NonRetryableError(err)
}

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

return diag.FromErr(err)
return readCustomDomainVerification(ctx, d, m)
}

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

0 comments on commit f24d568

Please sign in to comment.