• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / cli / 19331858962

13 Nov 2025 12:40PM UTC coverage: 55.051% (+0.4%) from 54.689%
19331858962

Pull #4381

github

web-flow
Merge 897ea3f61 into 9f3ad132d
Pull Request #4381: feat: `functions download foo --use-api`

94 of 139 new or added lines in 2 files covered. (67.63%)

156 existing lines in 11 files now uncovered.

6507 of 11820 relevant lines covered (55.05%)

6.29 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.63
/internal/utils/api.go
1
package utils
2

3
import (
4
        "context"
5
        "encoding/json"
6
        "fmt"
7
        "log"
8
        "net"
9
        "net/http"
10
        "sync"
11

12
        "github.com/go-errors/errors"
13
        "github.com/spf13/afero"
14
        "github.com/supabase/cli/internal/utils/cloudflare"
15
        supabase "github.com/supabase/cli/pkg/api"
16
        "github.com/supabase/cli/pkg/cast"
17
)
18

19
const (
20
        DNS_GO_NATIVE  = "native"
21
        DNS_OVER_HTTPS = "https"
22
)
23

24
var (
25
        clientOnce sync.Once
26
        apiClient  *supabase.ClientWithResponses
27

28
        DNSResolver = EnumFlag{
29
                Allowed: []string{DNS_GO_NATIVE, DNS_OVER_HTTPS},
30
                Value:   DNS_GO_NATIVE,
31
        }
32
)
33

34
// Performs DNS lookup via HTTPS, in case firewall blocks native netgo resolver.
35
func FallbackLookupIP(ctx context.Context, host string) ([]string, error) {
13✔
36
        if net.ParseIP(host) != nil {
14✔
37
                return []string{host}, nil
1✔
38
        }
1✔
39
        // Ref: https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/make-api-requests/dns-json
40
        cf := cloudflare.NewCloudflareAPI()
12✔
41
        data, err := cf.DNSQuery(ctx, cloudflare.DNSParams{Name: host})
12✔
42
        if err != nil {
17✔
43
                return nil, err
5✔
44
        }
5✔
45
        // Look for first valid IP
46
        var resolved []string
7✔
47
        for _, answer := range data.Answer {
14✔
48
                if answer.Type == cloudflare.TypeA || answer.Type == cloudflare.TypeAAAA {
13✔
49
                        resolved = append(resolved, answer.Data)
6✔
50
                }
6✔
51
        }
52
        if len(resolved) == 0 {
8✔
53
                return nil, errors.Errorf("failed to locate valid IP for %s; resolves to %#v", host, data.Answer)
1✔
54
        }
1✔
55
        return resolved, nil
6✔
56
}
57

58
func ResolveCNAME(ctx context.Context, host string) (string, error) {
5✔
59
        // Ref: https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/make-api-requests/dns-json
5✔
60
        cf := cloudflare.NewCloudflareAPI()
5✔
61
        data, err := cf.DNSQuery(ctx, cloudflare.DNSParams{Name: host, Type: cast.Ptr(cloudflare.TypeCNAME)})
5✔
62
        if err != nil {
5✔
63
                return "", err
×
64
        }
×
65
        // Look for first valid IP
66
        for _, answer := range data.Answer {
9✔
67
                if answer.Type == cloudflare.TypeCNAME {
6✔
68
                        return answer.Data, nil
2✔
69
                }
2✔
70
        }
71
        serialized, err := json.MarshalIndent(data.Answer, "", "    ")
3✔
72
        if err != nil {
3✔
73
                // we ignore the error (not great), and use the underlying struct in our error message
×
74
                return "", errors.Errorf("failed to locate appropriate CNAME record for %s; resolves to %+v", host, data.Answer)
×
75
        }
×
76
        return "", errors.Errorf("failed to locate appropriate CNAME record for %s; resolves to %+v", host, serialized)
3✔
77
}
78

79
type DialContextFunc func(context.Context, string, string) (net.Conn, error)
80

81
// Wraps a DialContext with DNS-over-HTTPS as fallback resolver
82
func withFallbackDNS(dialContext DialContextFunc) DialContextFunc {
4✔
83
        dnsOverHttps := func(ctx context.Context, network, address string) (net.Conn, error) {
8✔
84
                host, port, err := net.SplitHostPort(address)
4✔
85
                if err != nil {
5✔
86
                        return nil, errors.Errorf("failed to split host port: %w", err)
1✔
87
                }
1✔
88
                ip, err := FallbackLookupIP(ctx, host)
3✔
89
                if err != nil {
4✔
90
                        return nil, err
1✔
91
                }
1✔
92
                conn, err := dialContext(ctx, network, net.JoinHostPort(ip[0], port))
2✔
93
                if err != nil {
3✔
94
                        return nil, errors.Errorf("failed to dial fallback: %w", err)
1✔
95
                }
1✔
96
                return conn, nil
1✔
97
        }
98
        if DNSResolver.Value == DNS_OVER_HTTPS {
6✔
99
                return dnsOverHttps
2✔
100
        }
2✔
101
        nativeWithFallback := func(ctx context.Context, network, address string) (net.Conn, error) {
4✔
102
                conn, err := dialContext(ctx, network, address)
2✔
103
                // Workaround when pure Go DNS resolver fails https://github.com/golang/go/issues/12524
2✔
104
                if err, ok := err.(net.Error); ok && err.Timeout() {
4✔
105
                        if conn, err := dnsOverHttps(ctx, network, address); err == nil {
3✔
106
                                return conn, nil
1✔
107
                        }
1✔
108
                }
109
                if err != nil {
2✔
110
                        return nil, errors.Errorf("failed to dial native: %w", err)
1✔
111
                }
1✔
112
                return conn, nil
×
113
        }
114
        return nativeWithFallback
2✔
115
}
116

117
func GetSupabase() *supabase.ClientWithResponses {
157✔
118
        clientOnce.Do(func() {
188✔
119
                token, err := LoadAccessTokenFS(afero.NewOsFs())
31✔
120
                if err != nil {
31✔
121
                        log.Fatalln(err)
×
122
                }
×
123
                if t, ok := http.DefaultTransport.(*http.Transport); ok {
31✔
UNCOV
124
                        t.DialContext = withFallbackDNS(t.DialContext)
×
UNCOV
125
                }
×
126
                apiClient, err = supabase.NewClientWithResponses(
31✔
127
                        GetSupabaseAPIHost(),
31✔
128
                        supabase.WithRequestEditorFn(func(ctx context.Context, req *http.Request) error {
194✔
129
                                req.Header.Set("Authorization", "Bearer "+token)
163✔
130
                                req.Header.Set("User-Agent", "SupabaseCLI/"+Version)
163✔
131
                                return nil
163✔
132
                        }),
163✔
133
                )
134
                if err != nil {
31✔
135
                        log.Fatalln(err)
×
136
                }
×
137
        })
138
        return apiClient
157✔
139
}
140

141
// Used by unit tests
142
var DefaultApiHost = CurrentProfile.APIURL
143

144
var RegionMap = map[string]string{
145
        "ap-northeast-1": "Northeast Asia (Tokyo)",
146
        "ap-northeast-2": "Northeast Asia (Seoul)",
147
        "ap-south-1":     "South Asia (Mumbai)",
148
        "ap-southeast-1": "Southeast Asia (Singapore)",
149
        "ap-southeast-2": "Oceania (Sydney)",
150
        "ca-central-1":   "Canada (Central)",
151
        "eu-central-1":   "Central EU (Frankfurt)",
152
        "eu-west-1":      "West EU (Ireland)",
153
        "eu-west-2":      "West EU (London)",
154
        "eu-west-3":      "West EU (Paris)",
155
        "sa-east-1":      "South America (São Paulo)",
156
        "us-east-1":      "East US (North Virginia)",
157
        "us-west-1":      "West US (North California)",
158
        "us-west-2":      "West US (Oregon)",
159
}
160

161
func GetSupabaseAPIHost() string {
33✔
162
        return CurrentProfile.APIURL
33✔
163
}
33✔
164

165
func GetSupabaseDashboardURL() string {
9✔
166
        return CurrentProfile.DashboardURL
9✔
167
}
9✔
168

169
func GetSupabaseHost(projectRef string) string {
59✔
170
        return fmt.Sprintf("%s.%s", projectRef, CurrentProfile.ProjectHost)
59✔
171
}
59✔
172

173
func GetSupabaseDbHost(projectRef string) string {
7✔
174
        return "db." + GetSupabaseHost(projectRef)
7✔
175
}
7✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc