Skip to content

Commit

Permalink
feat(config): implement storage config update (#2817)
Browse files Browse the repository at this point in the history
* feat(config): implement storage config update

* chore: update unit tests
  • Loading branch information
sweatybridge authored Oct 30, 2024
1 parent 4d2e5c3 commit 0109975
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 34 deletions.
5 changes: 4 additions & 1 deletion api/beta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3734,7 +3734,10 @@ components:
type: object
properties:
fileSizeLimit:
type: number
type: integer
minimum: 0
maximum: 53687091200
format: int64
features:
$ref: '#/components/schemas/StorageFeatures'
PostgresConfigResponse:
Expand Down
2 changes: 1 addition & 1 deletion pkg/api/types.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 0 additions & 29 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,35 +166,6 @@ type (
Pop3Port uint16 `toml:"pop3_port"`
}

storage struct {
Enabled bool `toml:"enabled"`
Image string `toml:"-"`
FileSizeLimit sizeInBytes `toml:"file_size_limit"`
S3Credentials storageS3Credentials `toml:"-"`
ImageTransformation imageTransformation `toml:"image_transformation"`
Buckets BucketConfig `toml:"buckets"`
}

BucketConfig map[string]bucket

bucket struct {
Public *bool `toml:"public"`
FileSizeLimit sizeInBytes `toml:"file_size_limit"`
AllowedMimeTypes []string `toml:"allowed_mime_types"`
ObjectsPath string `toml:"objects_path"`
}

imageTransformation struct {
Enabled bool `toml:"enabled"`
Image string `toml:"-"`
}

storageS3Credentials struct {
AccessKeyId string `toml:"-"`
SecretAccessKey string `toml:"-"`
Region string `toml:"-"`
}

auth struct {
Enabled bool `toml:"enabled"`
Image string `toml:"-"`
Expand Down
65 changes: 65 additions & 0 deletions pkg/config/storage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package config

import (
v1API "github.com/supabase/cli/pkg/api"
"github.com/supabase/cli/pkg/cast"
"github.com/supabase/cli/pkg/diff"
)

type (
storage struct {
Enabled bool `toml:"enabled"`
Image string `toml:"-"`
FileSizeLimit sizeInBytes `toml:"file_size_limit"`
S3Credentials storageS3Credentials `toml:"-"`
ImageTransformation imageTransformation `toml:"image_transformation"`
Buckets BucketConfig `toml:"buckets"`
}

imageTransformation struct {
Enabled bool `toml:"enabled"`
Image string `toml:"-"`
}

storageS3Credentials struct {
AccessKeyId string `toml:"-"`
SecretAccessKey string `toml:"-"`
Region string `toml:"-"`
}

BucketConfig map[string]bucket

bucket struct {
Public *bool `toml:"public"`
FileSizeLimit sizeInBytes `toml:"file_size_limit"`
AllowedMimeTypes []string `toml:"allowed_mime_types"`
ObjectsPath string `toml:"objects_path"`
}
)

func (s *storage) ToUpdateStorageConfigBody() v1API.UpdateStorageConfigBody {
body := v1API.UpdateStorageConfigBody{Features: &v1API.StorageFeatures{}}
body.FileSizeLimit = cast.Ptr(int64(s.FileSizeLimit))
body.Features.ImageTransformation.Enabled = s.ImageTransformation.Enabled
return body
}

func (s *storage) fromRemoteStorageConfig(remoteConfig v1API.StorageConfigResponse) storage {
result := *s
result.FileSizeLimit = sizeInBytes(remoteConfig.FileSizeLimit)
result.ImageTransformation.Enabled = remoteConfig.Features.ImageTransformation.Enabled
return result
}

func (s *storage) DiffWithRemote(remoteConfig v1API.StorageConfigResponse) ([]byte, error) {
// Convert the config values into easily comparable remoteConfig values
currentValue, err := ToTomlBytes(s)
if err != nil {
return nil, err
}
remoteCompare, err := ToTomlBytes(s.fromRemoteStorageConfig(remoteConfig))
if err != nil {
return nil, err
}
return diff.Diff("remote[storage]", remoteCompare, "local[storage]", currentValue), nil
}
37 changes: 34 additions & 3 deletions pkg/config/updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ func (u *ConfigUpdater) UpdateRemoteConfig(ctx context.Context, remote baseConfi
if err := u.UpdateDbConfig(ctx, remote.ProjectId, remote.Db); err != nil {
return err
}
if err := u.UpdateStorageConfig(ctx, remote.ProjectId, remote.Storage); err != nil {
return err
}
if err := u.UpdateExperimentalConfig(ctx, remote.ProjectId, remote.Experimental); err != nil {
return err
}
Expand Down Expand Up @@ -69,6 +72,7 @@ func (u *ConfigUpdater) UpdateDbSettingsConfig(ctx context.Context, projectRef s
return nil
}
fmt.Fprintln(os.Stderr, "Updating DB service with config:", string(dbDiff))

remoteConfig := s.fromRemoteConfig(*dbConfig.JSON200)
restartRequired := s.requireDbRestart(remoteConfig)
if restartRequired {
Expand All @@ -91,14 +95,41 @@ func (u *ConfigUpdater) UpdateDbConfig(ctx context.Context, projectRef string, c
return nil
}

func (u *ConfigUpdater) UpdateStorageConfig(ctx context.Context, projectRef string, c storage) error {
if !c.Enabled {
return nil
}
storageConfig, err := u.client.V1GetStorageConfigWithResponse(ctx, projectRef)
if err != nil {
return errors.Errorf("failed to read Storage config: %w", err)
} else if storageConfig.JSON200 == nil {
return errors.Errorf("unexpected status %d: %s", storageConfig.StatusCode(), string(storageConfig.Body))
}
storageDiff, err := c.DiffWithRemote(*storageConfig.JSON200)
if err != nil {
return err
} else if len(storageDiff) == 0 {
fmt.Fprintln(os.Stderr, "Remote Storage config is up to date.")
return nil
}
fmt.Fprintln(os.Stderr, "Updating Storage service with config:", string(storageDiff))

if resp, err := u.client.V1UpdateStorageConfigWithResponse(ctx, projectRef, c.ToUpdateStorageConfigBody()); err != nil {
return errors.Errorf("failed to update Storage config: %w", err)
} else if status := resp.StatusCode(); status < 200 || status >= 300 {
return errors.Errorf("unexpected status %d: %s", status, string(resp.Body))
}
return nil
}

func (u *ConfigUpdater) UpdateExperimentalConfig(ctx context.Context, projectRef string, exp experimental) error {
if exp.Webhooks != nil && exp.Webhooks.Enabled {
fmt.Fprintln(os.Stderr, "Enabling webhooks for the project...")
fmt.Fprintln(os.Stderr, "Enabling webhooks for project:", projectRef)

if resp, err := u.client.V1EnableDatabaseWebhookWithResponse(ctx, projectRef); err != nil {
return errors.Errorf("failed to enable webhooks: %w", err)
} else if resp.StatusCode() < 200 || resp.StatusCode() >= 300 {
return errors.Errorf("unexpected enable webhook status %d: %s", resp.StatusCode(), string(resp.Body))
} else if status := resp.StatusCode(); status < 200 || status >= 300 {
return errors.Errorf("unexpected enable webhook status %d: %s", status, string(resp.Body))
}
}
return nil
Expand Down
61 changes: 61 additions & 0 deletions pkg/config/updater_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,52 @@ func TestUpdateExperimentalConfig(t *testing.T) {
})
}

func TestUpdateStorageConfig(t *testing.T) {
server := "http://localhost"
client, err := v1API.NewClientWithResponses(server)
require.NoError(t, err)

t.Run("updates remote Storage config", func(t *testing.T) {
updater := NewConfigUpdater(*client)
// Setup mock server
defer gock.Off()
gock.New(server).
Get("/v1/projects/test-project/config/storage").
Reply(http.StatusOK).
JSON(v1API.StorageConfigResponse{
FileSizeLimit: 100,
Features: v1API.StorageFeatures{
ImageTransformation: v1API.StorageFeatureImageTransformation{
Enabled: true,
},
},
})
gock.New(server).
Patch("/v1/projects/test-project/config/storage").
Reply(http.StatusOK)
// Run test
err := updater.UpdateStorageConfig(context.Background(), "test-project", storage{Enabled: true})
// Check result
assert.NoError(t, err)
assert.True(t, gock.IsDone())
})

t.Run("skips update if no diff in Storage config", func(t *testing.T) {
updater := NewConfigUpdater(*client)
// Setup mock server
defer gock.Off()
gock.New(server).
Get("/v1/projects/test-project/config/storage").
Reply(http.StatusOK).
JSON(v1API.StorageConfigResponse{})
// Run test
err := updater.UpdateStorageConfig(context.Background(), "test-project", storage{Enabled: true})
// Check result
assert.NoError(t, err)
assert.True(t, gock.IsDone())
})
}

func TestUpdateRemoteConfig(t *testing.T) {
server := "http://localhost"
client, err := v1API.NewClientWithResponses(server)
Expand Down Expand Up @@ -187,6 +233,14 @@ func TestUpdateRemoteConfig(t *testing.T) {
JSON(v1API.PostgresConfigResponse{
MaxConnections: cast.Ptr(cast.UintToInt(100)),
})
// Storage config
gock.New(server).
Get("/v1/projects/test-project/config/storage").
Reply(http.StatusOK).
JSON(v1API.StorageConfigResponse{})
gock.New(server).
Patch("/v1/projects/test-project/config/storage").
Reply(http.StatusOK)
// Experimental config
gock.New(server).
Post("/v1/projects/test-project/database/webhooks/enable").
Expand All @@ -205,6 +259,13 @@ func TestUpdateRemoteConfig(t *testing.T) {
MaxConnections: cast.Ptr(cast.IntToUint(100)),
},
},
Storage: storage{
Enabled: true,
FileSizeLimit: 100,
ImageTransformation: imageTransformation{
Enabled: true,
},
},
Experimental: experimental{
Webhooks: &webhooks{
Enabled: true,
Expand Down

0 comments on commit 0109975

Please sign in to comment.