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

supabase / cli / 12005516982

25 Nov 2024 07:49AM UTC coverage: 59.57% (+0.02%) from 59.552%
12005516982

Pull #2904

github

sweatybridge
chore: refactor terminate backend query
Pull Request #2904: fix(reset): ensure _supabase connections disconnect before reset

29 of 31 new or added lines in 3 files covered. (93.55%)

11 existing lines in 2 files now uncovered.

6396 of 10737 relevant lines covered (59.57%)

6.07 hits per line

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

71.3
/internal/utils/misc.go
1
package utils
2

3
import (
4
        "context"
5
        _ "embed"
6
        "fmt"
7
        "net"
8
        "os"
9
        "path/filepath"
10
        "regexp"
11
        "time"
12

13
        "github.com/docker/docker/client"
14
        "github.com/go-errors/errors"
15
        "github.com/go-git/go-git/v5"
16
        "github.com/spf13/afero"
17
        "github.com/spf13/viper"
18
)
19

20
// Assigned using `-ldflags` https://stackoverflow.com/q/11354518
21
var (
22
        Version   string
23
        SentryDsn string
24
)
25

26
func ShortContainerImageName(imageName string) string {
49✔
27
        matches := ImageNamePattern.FindStringSubmatch(imageName)
49✔
28
        if len(matches) < 2 {
49✔
29
                return imageName
×
30
        }
×
31
        return matches[1]
49✔
32
}
33

34
const SuggestDebugFlag = "Try rerunning the command with --debug to troubleshoot the error."
35

36
var (
37
        CmdSuggestion string
38
        CurrentDirAbs string
39

40
        // pg_dumpall --globals-only --no-role-passwords --dbname $DB_URL \
41
        // | sed '/^CREATE ROLE postgres;/d' \
42
        // | sed '/^ALTER ROLE postgres WITH /d' \
43
        // | sed "/^ALTER ROLE .* WITH .* LOGIN /s/;$/ PASSWORD 'postgres';/"
44
        //go:embed templates/globals.sql
45
        GlobalsSql string
46

47
        ProjectRefPattern  = regexp.MustCompile(`^[a-z]{20}$`)
48
        UUIDPattern        = regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)
49
        ProjectHostPattern = regexp.MustCompile(`^(db\.)([a-z]{20})\.supabase\.(co|red)$`)
50
        BranchNamePattern  = regexp.MustCompile(`[[:word:]-]+`)
51
        FuncSlugPattern    = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_-]*$`)
52
        ImageNamePattern   = regexp.MustCompile(`\/(.*):`)
53

54
        // These schemas are ignored from db diff and db dump
55
        PgSchemas = []string{
56
                "information_schema",
57
                "pg_*", // Wildcard pattern follows pg_dump
58
        }
59
        // Initialised by postgres image and owned by postgres role
60
        InternalSchemas = append([]string{
61
                "_analytics",
62
                "_realtime",
63
                "_supavisor",
64
                "auth",
65
                "extensions",
66
                "pgbouncer",
67
                "realtime",
68
                "storage",
69
                "supabase_functions",
70
                "supabase_migrations",
71
                // Owned by extensions
72
                "cron",
73
                "dbdev",
74
                "graphql",
75
                "graphql_public",
76
                "net",
77
                "pgsodium",
78
                "pgsodium_masks",
79
                "pgtle",
80
                "repack",
81
                "tiger",
82
                "tiger_data",
83
                "timescaledb_*",
84
                "_timescaledb_*",
85
                "topology",
86
                "vault",
87
        }, PgSchemas...)
88
        ReservedRoles = []string{
89
                "anon",
90
                "authenticated",
91
                "authenticator",
92
                "dashboard_user",
93
                "pgbouncer",
94
                "postgres",
95
                "service_role",
96
                "supabase_admin",
97
                "supabase_auth_admin",
98
                "supabase_functions_admin",
99
                "supabase_read_only_user",
100
                "supabase_realtime_admin",
101
                "supabase_replication_admin",
102
                "supabase_storage_admin",
103
                // Managed by extensions
104
                "pgsodium_keyholder",
105
                "pgsodium_keyiduser",
106
                "pgsodium_keymaker",
107
                "pgtle_admin",
108
        }
109
        AllowedConfigs = []string{
110
                // Ref: https://github.com/supabase/postgres/blob/develop/ansible/files/postgresql_config/supautils.conf.j2#L10
111
                "pgaudit.*",
112
                "pgrst.*",
113
                "session_replication_role",
114
                "statement_timeout",
115
                "track_io_timing",
116
        }
117

118
        SupabaseDirPath       = "supabase"
119
        ConfigPath            = filepath.Join(SupabaseDirPath, "config.toml")
120
        GitIgnorePath         = filepath.Join(SupabaseDirPath, ".gitignore")
121
        TempDir               = filepath.Join(SupabaseDirPath, ".temp")
122
        ImportMapsDir         = filepath.Join(TempDir, "import_maps")
123
        ProjectRefPath        = filepath.Join(TempDir, "project-ref")
124
        PoolerUrlPath         = filepath.Join(TempDir, "pooler-url")
125
        PostgresVersionPath   = filepath.Join(TempDir, "postgres-version")
126
        GotrueVersionPath     = filepath.Join(TempDir, "gotrue-version")
127
        RestVersionPath       = filepath.Join(TempDir, "rest-version")
128
        StorageVersionPath    = filepath.Join(TempDir, "storage-version")
129
        StudioVersionPath     = filepath.Join(TempDir, "studio-version")
130
        PgmetaVersionPath     = filepath.Join(TempDir, "pgmeta-version")
131
        PoolerVersionPath     = filepath.Join(TempDir, "pooler-version")
132
        RealtimeVersionPath   = filepath.Join(TempDir, "realtime-version")
133
        CliVersionPath        = filepath.Join(TempDir, "cli-latest")
134
        CurrBranchPath        = filepath.Join(SupabaseDirPath, ".branches", "_current_branch")
135
        SchemasDir            = filepath.Join(SupabaseDirPath, "schemas")
136
        MigrationsDir         = filepath.Join(SupabaseDirPath, "migrations")
137
        FunctionsDir          = filepath.Join(SupabaseDirPath, "functions")
138
        FallbackImportMapPath = filepath.Join(FunctionsDir, "import_map.json")
139
        FallbackEnvFilePath   = filepath.Join(FunctionsDir, ".env")
140
        DbTestsDir            = filepath.Join(SupabaseDirPath, "tests")
141
        CustomRolesPath       = filepath.Join(SupabaseDirPath, "roles.sql")
142

143
        ErrNotLinked   = errors.Errorf("Cannot find project ref. Have you run %s?", Aqua("supabase link"))
144
        ErrInvalidRef  = errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
145
        ErrInvalidSlug = errors.New("Invalid Function name. Must start with at least one letter, and only include alphanumeric characters, underscores, and hyphens. (^[A-Za-z][A-Za-z0-9_-]*$)")
146
        ErrNotRunning  = errors.Errorf("%s is not running.", Aqua("supabase start"))
147
)
148

149
func GetCurrentTimestamp() string {
7✔
150
        // Magic number: https://stackoverflow.com/q/45160822.
7✔
151
        return time.Now().UTC().Format("20060102150405")
7✔
152
}
7✔
153

154
func GetCurrentBranchFS(fsys afero.Fs) (string, error) {
11✔
155
        branch, err := afero.ReadFile(fsys, CurrBranchPath)
11✔
156
        if err != nil {
19✔
157
                return "", errors.Errorf("failed to load current branch: %w", err)
8✔
158
        }
8✔
159

160
        return string(branch), nil
3✔
161
}
162

163
func AssertSupabaseDbIsRunning() error {
25✔
164
        return AssertServiceIsRunning(context.Background(), DbId)
25✔
165
}
25✔
166

167
func AssertServiceIsRunning(ctx context.Context, containerId string) error {
25✔
168
        if _, err := Docker.ContainerInspect(ctx, containerId); err != nil {
34✔
169
                if client.IsErrNotFound(err) {
15✔
170
                        return errors.New(ErrNotRunning)
6✔
171
                }
6✔
172
                if client.IsErrConnectionFailed(err) {
5✔
173
                        CmdSuggestion = suggestDockerInstall
2✔
174
                }
2✔
175
                return errors.Errorf("failed to inspect service: %w", err)
3✔
176
        }
177
        return nil
16✔
178
}
179

180
func IsGitRepo() bool {
11✔
181
        opts := &git.PlainOpenOptions{DetectDotGit: true}
11✔
182
        _, err := git.PlainOpenWithOptions(".", opts)
11✔
183
        return err == nil
11✔
184
}
11✔
185

186
// If the `os.Getwd()` is within a supabase project, this will return
187
// the root of the given project as the current working directory.
188
// Otherwise, the `os.Getwd()` is kept as is.
189
func getProjectRoot(absPath string, fsys afero.Fs) string {
4✔
190
        for cwd := absPath; ; cwd = filepath.Dir(cwd) {
27✔
191
                path := filepath.Join(cwd, ConfigPath)
23✔
192
                // Treat all errors as file not exists
23✔
193
                if isSupaProj, err := afero.Exists(fsys, path); isSupaProj {
25✔
194
                        return cwd
2✔
195
                } else if err != nil && !errors.Is(err, os.ErrNotExist) {
24✔
196
                        logger := GetDebugLogger()
1✔
197
                        fmt.Fprintln(logger, err)
1✔
198
                }
1✔
199
                if isRootDirectory(cwd) {
23✔
200
                        break
2✔
201
                }
202
        }
203
        return absPath
2✔
204
}
205

206
func isRootDirectory(cleanPath string) bool {
21✔
207
        // A cleaned path only ends with separator if it is root
21✔
208
        return os.IsPathSeparator(cleanPath[len(cleanPath)-1])
21✔
209
}
21✔
210

UNCOV
211
func ChangeWorkDir(fsys afero.Fs) error {
×
212
        // Track the original workdir before changing to project root
×
213
        if !filepath.IsAbs(CurrentDirAbs) {
×
214
                var err error
×
215
                if CurrentDirAbs, err = os.Getwd(); err != nil {
×
216
                        return errors.Errorf("failed to get current directory: %w", err)
×
217
                }
×
218
        }
UNCOV
219
        workdir := viper.GetString("WORKDIR")
×
220
        if len(workdir) == 0 {
×
221
                workdir = getProjectRoot(CurrentDirAbs, fsys)
×
222
        }
×
223
        if err := os.Chdir(workdir); err != nil {
×
224
                return errors.Errorf("failed to change workdir: %w", err)
×
225
        }
×
226
        if cwd, err := os.Getwd(); err == nil && cwd != CurrentDirAbs {
×
227
                fmt.Fprintln(os.Stderr, "Using workdir", Bold(workdir))
×
228
        }
×
229
        return nil
×
230
}
231

232
func IsBranchNameReserved(branch string) bool {
12✔
233
        switch branch {
12✔
234
        case "_current_branch", "main", "postgres", "template0", "template1":
3✔
235
                return true
3✔
236
        default:
9✔
237
                return false
9✔
238
        }
239
}
240

UNCOV
241
func MkdirIfNotExist(path string) error {
×
242
        return MkdirIfNotExistFS(afero.NewOsFs(), path)
×
243
}
×
244

245
func MkdirIfNotExistFS(fsys afero.Fs, path string) error {
105✔
246
        if err := fsys.MkdirAll(path, 0755); err != nil && !errors.Is(err, os.ErrExist) {
116✔
247
                return errors.Errorf("failed to mkdir: %w", err)
11✔
248
        }
11✔
249

250
        return nil
94✔
251
}
252

253
func WriteFile(path string, contents []byte, fsys afero.Fs) error {
21✔
254
        if err := MkdirIfNotExistFS(fsys, filepath.Dir(path)); err != nil {
25✔
255
                return err
4✔
256
        }
4✔
257
        if err := afero.WriteFile(fsys, path, contents, 0644); err != nil {
18✔
258
                return errors.Errorf("failed to write file: %w", err)
1✔
259
        }
1✔
260
        return nil
16✔
261
}
262

UNCOV
263
func AssertSupabaseCliIsSetUpFS(fsys afero.Fs) error {
×
264
        if _, err := fsys.Stat(ConfigPath); errors.Is(err, os.ErrNotExist) {
×
265
                return errors.Errorf("Cannot find %s in the current directory. Have you set up the project with %s?", Bold(ConfigPath), Aqua("supabase init"))
×
266
        } else if err != nil {
×
267
                return errors.Errorf("failed to read config file: %w", err)
×
268
        }
×
269

UNCOV
270
        return nil
×
271
}
272

273
func AssertProjectRefIsValid(projectRef string) error {
2✔
274
        if !ProjectRefPattern.MatchString(projectRef) {
3✔
275
                return errors.New(ErrInvalidRef)
1✔
276
        }
1✔
277
        return nil
1✔
278
}
279

280
func ValidateFunctionSlug(slug string) error {
19✔
281
        if !FuncSlugPattern.MatchString(slug) {
23✔
282
                return errors.New(ErrInvalidSlug)
4✔
283
        }
4✔
284

285
        return nil
15✔
286
}
287

288
func GetHostname() string {
90✔
289
        host := Docker.DaemonHost()
90✔
290
        if parsed, err := client.ParseHostURL(host); err == nil && parsed.Scheme == "tcp" {
90✔
UNCOV
291
                if host, _, err := net.SplitHostPort(parsed.Host); err == nil {
×
292
                        return host
×
293
                }
×
294
        }
295
        return "127.0.0.1"
90✔
296
}
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