Skip to content

Commit

Permalink
apps: Deprecate domains list in favor of domain block. (#572)
Browse files Browse the repository at this point in the history
* Replace domains string array and add image attribute

* Fix wildcard typo in flatten domain

* Clean up comments.

* apps: Deprecate domains list in favor of domain block.

Co-authored-by: Andrew Craven <andrew@epworth-consulting.co.uk>
  • Loading branch information
andrewsomething and acraven authored Feb 2, 2021
1 parent b7ffd57 commit 9d98694
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 11 deletions.
118 changes: 110 additions & 8 deletions digitalocean/app_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

func appSpecSchema() map[string]*schema.Schema {
return map[string]*schema.Schema{
// appSpecSchema returns map[string]*schema.Schema for the App Specification.
// Set isResource to true in order to return a schema with additional attributes
// appropriate for a resource or false for one used with a data-source.
func appSpecSchema(isResource bool) map[string]*schema.Schema {
spec := map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
Expand All @@ -21,10 +24,18 @@ func appSpecSchema() map[string]*schema.Schema {
Optional: true,
Description: "The slug for the DigitalOcean data center region hosting the app",
},
"domains": {
Type: schema.TypeSet,
"domain": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Computed: true,
Elem: appSpecDomainSchema(),
},
"domains": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Deprecated: "This attribute has been replaced by `domain` which supports additional functionality.",
},
"service": {
Type: schema.TypeList,
Expand Down Expand Up @@ -59,6 +70,46 @@ func appSpecSchema() map[string]*schema.Schema {
Set: schema.HashResource(appSpecEnvSchema()),
},
}

if isResource {
spec["domain"].ConflictsWith = []string{"spec.0.domains"}
}

return spec
}

func appSpecDomainSchema() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
Description: "The hostname for the domain.",
},
"type": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringInSlice([]string{
"DEFAULT",
"PRIMARY",
"ALIAS",
}, false),
Description: "The type of the domain.",
},
"wildcard": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
Description: "Indicates whether the domain includes all sub-domains, in addition to the given domain.",
},
"zone": {
Type: schema.TypeString,
Optional: true,
Description: "If the domain uses DigitalOcean DNS and you would like App Platform to automatically manage it for you, set this to the name of the domain on your account.",
},
},
}
}

func appSpecGitSourceSchema() map[string]*schema.Schema {
Expand Down Expand Up @@ -529,7 +580,6 @@ func expandAppSpec(config []interface{}) *godo.AppSpec {
appSpec := &godo.AppSpec{
Name: appSpecConfig["name"].(string),
Region: appSpecConfig["region"].(string),
Domains: expandAppDomainSpec(appSpecConfig["domains"].(*schema.Set).List()),
Services: expandAppSpecServices(appSpecConfig["service"].([]interface{})),
StaticSites: expandAppSpecStaticSites(appSpecConfig["static_site"].([]interface{})),
Workers: expandAppSpecWorkers(appSpecConfig["worker"].([]interface{})),
Expand All @@ -538,18 +588,32 @@ func expandAppSpec(config []interface{}) *godo.AppSpec {
Envs: expandAppEnvs(appSpecConfig["env"].(*schema.Set).List()),
}

// Prefer the `domain` block over `domains` if it is set.
domainConfig := appSpecConfig["domain"].([]interface{})
if len(domainConfig) > 0 {
appSpec.Domains = expandAppSpecDomains(domainConfig)
} else {
appSpec.Domains = expandAppDomainSpec(appSpecConfig["domains"].(*schema.Set).List())
}

return appSpec
}

func flattenAppSpec(spec *godo.AppSpec) []map[string]interface{} {
func flattenAppSpec(d *schema.ResourceData, spec *godo.AppSpec) []map[string]interface{} {
result := make([]map[string]interface{}, 0, 1)

if spec != nil {

r := make(map[string]interface{})
r["name"] = (*spec).Name
r["region"] = (*spec).Region
r["domains"] = flattenAppDomainSpec((*spec).Domains)

if len((*spec).Domains) > 0 {
r["domains"] = flattenAppDomainSpec((*spec).Domains)
if _, ok := d.GetOk("spec.0.domain"); ok {
r["domain"] = flattenAppSpecDomains((*spec).Domains)
}
}

if len((*spec).Services) > 0 {
r["service"] = flattenAppSpecServices((*spec).Services)
Expand Down Expand Up @@ -581,6 +645,7 @@ func flattenAppSpec(spec *godo.AppSpec) []map[string]interface{} {
return result
}

// expandAppDomainSpec has been deprecated in favor of expandAppSpecDomains.
func expandAppDomainSpec(config []interface{}) []*godo.AppDomainSpec {
appDomains := make([]*godo.AppDomainSpec, 0, len(config))

Expand All @@ -595,6 +660,26 @@ func expandAppDomainSpec(config []interface{}) []*godo.AppDomainSpec {
return appDomains
}

func expandAppSpecDomains(config []interface{}) []*godo.AppDomainSpec {
appDomains := make([]*godo.AppDomainSpec, 0, len(config))

for _, rawDomain := range config {
domain := rawDomain.(map[string]interface{})

d := &godo.AppDomainSpec{
Domain: domain["name"].(string),
Type: godo.AppDomainSpecType(domain["type"].(string)),
Wildcard: domain["wildcard"].(bool),
Zone: domain["zone"].(string),
}

appDomains = append(appDomains, d)
}

return appDomains
}

// flattenAppDomainSpec has been deprecated in favor of flattenAppSpecDomains
func flattenAppDomainSpec(spec []*godo.AppDomainSpec) *schema.Set {
result := schema.NewSet(schema.HashString, []interface{}{})

Expand All @@ -605,6 +690,23 @@ func flattenAppDomainSpec(spec []*godo.AppDomainSpec) *schema.Set {
return result
}

func flattenAppSpecDomains(domains []*godo.AppDomainSpec) []map[string]interface{} {
result := make([]map[string]interface{}, len(domains))

for i, d := range domains {
r := make(map[string]interface{})

r["name"] = d.Domain
r["type"] = string(d.Type)
r["wildcard"] = d.Wildcard
r["zone"] = d.Zone

result[i] = r
}

return result
}

func expandAppGitHubSourceSpec(config []interface{}) *godo.GitHubSourceSpec {
gitHubSourceConfig := config[0].(map[string]interface{})

Expand Down
2 changes: 1 addition & 1 deletion digitalocean/datasource_digitalocean_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func dataSourceDigitalOceanApp() *schema.Resource {
Computed: true,
Description: "A DigitalOcean App Platform Spec",
Elem: &schema.Resource{
Schema: appSpecSchema(),
Schema: appSpecSchema(false),
},
},
"default_ingress": {
Expand Down
4 changes: 2 additions & 2 deletions digitalocean/resource_digitalocean_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func resourceDigitalOceanApp() *schema.Resource {
MaxItems: 1,
Description: "A DigitalOcean App Platform Spec",
Elem: &schema.Resource{
Schema: appSpecSchema(),
Schema: appSpecSchema(true),
},
},

Expand Down Expand Up @@ -112,7 +112,7 @@ func resourceDigitalOceanAppRead(ctx context.Context, d *schema.ResourceData, me
d.Set("updated_at", app.UpdatedAt.UTC().String())
d.Set("created_at", app.CreatedAt.UTC().String())

if err := d.Set("spec", flattenAppSpec(app.Spec)); err != nil {
if err := d.Set("spec", flattenAppSpec(d, app.Spec)); err != nil {
return diag.Errorf("[DEBUG] Error setting app spec: %#v", err)
}

Expand Down
137 changes: 137 additions & 0 deletions digitalocean/resource_digitalocean_app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,121 @@ func TestAccDigitalOceanApp_Worker(t *testing.T) {
})
}

func TestAccDigitalOceanApp_Domain(t *testing.T) {
var app godo.App
appName := randomTestName()

domain := fmt.Sprintf(`
domain {
name = "%s.com"
wildcard = true
}
`, appName)

updatedDomain := fmt.Sprintf(`
domain {
name = "%s.net"
wildcard = true
}
`, appName)

domainsConfig := fmt.Sprintf(testAccCheckDigitalOceanAppConfig_Domains, appName, domain)
updatedDomainConfig := fmt.Sprintf(testAccCheckDigitalOceanAppConfig_Domains, appName, updatedDomain)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDigitalOceanAppDestroy,
Steps: []resource.TestStep{
{
Config: domainsConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanAppExists("digitalocean_app.foobar", &app),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.name", appName),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.domain.0.name", appName+".com"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.domain.0.wildcard", "true"),
),
},
{
Config: updatedDomainConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanAppExists("digitalocean_app.foobar", &app),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.name", appName),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.domain.0.name", appName+".net"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.domain.0.wildcard", "true"),
),
},
},
})
}

func TestAccDigitalOceanApp_DomainsDeprecation(t *testing.T) {
var app godo.App
appName := randomTestName()

deprecatedStyleDomain := fmt.Sprintf(`
domains = ["%s.com"]
`, appName)

updatedDeprecatedStyleDomain := fmt.Sprintf(`
domains = ["%s.net"]
`, appName)

newStyleDomain := fmt.Sprintf(`
domain {
name = "%s.com"
wildcard = true
}
`, appName)

domainsConfig := fmt.Sprintf(testAccCheckDigitalOceanAppConfig_Domains, appName, deprecatedStyleDomain)
updateDomainsConfig := fmt.Sprintf(testAccCheckDigitalOceanAppConfig_Domains, appName, updatedDeprecatedStyleDomain)
replaceDomainsConfig := fmt.Sprintf(testAccCheckDigitalOceanAppConfig_Domains, appName, newStyleDomain)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDigitalOceanAppDestroy,
Steps: []resource.TestStep{
{
Config: domainsConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanAppExists("digitalocean_app.foobar", &app),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.name", appName),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.domains.0", appName+".com"),
),
},
{
Config: updateDomainsConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanAppExists("digitalocean_app.foobar", &app),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.name", appName),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.domains.0", appName+".net"),
),
},
{
Config: replaceDomainsConfig,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.domain.0.name", appName+".com"),
resource.TestCheckResourceAttr(
"digitalocean_app.foobar", "spec.0.domain.0.wildcard", "true"),
),
},
},
})
}

func testAccCheckDigitalOceanAppDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*CombinedConfig).godoClient()

Expand Down Expand Up @@ -722,3 +837,25 @@ resource "digitalocean_app" "foobar" {
}
}
}`

var testAccCheckDigitalOceanAppConfig_Domains = `
resource "digitalocean_app" "foobar" {
spec {
name = "%s"
region = "ams"
%s
service {
name = "go-service"
environment_slug = "go"
instance_count = 1
instance_size_slug = "basic-xxs"
git {
repo_clone_url = "https://github.com/digitalocean/sample-golang.git"
branch = "main"
}
}
}
}`

0 comments on commit 9d98694

Please sign in to comment.