Skip to content

Commit 32b8b7f

Browse files
committed
rework addon name validation
1 parent b4abe8d commit 32b8b7f

File tree

2 files changed

+59
-43
lines changed

2 files changed

+59
-43
lines changed

heroku/resource_heroku_addon.go

Lines changed: 24 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package heroku
33
import (
44
"context"
55
"fmt"
6+
"github.com/hashicorp/terraform/helper/validation"
67
"log"
78
"net/url"
89
"regexp"
@@ -15,10 +16,6 @@ import (
1516
heroku "github.com/heroku/heroku-go/v5"
1617
)
1718

18-
const (
19-
AddonNameMaxLength = 256
20-
)
21-
2219
// Global lock to prevent parallelism for heroku_addon since
2320
// the Heroku API cannot handle a single application requesting
2421
// multiple addons simultaneously.
@@ -52,9 +49,10 @@ func resourceHerokuAddon() *schema.Resource {
5249
},
5350

5451
"name": {
55-
Type: schema.TypeString,
56-
Optional: true,
57-
Computed: true,
52+
Type: schema.TypeString,
53+
Optional: true,
54+
Computed: true,
55+
ValidateFunc: validateCustomAddonName,
5856
},
5957

6058
"config": {
@@ -79,6 +77,25 @@ func resourceHerokuAddon() *schema.Resource {
7977
}
8078
}
8179

80+
func validateCustomAddonName(v interface{}, k string) (ws []string, errors []error) {
81+
// Check length
82+
v1 := validation.StringLenBetween(1, 256)
83+
_, errs1 := v1(v, k)
84+
for _, err := range errs1 {
85+
errors = append(errors, err)
86+
}
87+
88+
// Check validity
89+
valRegex := regexp.MustCompile(`^[a-zA-Z][A-Za-z0-9_-]+$`)
90+
v2 := validation.StringMatch(valRegex, "Invalid custom addon name: must start with a letter and can only contain lowercase letters, numbers, and dashes")
91+
_, errs2 := v2(v, k)
92+
for _, err := range errs2 {
93+
errors = append(errors, err)
94+
}
95+
96+
return ws, errors
97+
}
98+
8299
func resourceHerokuAddonCreate(d *schema.ResourceData, meta interface{}) error {
83100
addonLock.Lock()
84101
defer addonLock.Unlock()
@@ -99,12 +116,6 @@ func resourceHerokuAddonCreate(d *schema.ResourceData, meta interface{}) error {
99116
}
100117

101118
if v := d.Get("name").(string); v != "" {
102-
// Validate name with regex
103-
valErr := validateAddonName(v)
104-
if valErr != nil {
105-
return valErr
106-
}
107-
108119
opts.Name = &v
109120
}
110121

@@ -256,32 +267,3 @@ func AddOnStateRefreshFunc(client *heroku.Service, appID, addOnID string) resour
256267
return (*heroku.AddOn)(addon), addon.State, nil
257268
}
258269
}
259-
260-
// validateAddonName uses the documented regex expression to make sure the user provided addon name is valid.
261-
//
262-
// Reference: https://devcenter.heroku.com/articles/platform-api-reference#add-on-create-optional-parameters
263-
func validateAddonName(name string) error {
264-
errors := make([]string, 0)
265-
266-
// First validate length. There is no documented length
267-
// but I've tried up to 256 characters so this will be max for now.
268-
if len(name) > AddonNameMaxLength {
269-
errors = append(errors, fmt.Sprintf("Length cannot exceed %v characters", AddonNameMaxLength))
270-
}
271-
272-
// Then validate the content of the string against documented regex.
273-
regex := regexp.MustCompile(`^[a-zA-Z][A-Za-z0-9_-]+$`)
274-
matches := regex.FindStringSubmatch(name)
275-
if len(matches) == 0 {
276-
errors = append(errors, "Needs to match this regex: ^[a-zA-Z][A-Za-z0-9_-]+$")
277-
}
278-
279-
if len(errors) > 0 {
280-
errFormatted := ""
281-
for _, err := range errors {
282-
errFormatted += fmt.Sprintf("-%s\n", err)
283-
}
284-
return fmt.Errorf("Invalid custom addon name:\n" + errFormatted)
285-
}
286-
return nil
287-
}

heroku/resource_heroku_addon_test.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,41 @@ func TestAccHerokuAddon_CustomName_Invalid(t *testing.T) {
111111
Steps: []resource.TestStep{
112112
{
113113
Config: testAccCheckHerokuAddonConfig_CustomName(appName, customName),
114-
ExpectError: regexp.MustCompile(`Invalid custom addon name:.*`),
114+
ExpectError: regexp.MustCompile(`config is invalid: invalid value for name`),
115+
},
116+
},
117+
})
118+
}
119+
120+
func TestAccHerokuAddon_CustomName_EmptyString(t *testing.T) {
121+
appName := fmt.Sprintf("tftest-%s", acctest.RandString(10))
122+
customName := ""
123+
124+
resource.Test(t, resource.TestCase{
125+
PreCheck: func() { testAccPreCheck(t) },
126+
Providers: testAccProviders,
127+
CheckDestroy: testAccCheckHerokuAddonDestroy,
128+
Steps: []resource.TestStep{
129+
{
130+
Config: testAccCheckHerokuAddonConfig_CustomName(appName, customName),
131+
ExpectError: regexp.MustCompile(`config is invalid: 2 problems:.*`),
132+
},
133+
},
134+
})
135+
}
136+
137+
func TestAccHerokuAddon_CustomName_FirstCharNum(t *testing.T) {
138+
appName := fmt.Sprintf("tftest-%s", acctest.RandString(10))
139+
customName := "1dasdad"
140+
141+
resource.Test(t, resource.TestCase{
142+
PreCheck: func() { testAccPreCheck(t) },
143+
Providers: testAccProviders,
144+
CheckDestroy: testAccCheckHerokuAddonDestroy,
145+
Steps: []resource.TestStep{
146+
{
147+
Config: testAccCheckHerokuAddonConfig_CustomName(appName, customName),
148+
ExpectError: regexp.MustCompile(`config is invalid: invalid value for name`),
115149
},
116150
},
117151
})

0 commit comments

Comments
 (0)