Skip to content

Commit 5a69fcb

Browse files
committed
fix: correct URL validation and centralize logic
1 parent f239e51 commit 5a69fcb

File tree

7 files changed

+121
-53
lines changed

7 files changed

+121
-53
lines changed

provider/app.go

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package provider
22

33
import (
44
"context"
5-
"net/url"
5+
"github.com/coder/terraform-provider-coder/v2/provider/helpers"
66
"regexp"
77

88
"github.com/google/uuid"
@@ -93,15 +93,9 @@ func appResource() *schema.Resource {
9393
Description: "A URL to an icon that will display in the dashboard. View built-in " +
9494
"icons [here](https://github.com/coder/coder/tree/main/site/static/icon). Use a " +
9595
"built-in icon with `\"${data.coder_workspace.me.access_url}/icon/<path>\"`.",
96-
ForceNew: true,
97-
Optional: true,
98-
ValidateFunc: func(i any, s string) ([]string, []error) {
99-
_, err := url.Parse(s)
100-
if err != nil {
101-
return nil, []error{err}
102-
}
103-
return nil, nil
104-
},
96+
ForceNew: true,
97+
Optional: true,
98+
ValidateFunc: helpers.ValidateURL,
10599
},
106100
"slug": {
107101
Type: schema.TypeString,

provider/app_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,4 +478,61 @@ func TestApp(t *testing.T) {
478478
})
479479
}
480480
})
481+
482+
t.Run("Icon", func(t *testing.T) {
483+
t.Parallel()
484+
485+
cases := []struct {
486+
name string
487+
icon string
488+
expectError *regexp.Regexp
489+
}{
490+
{
491+
name: "Empty",
492+
icon: "",
493+
},
494+
{
495+
name: "ValidURL",
496+
icon: "/icon/region.svg",
497+
},
498+
{
499+
name: "InvalidURL",
500+
icon: "/icon%.svg",
501+
expectError: regexp.MustCompile("invalid URL escape"),
502+
},
503+
}
504+
505+
for _, c := range cases {
506+
c := c
507+
508+
t.Run(c.name, func(t *testing.T) {
509+
t.Parallel()
510+
511+
config := fmt.Sprintf(`
512+
provider "coder" {}
513+
resource "coder_agent" "dev" {
514+
os = "linux"
515+
arch = "amd64"
516+
}
517+
resource "coder_app" "code-server" {
518+
agent_id = coder_agent.dev.id
519+
slug = "code-server"
520+
display_name = "Testing"
521+
url = "http://localhost:13337"
522+
open_in = "slim-window"
523+
icon = "%s"
524+
}
525+
`, c.icon)
526+
527+
resource.Test(t, resource.TestCase{
528+
ProviderFactories: coderFactory(),
529+
IsUnitTest: true,
530+
Steps: []resource.TestStep{{
531+
Config: config,
532+
ExpectError: c.expectError,
533+
}},
534+
})
535+
})
536+
}
537+
})
481538
}

provider/helpers/validation.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package helpers
2+
3+
import (
4+
"fmt"
5+
"net/url"
6+
)
7+
8+
// ValidateURL checks that the given value is a valid URL string.
9+
// Example: for `icon = "/icon/region.svg"`, value is `/icon/region.svg` and label is `icon`.
10+
func ValidateURL(value interface{}, label string) ([]string, []error) {
11+
val, ok := value.(string)
12+
if !ok {
13+
return nil, []error{fmt.Errorf("expected %q to be a string", label)}
14+
}
15+
if _, err := url.Parse(val); err != nil {
16+
return nil, []error{err}
17+
}
18+
return nil, nil
19+
}

provider/metadata.go

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ package provider
22

33
import (
44
"context"
5-
"net/url"
6-
5+
"github.com/coder/terraform-provider-coder/v2/provider/helpers"
76
"github.com/google/uuid"
87
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
98
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -56,15 +55,9 @@ func metadataResource() *schema.Resource {
5655
Description: "A URL to an icon that will display in the dashboard. View built-in " +
5756
"icons [here](https://github.com/coder/coder/tree/main/site/static/icon). Use a " +
5857
"built-in icon with `\"${data.coder_workspace.me.access_url}/icon/<path>\"`.",
59-
ForceNew: true,
60-
Optional: true,
61-
ValidateFunc: func(i interface{}, s string) ([]string, []error) {
62-
_, err := url.Parse(s)
63-
if err != nil {
64-
return nil, []error{err}
65-
}
66-
return nil, nil
67-
},
58+
ForceNew: true,
59+
Optional: true,
60+
ValidateFunc: helpers.ValidateURL,
6861
},
6962
"daily_cost": {
7063
Type: schema.TypeInt,

provider/parameter.go

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"encoding/hex"
77
"encoding/json"
88
"fmt"
9-
"net/url"
9+
"github.com/coder/terraform-provider-coder/v2/provider/helpers"
1010
"os"
1111
"regexp"
1212
"strconv"
@@ -223,15 +223,9 @@ func parameterDataSource() *schema.Resource {
223223
Description: "A URL to an icon that will display in the dashboard. View built-in " +
224224
"icons [here](https://github.com/coder/coder/tree/main/site/static/icon). Use a " +
225225
"built-in icon with `\"${data.coder_workspace.me.access_url}/icon/<path>\"`.",
226-
ForceNew: true,
227-
Optional: true,
228-
ValidateFunc: func(i interface{}, s string) ([]string, []error) {
229-
_, err := url.Parse(s)
230-
if err != nil {
231-
return nil, []error{err}
232-
}
233-
return nil, nil
234-
},
226+
ForceNew: true,
227+
Optional: true,
228+
ValidateFunc: helpers.ValidateURL,
235229
},
236230
"option": {
237231
Type: schema.TypeList,
@@ -263,15 +257,9 @@ func parameterDataSource() *schema.Resource {
263257
Description: "A URL to an icon that will display in the dashboard. View built-in " +
264258
"icons [here](https://github.com/coder/coder/tree/main/site/static/icon). Use a " +
265259
"built-in icon with `\"${data.coder_workspace.me.access_url}/icon/<path>\"`.",
266-
ForceNew: true,
267-
Optional: true,
268-
ValidateFunc: func(i interface{}, s string) ([]string, []error) {
269-
_, err := url.Parse(s)
270-
if err != nil {
271-
return nil, []error{err}
272-
}
273-
return nil, nil
274-
},
260+
ForceNew: true,
261+
Optional: true,
262+
ValidateFunc: helpers.ValidateURL,
275263
},
276264
},
277265
},

provider/parameter_test.go

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -234,10 +234,6 @@ func TestParameter(t *testing.T) {
234234
icon = "/icon/code.svg"
235235
description = "Something!"
236236
}
237-
option {
238-
name = "2"
239-
value = "2"
240-
}
241237
}
242238
`,
243239
Check: func(state *terraform.ResourceState) {
@@ -665,7 +661,33 @@ data "coder_parameter" "region" {
665661
}
666662
`,
667663
ExpectError: regexp.MustCompile("ephemeral parameter requires the default property"),
668-
}} {
664+
}, {
665+
Name: "InvalidIconURL",
666+
Config: `
667+
data "coder_parameter" "region" {
668+
name = "Region"
669+
type = "string"
670+
icon = "/icon%.svg"
671+
}
672+
`,
673+
ExpectError: regexp.MustCompile("invalid URL escape"),
674+
}, {
675+
Name: "OptionInvalidIconURL",
676+
Config: `
677+
data "coder_parameter" "region" {
678+
name = "Region"
679+
type = "string"
680+
option {
681+
name = "1"
682+
value = "1"
683+
icon = "/icon%.svg"
684+
description = "Something!"
685+
}
686+
}
687+
`,
688+
ExpectError: regexp.MustCompile("foo"),
689+
},
690+
} {
669691
tc := tc
670692
t.Run(tc.Name, func(t *testing.T) {
671693
t.Parallel()

provider/provider.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package provider
22

33
import (
44
"context"
5+
"github.com/coder/terraform-provider-coder/v2/provider/helpers"
56
"net/url"
67
"reflect"
78
"strings"
@@ -26,14 +27,8 @@ func New() *schema.Provider {
2627
Optional: true,
2728
// The "CODER_AGENT_URL" environment variable is used by default
2829
// as the Access URL when generating scripts.
29-
DefaultFunc: schema.EnvDefaultFunc("CODER_AGENT_URL", "https://mydeployment.coder.com"),
30-
ValidateFunc: func(i interface{}, s string) ([]string, []error) {
31-
_, err := url.Parse(s)
32-
if err != nil {
33-
return nil, []error{err}
34-
}
35-
return nil, nil
36-
},
30+
DefaultFunc: schema.EnvDefaultFunc("CODER_AGENT_URL", "https://mydeployment.coder.com"),
31+
ValidateFunc: helpers.ValidateURL,
3732
},
3833
},
3934
ConfigureContextFunc: func(c context.Context, resourceData *schema.ResourceData) (interface{}, diag.Diagnostics) {

0 commit comments

Comments
 (0)