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

supabase / cli / 12212288177

07 Dec 2024 10:53AM UTC coverage: 59.644% (-0.003%) from 59.647%
12212288177

Pull #2952

github

sweatybridge
fix: account for remote config when pushing
Pull Request #2952: fix: account for remote config when pushing

38 of 58 new or added lines in 10 files covered. (65.52%)

8 existing lines in 3 files now uncovered.

6404 of 10737 relevant lines covered (59.64%)

6.07 hits per line

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

74.37
/internal/db/reset/reset.go
1
package reset
2

3
import (
4
        "context"
5
        _ "embed"
6
        "fmt"
7
        "io"
8
        "os"
9
        "strconv"
10
        "strings"
11
        "time"
12

13
        "github.com/cenkalti/backoff/v4"
14
        "github.com/docker/docker/api/types"
15
        "github.com/docker/docker/api/types/container"
16
        "github.com/docker/docker/api/types/network"
17
        "github.com/docker/docker/errdefs"
18
        "github.com/go-errors/errors"
19
        "github.com/jackc/pgconn"
20
        "github.com/jackc/pgerrcode"
21
        "github.com/jackc/pgx/v4"
22
        "github.com/spf13/afero"
23
        "github.com/supabase/cli/internal/db/start"
24
        "github.com/supabase/cli/internal/gen/keys"
25
        "github.com/supabase/cli/internal/migration/apply"
26
        "github.com/supabase/cli/internal/migration/list"
27
        "github.com/supabase/cli/internal/migration/repair"
28
        "github.com/supabase/cli/internal/seed/buckets"
29
        "github.com/supabase/cli/internal/utils"
30
        "github.com/supabase/cli/internal/utils/flags"
31
        "github.com/supabase/cli/pkg/migration"
32
)
33

34
func Run(ctx context.Context, version string, config pgconn.Config, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error {
5✔
35
        if len(version) > 0 {
5✔
36
                if _, err := strconv.Atoi(version); err != nil {
×
37
                        return errors.New(repair.ErrInvalidVersion)
×
38
                }
×
39
                if _, err := repair.GetMigrationFile(version, fsys); err != nil {
×
40
                        return err
×
41
                }
×
42
        }
43
        if !utils.IsLocalDatabase(config) {
7✔
44
                msg := "Do you want to reset the remote database?"
2✔
45
                if shouldReset, err := utils.NewConsole().PromptYesNo(ctx, msg, false); err != nil {
2✔
46
                        return err
×
47
                } else if !shouldReset {
3✔
48
                        return errors.New(context.Canceled)
1✔
49
                }
1✔
50
                return resetRemote(ctx, version, config, fsys, options...)
1✔
51
        }
52
        // Config file is loaded before parsing --linked or --local flags
53
        if err := utils.AssertSupabaseDbIsRunning(); err != nil {
4✔
54
                return err
1✔
55
        }
1✔
56
        // Reset postgres database because extensions (pg_cron, pg_net) require postgres
57
        if err := resetDatabase(ctx, version, fsys, options...); err != nil {
3✔
58
                return err
1✔
59
        }
1✔
60
        // Seed objects from supabase/buckets directory
61
        if resp, err := utils.Docker.ContainerInspect(ctx, utils.StorageId); err == nil {
2✔
62
                if resp.State.Health == nil || resp.State.Health.Status != types.Healthy {
1✔
63
                        if err := start.WaitForHealthyService(ctx, 30*time.Second, utils.StorageId); err != nil {
×
64
                                return err
×
65
                        }
×
66
                }
67
                if err := buckets.Run(ctx, "", false, fsys); err != nil {
1✔
68
                        return err
×
69
                }
×
70
        }
71
        branch := keys.GetGitBranch(fsys)
1✔
72
        fmt.Fprintln(os.Stderr, "Finished "+utils.Aqua("supabase db reset")+" on branch "+utils.Aqua(branch)+".")
1✔
73
        return nil
1✔
74
}
75

76
func resetDatabase(ctx context.Context, version string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error {
2✔
77
        fmt.Fprintln(os.Stderr, "Resetting local database"+toLogMessage(version))
2✔
78
        if utils.Config.Db.MajorVersion <= 14 {
2✔
79
                return resetDatabase14(ctx, version, fsys, options...)
×
80
        }
×
81
        return resetDatabase15(ctx, version, fsys, options...)
2✔
82
}
83

84
func toLogMessage(version string) string {
7✔
85
        if len(version) > 0 {
7✔
86
                return " to version: " + version
×
87
        }
×
88
        return "..."
7✔
89
}
90

91
func resetDatabase14(ctx context.Context, version string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error {
×
92
        if err := recreateDatabase(ctx, options...); err != nil {
×
93
                return err
×
94
        }
×
95
        if err := initDatabase(ctx, options...); err != nil {
×
96
                return err
×
97
        }
×
98
        if err := RestartDatabase(ctx, os.Stderr); err != nil {
×
99
                return err
×
100
        }
×
101
        conn, err := utils.ConnectLocalPostgres(ctx, pgconn.Config{}, options...)
×
102
        if err != nil {
×
103
                return err
×
104
        }
×
105
        defer conn.Close(context.Background())
×
106
        return apply.MigrateAndSeed(ctx, version, conn, fsys)
×
107
}
108

109
func resetDatabase15(ctx context.Context, version string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error {
2✔
110
        if err := utils.Docker.ContainerRemove(ctx, utils.DbId, container.RemoveOptions{Force: true}); err != nil {
3✔
111
                return errors.Errorf("failed to remove container: %w", err)
1✔
112
        }
1✔
113
        if err := utils.Docker.VolumeRemove(ctx, utils.DbId, true); err != nil {
1✔
114
                return errors.Errorf("failed to remove volume: %w", err)
×
115
        }
×
116
        config := start.NewContainerConfig()
1✔
117
        hostConfig := start.NewHostConfig()
1✔
118
        networkingConfig := network.NetworkingConfig{
1✔
119
                EndpointsConfig: map[string]*network.EndpointSettings{
1✔
120
                        utils.NetId: {
1✔
121
                                Aliases: utils.DbAliases,
1✔
122
                        },
1✔
123
                },
1✔
124
        }
1✔
125
        fmt.Fprintln(os.Stderr, "Recreating database...")
1✔
126
        if _, err := utils.DockerStart(ctx, config, hostConfig, networkingConfig, utils.DbId); err != nil {
1✔
127
                return err
×
128
        }
×
129
        if err := start.WaitForHealthyService(ctx, start.HealthTimeout, utils.DbId); err != nil {
1✔
130
                return err
×
131
        }
×
132
        if err := start.SetupLocalDatabase(ctx, version, fsys, os.Stderr, options...); err != nil {
1✔
133
                return err
×
134
        }
×
135
        fmt.Fprintln(os.Stderr, "Restarting containers...")
1✔
136
        return restartServices(ctx)
1✔
137
}
138

139
func initDatabase(ctx context.Context, options ...func(*pgx.ConnConfig)) error {
3✔
140
        conn, err := utils.ConnectLocalPostgres(ctx, pgconn.Config{User: "supabase_admin"}, options...)
3✔
141
        if err != nil {
4✔
142
                return err
1✔
143
        }
1✔
144
        defer conn.Close(context.Background())
2✔
145
        return start.InitSchema14(ctx, conn)
2✔
146
}
147

148
// Recreate postgres database by connecting to template1
149
func recreateDatabase(ctx context.Context, options ...func(*pgx.ConnConfig)) error {
5✔
150
        conn, err := utils.ConnectLocalPostgres(ctx, pgconn.Config{User: "supabase_admin", Database: "template1"}, options...)
5✔
151
        if err != nil {
6✔
152
                return err
1✔
153
        }
1✔
154
        defer conn.Close(context.Background())
4✔
155
        if err := DisconnectClients(ctx, conn); err != nil {
6✔
156
                return err
2✔
157
        }
2✔
158
        // We are not dropping roles here because they are cluster level entities. Use stop && start instead.
159
        sql := migration.MigrationFile{
2✔
160
                Statements: []string{
2✔
161
                        "DROP DATABASE IF EXISTS postgres WITH (FORCE)",
2✔
162
                        "CREATE DATABASE postgres WITH OWNER postgres",
2✔
163
                        "DROP DATABASE IF EXISTS _supabase WITH (FORCE)",
2✔
164
                        "CREATE DATABASE _supabase WITH OWNER postgres",
2✔
165
                },
2✔
166
        }
2✔
167
        return sql.ExecBatch(ctx, conn)
2✔
168
}
169

170
const (
171
        TERMINATE_BACKENDS      = "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname IN ('postgres', '_supabase')"
172
        COUNT_REPLICATION_SLOTS = "SELECT COUNT(*) FROM pg_replication_slots WHERE database IN ('postgres', '_supabase')"
173
)
174

175
func DisconnectClients(ctx context.Context, conn *pgx.Conn) error {
9✔
176
        // Must be executed separately because looping in transaction is unsupported
9✔
177
        // https://dba.stackexchange.com/a/11895
9✔
178
        disconn := migration.MigrationFile{
9✔
179
                Statements: []string{
9✔
180
                        "ALTER DATABASE postgres ALLOW_CONNECTIONS false",
9✔
181
                        "ALTER DATABASE _supabase ALLOW_CONNECTIONS false",
9✔
182
                        TERMINATE_BACKENDS,
9✔
183
                },
9✔
184
        }
9✔
185
        if err := disconn.ExecBatch(ctx, conn); err != nil {
13✔
186
                var pgErr *pgconn.PgError
4✔
187
                if errors.As(err, &pgErr) && pgErr.Code != pgerrcode.InvalidCatalogName {
6✔
188
                        return errors.Errorf("failed to disconnect clients: %w", err)
2✔
189
                }
2✔
190
        }
191
        // Wait for WAL senders to drop their replication slots
192
        policy := start.NewBackoffPolicy(ctx, 10*time.Second)
7✔
193
        waitForDrop := func() error {
14✔
194
                var count int
7✔
195
                if err := conn.QueryRow(ctx, COUNT_REPLICATION_SLOTS).Scan(&count); err != nil {
9✔
196
                        err = errors.Errorf("failed to count replication slots: %w", err)
2✔
197
                        return &backoff.PermanentError{Err: err}
2✔
198
                } else if count > 0 {
7✔
199
                        return errors.Errorf("replication slots still active: %d", count)
×
200
                }
×
201
                return nil
5✔
202
        }
203
        return backoff.Retry(waitForDrop, policy)
7✔
204
}
205

206
func RestartDatabase(ctx context.Context, w io.Writer) error {
7✔
207
        fmt.Fprintln(w, "Restarting containers...")
7✔
208
        // Some extensions must be manually restarted after pg_terminate_backend
7✔
209
        // Ref: https://github.com/citusdata/pg_cron/issues/99
7✔
210
        if err := utils.Docker.ContainerRestart(ctx, utils.DbId, container.StopOptions{}); err != nil {
11✔
211
                return errors.Errorf("failed to restart container: %w", err)
4✔
212
        }
4✔
213
        if err := start.WaitForHealthyService(ctx, start.HealthTimeout, utils.DbId); err != nil {
4✔
214
                return err
1✔
215
        }
1✔
216
        return restartServices(ctx)
2✔
217
}
218

219
func restartServices(ctx context.Context) error {
3✔
220
        // No need to restart PostgREST because it automatically reconnects and listens for schema changes
3✔
221
        services := listServicesToRestart()
3✔
222
        result := utils.WaitAll(services, func(id string) error {
15✔
223
                if err := utils.Docker.ContainerRestart(ctx, id, container.StopOptions{}); err != nil && !errdefs.IsNotFound(err) {
15✔
224
                        return errors.Errorf("failed to restart %s: %w", id, err)
3✔
225
                }
3✔
226
                return nil
9✔
227
        })
228
        // Do not wait for service healthy as those services may be excluded from starting
229
        return errors.Join(result...)
3✔
230
}
231

232
func listServicesToRestart() []string {
5✔
233
        return []string{utils.StorageId, utils.GotrueId, utils.RealtimeId, utils.PoolerId}
5✔
234
}
5✔
235

236
func resetRemote(ctx context.Context, version string, config pgconn.Config, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error {
5✔
237
        fmt.Fprintln(os.Stderr, "Resetting remote database"+toLogMessage(version))
5✔
238
        conn, err := utils.ConnectByConfigStream(ctx, config, io.Discard, options...)
5✔
239
        if err != nil {
7✔
240
                return err
2✔
241
        }
2✔
242
        defer conn.Close(context.Background())
3✔
243
        if err := migration.DropUserSchemas(ctx, conn); err != nil {
4✔
244
                return err
1✔
245
        }
1✔
246
        migrations, err := list.LoadPartialMigrations(version, fsys)
2✔
247
        if err != nil {
2✔
NEW
248
                return err
×
NEW
249
        }
×
250
        if err := migration.ApplyMigrations(ctx, migrations, conn, afero.NewIOFS(fsys)); err != nil {
2✔
NEW
251
                return err
×
NEW
252
        }
×
253
        remote, _ := utils.Config.GetRemoteByProjectRef(flags.ProjectRef)
2✔
254
        if !remote.Db.Seed.Enabled {
3✔
255
                fmt.Fprintln(os.Stderr, "Skipping seed because it is disabled in config.toml for project:", remote.ProjectId)
1✔
256
                return nil
1✔
257
        } else if !utils.Config.Db.Seed.Enabled {
2✔
NEW
258
                // Skip because --no-seed flag is set
×
NEW
259
                return nil
×
NEW
260
        }
×
261
        seeds, err := migration.GetPendingSeeds(ctx, remote.Db.Seed.SqlPaths, conn, afero.NewIOFS(fsys))
1✔
262
        if err != nil {
1✔
NEW
263
                return err
×
NEW
264
        }
×
265
        return migration.SeedData(ctx, seeds, conn, afero.NewIOFS(fsys))
1✔
266
}
267

268
func LikeEscapeSchema(schemas []string) (result []string) {
24✔
269
        // Treat _ as literal, * as any character
24✔
270
        replacer := strings.NewReplacer("_", `\_`, "*", "%")
24✔
271
        for _, sch := range schemas {
522✔
272
                result = append(result, replacer.Replace(sch))
498✔
273
        }
498✔
274
        return result
24✔
275
}
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