Skip to content

Commit 25281b6

Browse files
authored
feat(http): add new URL formatting for PostgreSQL connections on ipv6 (#4783)
feat(http): add new URL formatting for PostgreSQL connections
1 parent 692f4ba commit 25281b6

File tree

2 files changed

+92
-2
lines changed

2 files changed

+92
-2
lines changed

flypg/http.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import (
44
"bytes"
55
"context"
66
"encoding/json"
7-
"fmt"
87
"io"
98
"net"
109
"net/http"
10+
"net/url"
1111
"time"
1212

1313
"github.com/PuerkitoBio/rehttp"
@@ -16,14 +16,16 @@ import (
1616
"github.com/superfly/flyctl/terminal"
1717
)
1818

19+
const flypgPort = "5500"
20+
1921
type Client struct {
2022
httpClient *http.Client
2123
BaseURL string
2224
}
2325

2426
// NewFromInstance creates a new Client that targets a specific instance(address)
2527
func NewFromInstance(address string, dialer agent.Dialer) *Client {
26-
url := fmt.Sprintf("http://%s:5500", address)
28+
url := formatPGBaseURL(address)
2729
terminal.Debugf("flypg will connect to: %s\n", url)
2830

2931
return &Client{
@@ -32,6 +34,15 @@ func NewFromInstance(address string, dialer agent.Dialer) *Client {
3234
}
3335
}
3436

37+
func formatPGBaseURL(address string) string {
38+
hostport := net.JoinHostPort(address, flypgPort)
39+
40+
return (&url.URL{
41+
Scheme: "http",
42+
Host: hostport,
43+
}).String()
44+
}
45+
3546
func newHttpClient(dialer agent.Dialer) *http.Client {
3647
transport := &http.Transport{
3748
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {

flypg/http_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package flypg
2+
3+
// Run: go test ./flypg (repo root) or cd flypg && go test .
4+
//
5+
// Do not run go test flypg/http_test.go — that compiles only this file, so
6+
// NewFromInstance and flypgPort from http.go are undefined.
7+
8+
import (
9+
"net/url"
10+
"testing"
11+
12+
"github.com/stretchr/testify/assert"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
func TestFormatPGBaseURL_IPv4(t *testing.T) {
17+
client := NewFromInstance("192.168.1.1", nil)
18+
assert.Equal(t, "http://192.168.1.1:5500", client.BaseURL)
19+
assertParsableFlyPGURL(t, client.BaseURL, "192.168.1.1", "5500")
20+
}
21+
22+
func TestFormatPGBaseURL_IPv6(t *testing.T) {
23+
client := NewFromInstance("fdaa:2:be18:a7b:620:7ff8:9a20:2", nil)
24+
assert.Equal(t, "http://[fdaa:2:be18:a7b:620:7ff8:9a20:2]:5500", client.BaseURL)
25+
assertParsableFlyPGURL(t, client.BaseURL, "fdaa:2:be18:a7b:620:7ff8:9a20:2", "5500")
26+
}
27+
28+
func TestFormatPGBaseURL_Hostname(t *testing.T) {
29+
client := NewFromInstance("myapp.internal", nil)
30+
assert.Equal(t, "http://myapp.internal:5500", client.BaseURL)
31+
assertParsableFlyPGURL(t, client.BaseURL, "myapp.internal", "5500")
32+
}
33+
34+
func TestFormatPGBaseURL_Table(t *testing.T) {
35+
port := flypgPort
36+
tests := []struct {
37+
name string
38+
address string
39+
wantBaseURL string
40+
wantHostname string
41+
}{
42+
{
43+
name: "ipv4",
44+
address: "10.0.0.1",
45+
wantBaseURL: "http://10.0.0.1:5500",
46+
wantHostname: "10.0.0.1",
47+
},
48+
{
49+
name: "ipv6_compressed",
50+
address: "2001:db8::1",
51+
wantBaseURL: "http://[2001:db8::1]:5500",
52+
wantHostname: "2001:db8::1",
53+
},
54+
{
55+
name: "hostname",
56+
address: "pg.example.internal",
57+
wantBaseURL: "http://pg.example.internal:5500",
58+
wantHostname: "pg.example.internal",
59+
},
60+
}
61+
for _, tt := range tests {
62+
t.Run(tt.name, func(t *testing.T) {
63+
got := NewFromInstance(tt.address, nil).BaseURL
64+
assert.Equal(t, tt.wantBaseURL, got)
65+
assertParsableFlyPGURL(t, got, tt.wantHostname, port)
66+
})
67+
}
68+
}
69+
70+
// assertParsableFlyPGURL checks the base URL parses and Hostname/Port match expectations
71+
// (same rules as net/http for dialing).
72+
func assertParsableFlyPGURL(t *testing.T, raw, wantHostname, wantPort string) {
73+
t.Helper()
74+
u, err := url.Parse(raw)
75+
require.NoError(t, err)
76+
assert.Equal(t, "http", u.Scheme)
77+
assert.Equal(t, wantHostname, u.Hostname())
78+
assert.Equal(t, wantPort, u.Port())
79+
}

0 commit comments

Comments
 (0)