Skip to content

Commit b246f08

Browse files
authored
chore: move app URL parsing to its own package (#11651)
* chore: move app url parsing to it's own package
1 parent 1aee8da commit b246f08

32 files changed

+165
-133
lines changed

cli/clibase/values.go

+26
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,28 @@ func (i *Validator[T]) Type() string {
5959
return i.Value.Type()
6060
}
6161

62+
func (i *Validator[T]) MarshalYAML() (interface{}, error) {
63+
m, ok := any(i.Value).(yaml.Marshaler)
64+
if !ok {
65+
return i.Value, nil
66+
}
67+
return m.MarshalYAML()
68+
}
69+
70+
func (i *Validator[T]) UnmarshalYAML(n *yaml.Node) error {
71+
return n.Decode(i.Value)
72+
}
73+
74+
func (i *Validator[T]) MarshalJSON() ([]byte, error) {
75+
return json.Marshal(i.Value)
76+
}
77+
78+
func (i *Validator[T]) UnmarshalJSON(b []byte) error {
79+
return json.Unmarshal(b, i.Value)
80+
}
81+
82+
func (i *Validator[T]) Underlying() pflag.Value { return i.Value }
83+
6284
// values.go contains a standard set of value types that can be used as
6385
// Option Values.
6486

@@ -378,6 +400,7 @@ func (s *Struct[T]) String() string {
378400
return string(byt)
379401
}
380402

403+
// nolint:revive
381404
func (s *Struct[T]) MarshalYAML() (interface{}, error) {
382405
var n yaml.Node
383406
err := n.Encode(s.Value)
@@ -387,6 +410,7 @@ func (s *Struct[T]) MarshalYAML() (interface{}, error) {
387410
return n, nil
388411
}
389412

413+
// nolint:revive
390414
func (s *Struct[T]) UnmarshalYAML(n *yaml.Node) error {
391415
// HACK: for compatibility with flags, we use nil slices instead of empty
392416
// slices. In most cases, nil slices and empty slices are treated
@@ -403,10 +427,12 @@ func (s *Struct[T]) Type() string {
403427
return fmt.Sprintf("struct[%T]", s.Value)
404428
}
405429

430+
// nolint:revive
406431
func (s *Struct[T]) MarshalJSON() ([]byte, error) {
407432
return json.Marshal(s.Value)
408433
}
409434

435+
// nolint:revive
410436
func (s *Struct[T]) UnmarshalJSON(b []byte) error {
411437
return json.Unmarshal(b, &s.Value)
412438
}

cli/clibase/yaml.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"strings"
77

88
"github.com/mitchellh/go-wordwrap"
9+
"github.com/spf13/pflag"
910
"golang.org/x/xerrors"
1011
"gopkg.in/yaml.v3"
1112
)
@@ -74,13 +75,16 @@ func (optSet *OptionSet) MarshalYAML() (any, error) {
7475
Value: opt.YAML,
7576
HeadComment: comment,
7677
}
78+
79+
_, isValidator := opt.Value.(interface{ Underlying() pflag.Value })
7780
var valueNode yaml.Node
7881
if opt.Value == nil {
7982
valueNode = yaml.Node{
8083
Kind: yaml.ScalarNode,
8184
Value: "null",
8285
}
83-
} else if m, ok := opt.Value.(yaml.Marshaler); ok {
86+
} else if m, ok := opt.Value.(yaml.Marshaler); ok && !isValidator {
87+
// Validators do a wrap, and should be handled by the else statement.
8488
v, err := m.MarshalYAML()
8589
if err != nil {
8690
return nil, xerrors.Errorf(

cli/server.go

+6-7
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ import (
5353
"gopkg.in/yaml.v3"
5454
"tailscale.com/tailcfg"
5555

56-
"github.com/coder/pretty"
57-
5856
"cdr.dev/slog"
5957
"cdr.dev/slog/sloggers/sloghuman"
6058
"github.com/coder/coder/v2/buildinfo"
@@ -75,7 +73,6 @@ import (
7573
"github.com/coder/coder/v2/coderd/devtunnel"
7674
"github.com/coder/coder/v2/coderd/externalauth"
7775
"github.com/coder/coder/v2/coderd/gitsshkey"
78-
"github.com/coder/coder/v2/coderd/httpapi"
7976
"github.com/coder/coder/v2/coderd/httpmw"
8077
"github.com/coder/coder/v2/coderd/oauthpki"
8178
"github.com/coder/coder/v2/coderd/prometheusmetrics"
@@ -89,6 +86,7 @@ import (
8986
"github.com/coder/coder/v2/coderd/util/slice"
9087
stringutil "github.com/coder/coder/v2/coderd/util/strings"
9188
"github.com/coder/coder/v2/coderd/workspaceapps"
89+
"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
9290
"github.com/coder/coder/v2/codersdk"
9391
"github.com/coder/coder/v2/codersdk/drpc"
9492
"github.com/coder/coder/v2/cryptorand"
@@ -99,6 +97,7 @@ import (
9997
"github.com/coder/coder/v2/provisionersdk"
10098
sdkproto "github.com/coder/coder/v2/provisionersdk/proto"
10199
"github.com/coder/coder/v2/tailnet"
100+
"github.com/coder/pretty"
102101
"github.com/coder/retry"
103102
"github.com/coder/wgtunnel/tunnelsdk"
104103
)
@@ -434,11 +433,11 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
434433

435434
if vals.WildcardAccessURL.String() == "" {
436435
// Suffixed wildcard access URL.
437-
u, err := url.Parse(fmt.Sprintf("*--%s", tunnel.URL.Hostname()))
436+
wu := fmt.Sprintf("*--%s", tunnel.URL.Hostname())
437+
err = vals.WildcardAccessURL.Set(wu)
438438
if err != nil {
439-
return xerrors.Errorf("parse wildcard url: %w", err)
439+
return xerrors.Errorf("set wildcard access url %q: %w", wu, err)
440440
}
441-
vals.WildcardAccessURL = clibase.URL(*u)
442441
}
443442
}
444443

@@ -513,7 +512,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
513512
appHostname := vals.WildcardAccessURL.String()
514513
var appHostnameRegex *regexp.Regexp
515514
if appHostname != "" {
516-
appHostnameRegex, err = httpapi.CompileHostnamePattern(appHostname)
515+
appHostnameRegex, err = appurl.CompileHostnamePattern(appHostname)
517516
if err != nil {
518517
return xerrors.Errorf("parse wildcard access URL %q: %w", appHostname, err)
519518
}

cli/server_test.go

+13
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"time"
3030

3131
"github.com/go-chi/chi/v5"
32+
"github.com/spf13/pflag"
3233
"github.com/stretchr/testify/assert"
3334
"github.com/stretchr/testify/require"
3435
"go.uber.org/goleak"
@@ -1552,6 +1553,18 @@ func TestServer(t *testing.T) {
15521553
// ValueSource is not going to be correct on the `want`, so just
15531554
// match that field.
15541555
wantConfig.Options[i].ValueSource = gotConfig.Options[i].ValueSource
1556+
1557+
// If there is a wrapped value with a validator, unwrap it.
1558+
// The underlying doesn't compare well since it compares go pointers,
1559+
// and not the actual value.
1560+
if validator, isValidator := wantConfig.Options[i].Value.(interface{ Underlying() pflag.Value }); isValidator {
1561+
wantConfig.Options[i].Value = validator.Underlying()
1562+
}
1563+
1564+
if validator, isValidator := gotConfig.Options[i].Value.(interface{ Underlying() pflag.Value }); isValidator {
1565+
gotConfig.Options[i].Value = validator.Underlying()
1566+
}
1567+
15551568
assert.Equal(
15561569
t, wantConfig.Options[i],
15571570
gotConfig.Options[i],

cli/testdata/coder_server_--help.golden

+1-1
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ NETWORKING OPTIONS:
167167
--secure-auth-cookie bool, $CODER_SECURE_AUTH_COOKIE
168168
Controls if the 'Secure' property is set on browser session cookies.
169169

170-
--wildcard-access-url url, $CODER_WILDCARD_ACCESS_URL
170+
--wildcard-access-url string, $CODER_WILDCARD_ACCESS_URL
171171
Specifies the wildcard hostname to use for workspace applications in
172172
the form "*.example.com".
173173

cli/testdata/server-config.yaml.golden

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ networking:
44
accessURL:
55
# Specifies the wildcard hostname to use for workspace applications in the form
66
# "*.example.com".
7-
# (default: <unset>, type: url)
8-
wildcardAccessURL:
7+
# (default: <unset>, type: string)
8+
wildcardAccessURL: ""
99
# Specifies the custom docs URL.
1010
# (default: <unset>, type: url)
1111
docsURL:

coderd/agentapi/manifest.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020
"github.com/coder/coder/v2/coderd/database/db2sdk"
2121
"github.com/coder/coder/v2/coderd/database/dbauthz"
2222
"github.com/coder/coder/v2/coderd/externalauth"
23-
"github.com/coder/coder/v2/coderd/httpapi"
23+
"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
2424
"github.com/coder/coder/v2/codersdk"
2525
"github.com/coder/coder/v2/tailnet"
2626
)
@@ -108,7 +108,7 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest
108108
return nil, xerrors.Errorf("fetching workspace agent data: %w", err)
109109
}
110110

111-
appHost := httpapi.ApplicationURL{
111+
appHost := appurl.ApplicationURL{
112112
AppSlugOrPort: "{{port}}",
113113
AgentName: workspaceAgent.Name,
114114
WorkspaceName: workspace.Name,

coderd/apidoc/docs.go

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/coderd.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ type Options struct {
9696
// E.g. "*.apps.coder.com" or "*-apps.coder.com".
9797
AppHostname string
9898
// AppHostnameRegex contains the regex version of options.AppHostname as
99-
// generated by httpapi.CompileHostnamePattern(). It MUST be set if
99+
// generated by appurl.CompileHostnamePattern(). It MUST be set if
100100
// options.AppHostname is set.
101101
AppHostnameRegex *regexp.Regexp
102102
Logger slog.Logger

coderd/coderdtest/coderdtest.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ import (
6262
"github.com/coder/coder/v2/coderd/externalauth"
6363
"github.com/coder/coder/v2/coderd/gitsshkey"
6464
"github.com/coder/coder/v2/coderd/healthcheck"
65-
"github.com/coder/coder/v2/coderd/httpapi"
6665
"github.com/coder/coder/v2/coderd/httpmw"
6766
"github.com/coder/coder/v2/coderd/rbac"
6867
"github.com/coder/coder/v2/coderd/schedule"
@@ -71,6 +70,7 @@ import (
7170
"github.com/coder/coder/v2/coderd/updatecheck"
7271
"github.com/coder/coder/v2/coderd/util/ptr"
7372
"github.com/coder/coder/v2/coderd/workspaceapps"
73+
"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
7474
"github.com/coder/coder/v2/codersdk"
7575
"github.com/coder/coder/v2/codersdk/agentsdk"
7676
"github.com/coder/coder/v2/codersdk/drpc"
@@ -372,7 +372,7 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
372372
var appHostnameRegex *regexp.Regexp
373373
if options.AppHostname != "" {
374374
var err error
375-
appHostnameRegex, err = httpapi.CompileHostnamePattern(options.AppHostname)
375+
appHostnameRegex, err = appurl.CompileHostnamePattern(options.AppHostname)
376376
require.NoError(t, err)
377377
}
378378

coderd/database/db2sdk/db2sdk.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ import (
1414
"tailscale.com/tailcfg"
1515

1616
"github.com/coder/coder/v2/coderd/database"
17-
"github.com/coder/coder/v2/coderd/httpapi"
1817
"github.com/coder/coder/v2/coderd/parameter"
1918
"github.com/coder/coder/v2/coderd/rbac"
19+
"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
2020
"github.com/coder/coder/v2/codersdk"
2121
"github.com/coder/coder/v2/provisionersdk/proto"
2222
"github.com/coder/coder/v2/tailnet"
@@ -381,7 +381,7 @@ func AppSubdomain(dbApp database.WorkspaceApp, agentName, workspaceName, ownerNa
381381
if appSlug == "" {
382382
appSlug = dbApp.DisplayName
383383
}
384-
return httpapi.ApplicationURL{
384+
return appurl.ApplicationURL{
385385
// We never generate URLs with a prefix. We only allow prefixes when
386386
// parsing URLs from the hostname. Users that want this feature can
387387
// write out their own URLs.

coderd/database/dbmem/dbmem.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ import (
2121

2222
"github.com/coder/coder/v2/coderd/database"
2323
"github.com/coder/coder/v2/coderd/database/dbtime"
24-
"github.com/coder/coder/v2/coderd/httpapi"
2524
"github.com/coder/coder/v2/coderd/rbac"
2625
"github.com/coder/coder/v2/coderd/rbac/regosql"
2726
"github.com/coder/coder/v2/coderd/util/slice"
27+
"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
2828
"github.com/coder/coder/v2/codersdk"
2929
"github.com/coder/coder/v2/provisionersdk"
3030
)
@@ -4566,11 +4566,11 @@ func (q *FakeQuerier) GetWorkspaceProxyByHostname(_ context.Context, params data
45664566

45674567
// Compile the app hostname regex. This is slow sadly.
45684568
if params.AllowWildcardHostname {
4569-
wildcardRegexp, err := httpapi.CompileHostnamePattern(proxy.WildcardHostname)
4569+
wildcardRegexp, err := appurl.CompileHostnamePattern(proxy.WildcardHostname)
45704570
if err != nil {
45714571
return database.WorkspaceProxy{}, xerrors.Errorf("compile hostname pattern %q for proxy %q (%s): %w", proxy.WildcardHostname, proxy.Name, proxy.ID.String(), err)
45724572
}
4573-
if _, ok := httpapi.ExecuteHostnamePattern(wildcardRegexp, params.Hostname); ok {
4573+
if _, ok := appurl.ExecuteHostnamePattern(wildcardRegexp, params.Hostname); ok {
45744574
return proxy, nil
45754575
}
45764576
}

coderd/httpmw/cors.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77

88
"github.com/go-chi/cors"
99

10-
"github.com/coder/coder/v2/coderd/httpapi"
10+
"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
1111
)
1212

1313
const (
@@ -44,18 +44,18 @@ func Cors(allowAll bool, origins ...string) func(next http.Handler) http.Handler
4444
})
4545
}
4646

47-
func WorkspaceAppCors(regex *regexp.Regexp, app httpapi.ApplicationURL) func(next http.Handler) http.Handler {
47+
func WorkspaceAppCors(regex *regexp.Regexp, app appurl.ApplicationURL) func(next http.Handler) http.Handler {
4848
return cors.Handler(cors.Options{
4949
AllowOriginFunc: func(r *http.Request, rawOrigin string) bool {
5050
origin, err := url.Parse(rawOrigin)
5151
if rawOrigin == "" || origin.Host == "" || err != nil {
5252
return false
5353
}
54-
subdomain, ok := httpapi.ExecuteHostnamePattern(regex, origin.Host)
54+
subdomain, ok := appurl.ExecuteHostnamePattern(regex, origin.Host)
5555
if !ok {
5656
return false
5757
}
58-
originApp, err := httpapi.ParseSubdomainAppURL(subdomain)
58+
originApp, err := appurl.ParseSubdomainAppURL(subdomain)
5959
if err != nil {
6060
return false
6161
}

0 commit comments

Comments
 (0)