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

supabase / cli / 11639769758

02 Nov 2024 04:07AM UTC coverage: 59.803%. Remained the same
11639769758

Pull #2825

github

sweatybridge
chore: update generated v1 api client
Pull Request #2825: chore: update generated v1 api client

2 of 4 new or added lines in 2 files covered. (50.0%)

5 existing lines in 1 file now uncovered.

6375 of 10660 relevant lines covered (59.8%)

6.08 hits per line

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

76.56
/internal/link/link.go
1
package link
2

3
import (
4
        "context"
5
        "fmt"
6
        "net/http"
7
        "os"
8
        "strconv"
9
        "strings"
10
        "sync"
11

12
        "github.com/go-errors/errors"
13
        "github.com/jackc/pgconn"
14
        "github.com/jackc/pgx/v4"
15
        "github.com/spf13/afero"
16
        "github.com/spf13/viper"
17
        "github.com/supabase/cli/internal/utils"
18
        "github.com/supabase/cli/internal/utils/credentials"
19
        "github.com/supabase/cli/internal/utils/flags"
20
        "github.com/supabase/cli/internal/utils/tenant"
21
        "github.com/supabase/cli/pkg/api"
22
        "github.com/supabase/cli/pkg/cast"
23
        cliConfig "github.com/supabase/cli/pkg/config"
24
        "github.com/supabase/cli/pkg/diff"
25
        "github.com/supabase/cli/pkg/migration"
26
)
27

28
func Run(ctx context.Context, projectRef string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error {
3✔
29
        original, err := cliConfig.ToTomlBytes(map[string]interface{}{
3✔
30
                "api": utils.Config.Api,
3✔
31
                "db":  utils.Config.Db,
3✔
32
        })
3✔
33
        if err != nil {
3✔
34
                fmt.Fprintln(utils.GetDebugLogger(), err)
×
35
        }
×
36

37
        if err := checkRemoteProjectStatus(ctx, projectRef); err != nil {
3✔
38
                return err
×
39
        }
×
40

41
        // 1. Check service config
42
        keys, err := tenant.GetApiKeys(ctx, projectRef)
3✔
43
        if err != nil {
3✔
44
                return err
×
45
        }
×
46
        LinkServices(ctx, projectRef, keys.Anon, fsys)
3✔
47

3✔
48
        // 2. Check database connection
3✔
49
        config := flags.GetDbConfigOptionalPassword(projectRef)
3✔
50
        if len(config.Password) > 0 {
5✔
51
                if err := linkDatabase(ctx, config, options...); err != nil {
3✔
52
                        return err
1✔
53
                }
1✔
54
                // Save database password
55
                if err := credentials.StoreProvider.Set(projectRef, config.Password); err != nil {
1✔
56
                        fmt.Fprintln(os.Stderr, "Failed to save database password:", err)
×
57
                }
×
58
        }
59

60
        // 3. Save project ref
61
        if err := utils.WriteFile(utils.ProjectRefPath, []byte(projectRef), fsys); err != nil {
3✔
62
                return err
1✔
63
        }
1✔
64
        fmt.Fprintln(os.Stdout, "Finished "+utils.Aqua("supabase link")+".")
1✔
65

1✔
66
        // 4. Suggest config update
1✔
67
        updated, err := cliConfig.ToTomlBytes(map[string]interface{}{
1✔
68
                "api": utils.Config.Api,
1✔
69
                "db":  utils.Config.Db,
1✔
70
        })
1✔
71
        if err != nil {
1✔
72
                fmt.Fprintln(utils.GetDebugLogger(), err)
×
73
        }
×
74

75
        if lineDiff := diff.Diff(utils.ConfigPath, original, projectRef, updated); len(lineDiff) > 0 {
2✔
76
                fmt.Fprintln(os.Stderr, utils.Yellow("WARNING:"), "Local config differs from linked project. Try updating", utils.Bold(utils.ConfigPath))
1✔
77
                fmt.Println(string(lineDiff))
1✔
78
        }
1✔
79
        return nil
1✔
80
}
81

82
func LinkServices(ctx context.Context, projectRef, anonKey string, fsys afero.Fs) {
3✔
83
        // Ignore non-fatal errors linking services
3✔
84
        var wg sync.WaitGroup
3✔
85
        wg.Add(6)
3✔
86
        go func() {
6✔
87
                defer wg.Done()
3✔
88
                if err := linkDatabaseVersion(ctx, projectRef, fsys); err != nil && viper.GetBool("DEBUG") {
3✔
89
                        fmt.Fprintln(os.Stderr, err)
×
90
                }
×
91
        }()
92
        go func() {
6✔
93
                defer wg.Done()
3✔
94
                if err := linkPostgrest(ctx, projectRef); err != nil && viper.GetBool("DEBUG") {
3✔
95
                        fmt.Fprintln(os.Stderr, err)
×
96
                }
×
97
        }()
98
        go func() {
6✔
99
                defer wg.Done()
3✔
100
                if err := linkPooler(ctx, projectRef, fsys); err != nil && viper.GetBool("DEBUG") {
3✔
101
                        fmt.Fprintln(os.Stderr, err)
×
102
                }
×
103
        }()
104
        api := tenant.NewTenantAPI(ctx, projectRef, anonKey)
3✔
105
        go func() {
6✔
106
                defer wg.Done()
3✔
107
                if err := linkPostgrestVersion(ctx, api, fsys); err != nil && viper.GetBool("DEBUG") {
3✔
108
                        fmt.Fprintln(os.Stderr, err)
×
109
                }
×
110
        }()
111
        go func() {
6✔
112
                defer wg.Done()
3✔
113
                if err := linkGotrueVersion(ctx, api, fsys); err != nil && viper.GetBool("DEBUG") {
3✔
114
                        fmt.Fprintln(os.Stderr, err)
×
115
                }
×
116
        }()
117
        go func() {
6✔
118
                defer wg.Done()
3✔
119
                if err := linkStorageVersion(ctx, api, fsys); err != nil && viper.GetBool("DEBUG") {
3✔
120
                        fmt.Fprintln(os.Stderr, err)
×
121
                }
×
122
        }()
123
        wg.Wait()
3✔
124
}
125

126
func linkPostgrest(ctx context.Context, projectRef string) error {
7✔
127
        resp, err := utils.GetSupabase().V1GetPostgrestServiceConfigWithResponse(ctx, projectRef)
7✔
128
        if err != nil {
10✔
129
                return errors.Errorf("failed to get postgrest config: %w", err)
3✔
130
        }
3✔
131
        if resp.JSON200 == nil {
5✔
132
                return errors.Errorf("%w: %s", tenant.ErrAuthToken, string(resp.Body))
1✔
133
        }
1✔
134
        updateApiConfig(*resp.JSON200)
3✔
135
        return nil
3✔
136
}
137

138
func linkPostgrestVersion(ctx context.Context, api tenant.TenantAPI, fsys afero.Fs) error {
3✔
139
        version, err := api.GetPostgrestVersion(ctx)
3✔
140
        if err != nil {
5✔
141
                return err
2✔
142
        }
2✔
143
        return utils.WriteFile(utils.RestVersionPath, []byte(version), fsys)
1✔
144
}
145

146
func updateApiConfig(config api.PostgrestConfigWithJWTSecretResponse) {
3✔
147
        utils.Config.Api.MaxRows = cast.IntToUint(config.MaxRows)
3✔
148
        utils.Config.Api.ExtraSearchPath = readCsv(config.DbExtraSearchPath)
3✔
149
        utils.Config.Api.Schemas = readCsv(config.DbSchema)
3✔
150
}
3✔
151

152
func readCsv(line string) []string {
6✔
153
        var result []string
6✔
154
        tokens := strings.Split(line, ",")
6✔
155
        for _, t := range tokens {
14✔
156
                trimmed := strings.TrimSpace(t)
8✔
157
                if len(trimmed) > 0 {
12✔
158
                        result = append(result, trimmed)
4✔
159
                }
4✔
160
        }
161
        return result
6✔
162
}
163

164
func linkGotrueVersion(ctx context.Context, api tenant.TenantAPI, fsys afero.Fs) error {
3✔
165
        version, err := api.GetGotrueVersion(ctx)
3✔
166
        if err != nil {
5✔
167
                return err
2✔
168
        }
2✔
169
        return utils.WriteFile(utils.GotrueVersionPath, []byte(version), fsys)
1✔
170
}
171

172
func linkStorageVersion(ctx context.Context, api tenant.TenantAPI, fsys afero.Fs) error {
3✔
173
        version, err := api.GetStorageVersion(ctx)
3✔
174
        if err != nil {
5✔
175
                return err
2✔
176
        }
2✔
177
        return utils.WriteFile(utils.StorageVersionPath, []byte(version), fsys)
1✔
178
}
179

180
func linkDatabase(ctx context.Context, config pgconn.Config, options ...func(*pgx.ConnConfig)) error {
6✔
181
        conn, err := utils.ConnectByConfig(ctx, config, options...)
6✔
182
        if err != nil {
8✔
183
                return err
2✔
184
        }
2✔
185
        defer conn.Close(context.Background())
4✔
186
        updatePostgresConfig(conn)
4✔
187
        // If `schema_migrations` doesn't exist on the remote database, create it.
4✔
188
        if err := migration.CreateMigrationTable(ctx, conn); err != nil {
5✔
189
                return err
1✔
190
        }
1✔
191
        return migration.CreateSeedTable(ctx, conn)
3✔
192
}
193

194
func linkDatabaseVersion(ctx context.Context, projectRef string, fsys afero.Fs) error {
3✔
195
        version, err := tenant.GetDatabaseVersion(ctx, projectRef)
3✔
196
        if err != nil {
5✔
197
                return err
2✔
198
        }
2✔
199
        return utils.WriteFile(utils.PostgresVersionPath, []byte(version), fsys)
1✔
200
}
201

202
func updatePostgresConfig(conn *pgx.Conn) {
4✔
203
        serverVersion := conn.PgConn().ParameterStatus("server_version")
4✔
204
        // Safe to assume that supported Postgres version is 10.0 <= n < 100.0
4✔
205
        majorDigits := len(serverVersion)
4✔
206
        if majorDigits > 2 {
7✔
207
                majorDigits = 2
3✔
208
        }
3✔
209
        // Treat error as unchanged
210
        if dbMajorVersion, err := strconv.ParseUint(serverVersion[:majorDigits], 10, 7); err == nil {
7✔
211
                utils.Config.Db.MajorVersion = uint(dbMajorVersion)
3✔
212
        }
3✔
213
}
214

215
func linkPooler(ctx context.Context, projectRef string, fsys afero.Fs) error {
3✔
216
        resp, err := utils.GetSupabase().V1GetSupavisorConfigWithResponse(ctx, projectRef)
3✔
217
        if err != nil {
6✔
218
                return errors.Errorf("failed to get pooler config: %w", err)
3✔
219
        }
3✔
220
        if resp.JSON200 == nil {
×
221
                return errors.Errorf("%w: %s", tenant.ErrAuthToken, string(resp.Body))
×
222
        }
×
223
        for _, config := range *resp.JSON200 {
×
224
                if config.DatabaseType == api.PRIMARY {
×
225
                        updatePoolerConfig(config)
×
226
                }
×
227
        }
228
        return utils.WriteFile(utils.PoolerUrlPath, []byte(utils.Config.Db.Pooler.ConnectionString), fsys)
×
229
}
230

231
func updatePoolerConfig(config api.SupavisorConfigResponse) {
×
232
        utils.Config.Db.Pooler.ConnectionString = config.ConnectionString
×
233
        utils.Config.Db.Pooler.PoolMode = cliConfig.PoolMode(config.PoolMode)
×
234
        if config.DefaultPoolSize != nil {
×
NEW
235
                utils.Config.Db.Pooler.DefaultPoolSize = cast.IntToUint(*config.DefaultPoolSize)
×
236
        }
×
237
        if config.MaxClientConn != nil {
×
NEW
238
                utils.Config.Db.Pooler.MaxClientConn = cast.IntToUint(*config.MaxClientConn)
×
239
        }
×
240
}
241

242
var errProjectPaused = errors.New("project is paused")
243

244
func checkRemoteProjectStatus(ctx context.Context, projectRef string) error {
5✔
245
        resp, err := utils.GetSupabase().V1GetProjectWithResponse(ctx, projectRef)
5✔
246
        if err != nil {
5✔
247
                return errors.Errorf("failed to retrieve remote project status: %w", err)
×
248
        }
×
249
        switch resp.StatusCode() {
5✔
250
        case http.StatusNotFound:
1✔
251
                // Ignore not found error to support linking branch projects
1✔
252
                return nil
1✔
253
        case http.StatusOK:
4✔
254
                // resp.JSON200 is not nil, proceed
255
        default:
×
256
                return errors.New("Unexpected error retrieving remote project status: " + string(resp.Body))
×
257
        }
258

259
        switch resp.JSON200.Status {
4✔
260
        case api.V1ProjectResponseStatusINACTIVE:
1✔
261
                utils.CmdSuggestion = fmt.Sprintf("An admin must unpause it from the Supabase dashboard at %s", utils.Aqua(fmt.Sprintf("%s/project/%s", utils.GetSupabaseDashboardURL(), projectRef)))
1✔
262
                return errors.New(errProjectPaused)
1✔
263
        case api.V1ProjectResponseStatusACTIVEHEALTHY:
3✔
264
                // Project is in the desired state, do nothing
265
        default:
×
266
                fmt.Fprintf(os.Stderr, "%s: Project status is %s instead of Active Healthy. Some operations might fail.\n", utils.Yellow("WARNING"), resp.JSON200.Status)
×
267
        }
268

269
        return nil
3✔
270
}
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