Skip to content

Commit 8611ace

Browse files
authored
feat(config): experimental config webhooks (#2794)
1 parent 78bc7f7 commit 8611ace

File tree

3 files changed

+191
-5
lines changed

3 files changed

+191
-5
lines changed

pkg/config/config.go

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -388,12 +388,17 @@ type (
388388
VectorPort uint16 `toml:"vector_port"`
389389
}
390390

391+
webhooks struct {
392+
Enabled bool `toml:"enabled"`
393+
}
394+
391395
experimental struct {
392-
OrioleDBVersion string `toml:"orioledb_version"`
393-
S3Host string `toml:"s3_host"`
394-
S3Region string `toml:"s3_region"`
395-
S3AccessKey string `toml:"s3_access_key"`
396-
S3SecretKey string `toml:"s3_secret_key"`
396+
OrioleDBVersion string `toml:"orioledb_version"`
397+
S3Host string `toml:"s3_host"`
398+
S3Region string `toml:"s3_region"`
399+
S3AccessKey string `toml:"s3_access_key"`
400+
S3SecretKey string `toml:"s3_secret_key"`
401+
Webhooks *webhooks `toml:"webhooks"`
397402
}
398403
)
399404

@@ -986,6 +991,9 @@ func (c *baseConfig) Validate(fsys fs.FS) error {
986991
return errors.Errorf("Invalid config for analytics.backend. Must be one of: %v", allowed)
987992
}
988993
}
994+
if err := c.Experimental.validateWebhooks(); err != nil {
995+
return err
996+
}
989997
return nil
990998
}
991999

@@ -1351,3 +1359,12 @@ func ToTomlBytes(config any) ([]byte, error) {
13511359
}
13521360
return buf.Bytes(), nil
13531361
}
1362+
1363+
func (e *experimental) validateWebhooks() error {
1364+
if e.Webhooks != nil {
1365+
if !e.Webhooks.Enabled {
1366+
return errors.Errorf("Webhooks cannot be deactivated. [experimental.webhooks] enabled can either be true or left undefined")
1367+
}
1368+
}
1369+
return nil
1370+
}

pkg/config/updater.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ func (u *ConfigUpdater) UpdateRemoteConfig(ctx context.Context, remote baseConfi
2424
if err := u.UpdateDbConfig(ctx, remote.ProjectId, remote.Db); err != nil {
2525
return err
2626
}
27+
if err := u.UpdateExperimentalConfig(ctx, remote.ProjectId, remote.Experimental); err != nil {
28+
return err
29+
}
2730
return nil
2831
}
2932

@@ -87,3 +90,16 @@ func (u *ConfigUpdater) UpdateDbConfig(ctx context.Context, projectRef string, c
8790
}
8891
return nil
8992
}
93+
94+
func (u *ConfigUpdater) UpdateExperimentalConfig(ctx context.Context, projectRef string, exp experimental) error {
95+
if exp.Webhooks != nil && exp.Webhooks.Enabled {
96+
fmt.Fprintln(os.Stderr, "Enabling webhooks for the project...")
97+
98+
if resp, err := u.client.V1EnableDatabaseWebhookWithResponse(ctx, projectRef); err != nil {
99+
return errors.Errorf("failed to enable webhooks: %w", err)
100+
} else if resp.StatusCode() < 200 || resp.StatusCode() >= 300 {
101+
return errors.Errorf("unexpected enable webhook status %d: %s", resp.StatusCode(), string(resp.Body))
102+
}
103+
}
104+
return nil
105+
}

pkg/config/updater_test.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/stretchr/testify/assert"
1010
"github.com/stretchr/testify/require"
1111
v1API "github.com/supabase/cli/pkg/api"
12+
"github.com/supabase/cli/pkg/cast"
1213
)
1314

1415
func TestUpdateApi(t *testing.T) {
@@ -63,3 +64,155 @@ func TestUpdateApi(t *testing.T) {
6364
assert.True(t, gock.IsDone())
6465
})
6566
}
67+
68+
func TestUpdateDbConfig(t *testing.T) {
69+
server := "http://localhost"
70+
client, err := v1API.NewClientWithResponses(server)
71+
require.NoError(t, err)
72+
73+
t.Run("updates remote DB config", func(t *testing.T) {
74+
updater := NewConfigUpdater(*client)
75+
// Setup mock server
76+
defer gock.Off()
77+
gock.New(server).
78+
Get("/v1/projects/test-project/config/database").
79+
Reply(http.StatusOK).
80+
JSON(v1API.PostgresConfigResponse{})
81+
gock.New(server).
82+
Put("/v1/projects/test-project/config/database").
83+
Reply(http.StatusOK).
84+
JSON(v1API.PostgresConfigResponse{
85+
MaxConnections: cast.Ptr(cast.UintToInt(100)),
86+
})
87+
// Run test
88+
err := updater.UpdateDbConfig(context.Background(), "test-project", db{
89+
Settings: settings{
90+
MaxConnections: cast.Ptr(cast.IntToUint(100)),
91+
},
92+
})
93+
// Check result
94+
assert.NoError(t, err)
95+
assert.True(t, gock.IsDone())
96+
})
97+
98+
t.Run("skips update if no diff in DB config", func(t *testing.T) {
99+
updater := NewConfigUpdater(*client)
100+
// Setup mock server
101+
defer gock.Off()
102+
gock.New(server).
103+
Get("/v1/projects/test-project/config/database").
104+
Reply(http.StatusOK).
105+
JSON(v1API.PostgresConfigResponse{
106+
MaxConnections: cast.Ptr(cast.UintToInt(100)),
107+
})
108+
// Run test
109+
err := updater.UpdateDbConfig(context.Background(), "test-project", db{
110+
Settings: settings{
111+
MaxConnections: cast.Ptr(cast.IntToUint(100)),
112+
},
113+
})
114+
// Check result
115+
assert.NoError(t, err)
116+
assert.True(t, gock.IsDone())
117+
})
118+
}
119+
120+
func TestUpdateExperimentalConfig(t *testing.T) {
121+
server := "http://localhost"
122+
client, err := v1API.NewClientWithResponses(server)
123+
require.NoError(t, err)
124+
125+
t.Run("enables webhooks", func(t *testing.T) {
126+
updater := NewConfigUpdater(*client)
127+
// Setup mock server
128+
defer gock.Off()
129+
gock.New(server).
130+
Post("/v1/projects/test-project/database/webhooks/enable").
131+
Reply(http.StatusOK).
132+
JSON(map[string]interface{}{})
133+
// Run test
134+
err := updater.UpdateExperimentalConfig(context.Background(), "test-project", experimental{
135+
Webhooks: &webhooks{
136+
Enabled: true,
137+
},
138+
})
139+
// Check result
140+
assert.NoError(t, err)
141+
assert.True(t, gock.IsDone())
142+
})
143+
144+
t.Run("skips update if webhooks not enabled", func(t *testing.T) {
145+
updater := NewConfigUpdater(*client)
146+
// Run test
147+
err := updater.UpdateExperimentalConfig(context.Background(), "test-project", experimental{
148+
Webhooks: &webhooks{
149+
Enabled: false,
150+
},
151+
})
152+
// Check result
153+
assert.NoError(t, err)
154+
assert.True(t, gock.IsDone())
155+
})
156+
}
157+
158+
func TestUpdateRemoteConfig(t *testing.T) {
159+
server := "http://localhost"
160+
client, err := v1API.NewClientWithResponses(server)
161+
require.NoError(t, err)
162+
163+
t.Run("updates all configs", func(t *testing.T) {
164+
updater := NewConfigUpdater(*client)
165+
// Setup mock server
166+
defer gock.Off()
167+
// API config
168+
gock.New(server).
169+
Get("/v1/projects/test-project/postgrest").
170+
Reply(http.StatusOK).
171+
JSON(v1API.PostgrestConfigWithJWTSecretResponse{})
172+
gock.New(server).
173+
Patch("/v1/projects/test-project/postgrest").
174+
Reply(http.StatusOK).
175+
JSON(v1API.PostgrestConfigWithJWTSecretResponse{
176+
DbSchema: "public",
177+
MaxRows: 1000,
178+
})
179+
// DB config
180+
gock.New(server).
181+
Get("/v1/projects/test-project/config/database").
182+
Reply(http.StatusOK).
183+
JSON(v1API.PostgresConfigResponse{})
184+
gock.New(server).
185+
Put("/v1/projects/test-project/config/database").
186+
Reply(http.StatusOK).
187+
JSON(v1API.PostgresConfigResponse{
188+
MaxConnections: cast.Ptr(cast.UintToInt(100)),
189+
})
190+
// Experimental config
191+
gock.New(server).
192+
Post("/v1/projects/test-project/database/webhooks/enable").
193+
Reply(http.StatusOK).
194+
JSON(map[string]interface{}{})
195+
// Run test
196+
err := updater.UpdateRemoteConfig(context.Background(), baseConfig{
197+
ProjectId: "test-project",
198+
Api: api{
199+
Enabled: true,
200+
Schemas: []string{"public", "private"},
201+
MaxRows: 1000,
202+
},
203+
Db: db{
204+
Settings: settings{
205+
MaxConnections: cast.Ptr(cast.IntToUint(100)),
206+
},
207+
},
208+
Experimental: experimental{
209+
Webhooks: &webhooks{
210+
Enabled: true,
211+
},
212+
},
213+
})
214+
// Check result
215+
assert.NoError(t, err)
216+
assert.True(t, gock.IsDone())
217+
})
218+
}

0 commit comments

Comments
 (0)