diff --git a/api/beta.yaml b/api/beta.yaml index f4b0c08c2..ff547903e 100644 --- a/api/beta.yaml +++ b/api/beta.yaml @@ -1525,6 +1525,63 @@ paths: - Projects security: - bearer: [] + /v1/projects/{ref}/config/storage: + get: + operationId: v1-get-storage-config + summary: Gets project's storage config + parameters: + - name: ref + required: true + in: path + description: Project ref + schema: + minLength: 20 + maxLength: 20 + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/StorageConfigResponse' + '403': + description: '' + '500': + description: Failed to retrieve project's storage config + tags: + - Storage + security: + - bearer: [] + patch: + operationId: v1-update-storage-config + summary: Updates project's storage config + parameters: + - name: ref + required: true + in: path + description: Project ref + schema: + minLength: 20 + maxLength: 20 + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateStorageConfigBody' + responses: + '200': + description: '' + '403': + description: '' + '500': + description: Failed to update project's storage config + tags: + - Storage + security: + - bearer: [] /v1/projects/{ref}/config/database/postgres: get: operationId: v1-get-postgres-config @@ -1910,7 +1967,7 @@ paths: - bearer: [] /v1/projects/{ref}/functions: post: - operationId: createFunction + operationId: v1-create-a-function summary: Create a function description: Creates a function and adds it to the specified project. parameters: @@ -2545,6 +2602,7 @@ components: - RESTORING - RESTORE_FAILED - PAUSE_FAILED + - RESIZING type: string db_host: type: string @@ -2585,6 +2643,12 @@ components: BranchResponse: type: object properties: + pr_number: + type: integer + format: int32 + latest_check_run_id: + type: integer + format: int64 id: type: string name: @@ -2597,10 +2661,6 @@ components: type: boolean git_branch: type: string - pr_number: - type: number - latest_check_run_id: - type: number reset_on_push: type: boolean persistent: @@ -2639,9 +2699,12 @@ components: BranchResetResponse: type: object properties: + workflow_run_id: + type: string message: type: string required: + - workflow_run_id - message V1DatabaseResponse: type: object @@ -2701,6 +2764,7 @@ components: - RESTORING - RESTORE_FAILED - PAUSE_FAILED + - RESIZING type: string required: - id @@ -2846,6 +2910,9 @@ components: OAuthTokenResponse: type: object properties: + expires_in: + type: integer + format: int64 token_type: type: string enum: @@ -2854,18 +2921,17 @@ components: type: string refresh_token: type: string - expires_in: - type: number required: + - expires_in - token_type - access_token - refresh_token - - expires_in SnippetProject: type: object properties: id: - type: number + type: integer + format: int64 name: type: string required: @@ -2875,7 +2941,8 @@ components: type: object properties: id: - type: number + type: integer + format: int64 username: type: string required: @@ -3317,7 +3384,8 @@ components: type: object properties: id: - type: number + type: integer + format: int64 ref: type: string name: @@ -3451,6 +3519,8 @@ components: properties: current_app_version_release_channel: $ref: '#/components/schemas/ReleaseChannel' + duration_estimate_hours: + type: integer eligible: type: boolean current_app_version: @@ -3465,8 +3535,6 @@ components: type: array items: type: string - duration_estimate_hours: - type: number legacy_auth_custom_roles: type: array items: @@ -3477,23 +3545,29 @@ components: type: string required: - current_app_version_release_channel + - duration_estimate_hours - eligible - current_app_version - latest_app_version - target_upgrade_versions - potential_breaking_changes - - duration_estimate_hours - legacy_auth_custom_roles - extension_dependent_objects DatabaseUpgradeStatus: type: object properties: + target_version: + type: integer + status: + enum: + - 0 + - 1 + - 2 + type: integer initiated_at: type: string latest_status_at: type: string - target_version: - type: number error: type: string enum: @@ -3520,17 +3594,11 @@ components: - 8_attached_volume_to_upgraded_instance - 9_completed_upgrade - 10_completed_post_physical_backup - status: - type: number - enum: - - 0 - - 1 - - 2 required: - - initiated_at - - latest_status_at - target_version - status + - initiated_at + - latest_status_at DatabaseUpgradeStatusResponse: type: object properties: @@ -3593,26 +3661,16 @@ components: properties: name: type: string - version: - type: string - description: - type: string + enum: + - GoTrue required: - name - - version - - description RealtimeHealthResponse: type: object properties: - healthy: - type: boolean - db_connected: - type: boolean connected_cluster: - type: number + type: integer required: - - healthy - - db_connected - connected_cluster V1ServiceHealthResponse: type: object @@ -3644,6 +3702,41 @@ components: - name - healthy - status + StorageFeatureImageTransformation: + type: object + properties: + enabled: + type: boolean + required: + - enabled + StorageFeatures: + type: object + properties: + imageTransformation: + $ref: '#/components/schemas/StorageFeatureImageTransformation' + required: + - imageTransformation + StorageConfigResponse: + type: object + properties: + fileSizeLimit: + type: integer + format: int64 + features: + $ref: '#/components/schemas/StorageFeatures' + required: + - fileSizeLimit + - features + UpdateStorageConfigBody: + type: object + properties: + fileSizeLimit: + type: integer + minimum: 0 + maximum: 53687091200 + format: int64 + features: + $ref: '#/components/schemas/StorageFeatures' PostgresConfigResponse: type: object properties: @@ -3788,6 +3881,14 @@ components: SupavisorConfigResponse: type: object properties: + db_port: + type: integer + default_pool_size: + type: integer + nullable: true + max_client_conn: + type: integer + nullable: true identifier: type: string database_type: @@ -3801,34 +3902,26 @@ components: type: string db_host: type: string - db_port: - type: number db_name: type: string connectionString: type: string - default_pool_size: - type: number - nullable: true - max_client_conn: - type: number - nullable: true pool_mode: enum: - transaction - session type: string required: + - db_port + - default_pool_size + - max_client_conn - identifier - database_type - is_using_scram_auth - db_user - db_host - - db_port - db_name - connectionString - - default_pool_size - - max_client_conn - pool_mode UpdateSupavisorConfigBody: type: object @@ -3849,7 +3942,7 @@ components: type: object properties: default_pool_size: - type: number + type: integer nullable: true pool_mode: enum: @@ -3863,10 +3956,67 @@ components: type: object properties: api_max_request_duration: - type: number + type: integer nullable: true db_max_pool_size: - type: number + type: integer + nullable: true + jwt_exp: + type: integer + nullable: true + mailer_otp_exp: + type: integer + mailer_otp_length: + type: integer + nullable: true + mfa_max_enrolled_factors: + type: integer + nullable: true + mfa_phone_otp_length: + type: integer + mfa_phone_max_frequency: + type: integer + nullable: true + password_min_length: + type: integer + nullable: true + rate_limit_anonymous_users: + type: integer + nullable: true + rate_limit_email_sent: + type: integer + nullable: true + rate_limit_sms_sent: + type: integer + nullable: true + rate_limit_token_refresh: + type: integer + nullable: true + rate_limit_verify: + type: integer + nullable: true + rate_limit_otp: + type: integer + nullable: true + security_refresh_token_reuse_interval: + type: integer + nullable: true + sessions_inactivity_timeout: + type: integer + nullable: true + sessions_timebox: + type: integer + nullable: true + sms_max_frequency: + type: integer + nullable: true + sms_otp_exp: + type: integer + nullable: true + sms_otp_length: + type: integer + smtp_max_frequency: + type: integer nullable: true disable_signup: type: boolean @@ -4126,20 +4276,12 @@ components: hook_send_email_secrets: type: string nullable: true - jwt_exp: - type: number - nullable: true mailer_allow_unverified_email_sign_ins: type: boolean nullable: true mailer_autoconfirm: type: boolean nullable: true - mailer_otp_exp: - type: number - mailer_otp_length: - type: number - nullable: true mailer_secure_email_change_enabled: type: boolean nullable: true @@ -4179,9 +4321,6 @@ components: mailer_templates_recovery_content: type: string nullable: true - mfa_max_enrolled_factors: - type: number - nullable: true mfa_totp_enroll_enabled: type: boolean nullable: true @@ -4200,41 +4339,15 @@ components: mfa_web_authn_verify_enabled: type: boolean nullable: true - mfa_phone_otp_length: - type: number mfa_phone_template: type: string nullable: true - mfa_phone_max_frequency: - type: number - nullable: true password_hibp_enabled: type: boolean nullable: true - password_min_length: - type: number - nullable: true password_required_characters: type: string nullable: true - rate_limit_anonymous_users: - type: number - nullable: true - rate_limit_email_sent: - type: number - nullable: true - rate_limit_sms_sent: - type: number - nullable: true - rate_limit_token_refresh: - type: number - nullable: true - rate_limit_verify: - type: number - nullable: true - rate_limit_otp: - type: number - nullable: true refresh_token_rotation_enabled: type: boolean nullable: true @@ -4259,44 +4372,27 @@ components: security_manual_linking_enabled: type: boolean nullable: true - security_refresh_token_reuse_interval: - type: number - nullable: true security_update_password_require_reauthentication: type: boolean nullable: true - sessions_inactivity_timeout: - type: number - nullable: true sessions_single_per_user: type: boolean nullable: true sessions_tags: type: string nullable: true - sessions_timebox: - type: number - nullable: true site_url: type: string nullable: true sms_autoconfirm: type: boolean nullable: true - sms_max_frequency: - type: number - nullable: true sms_messagebird_access_key: type: string nullable: true sms_messagebird_originator: type: string nullable: true - sms_otp_exp: - type: number - nullable: true - sms_otp_length: - type: number sms_provider: type: string nullable: true @@ -4351,9 +4447,6 @@ components: smtp_host: type: string nullable: true - smtp_max_frequency: - type: number - nullable: true smtp_pass: type: string nullable: true @@ -4372,6 +4465,26 @@ components: required: - api_max_request_duration - db_max_pool_size + - jwt_exp + - mailer_otp_exp + - mailer_otp_length + - mfa_max_enrolled_factors + - mfa_phone_otp_length + - mfa_phone_max_frequency + - password_min_length + - rate_limit_anonymous_users + - rate_limit_email_sent + - rate_limit_sms_sent + - rate_limit_token_refresh + - rate_limit_verify + - rate_limit_otp + - security_refresh_token_reuse_interval + - sessions_inactivity_timeout + - sessions_timebox + - sms_max_frequency + - sms_otp_exp + - sms_otp_length + - smtp_max_frequency - disable_signup - external_anonymous_users_enabled - external_apple_additional_client_ids @@ -4458,11 +4571,8 @@ components: - hook_send_email_enabled - hook_send_email_uri - hook_send_email_secrets - - jwt_exp - mailer_allow_unverified_email_sign_ins - mailer_autoconfirm - - mailer_otp_exp - - mailer_otp_length - mailer_secure_email_change_enabled - mailer_subjects_confirmation - mailer_subjects_email_change @@ -4476,25 +4586,15 @@ components: - mailer_templates_magic_link_content - mailer_templates_reauthentication_content - mailer_templates_recovery_content - - mfa_max_enrolled_factors - mfa_totp_enroll_enabled - mfa_totp_verify_enabled - mfa_phone_enroll_enabled - mfa_phone_verify_enabled - mfa_web_authn_enroll_enabled - mfa_web_authn_verify_enabled - - mfa_phone_otp_length - mfa_phone_template - - mfa_phone_max_frequency - password_hibp_enabled - - password_min_length - password_required_characters - - rate_limit_anonymous_users - - rate_limit_email_sent - - rate_limit_sms_sent - - rate_limit_token_refresh - - rate_limit_verify - - rate_limit_otp - refresh_token_rotation_enabled - saml_enabled - saml_external_url @@ -4503,19 +4603,13 @@ components: - security_captcha_provider - security_captcha_secret - security_manual_linking_enabled - - security_refresh_token_reuse_interval - security_update_password_require_reauthentication - - sessions_inactivity_timeout - sessions_single_per_user - sessions_tags - - sessions_timebox - site_url - sms_autoconfirm - - sms_max_frequency - sms_messagebird_access_key - sms_messagebird_originator - - sms_otp_exp - - sms_otp_length - sms_provider - sms_template - sms_test_otp @@ -4534,7 +4628,6 @@ components: - sms_vonage_from - smtp_admin_email - smtp_host - - smtp_max_frequency - smtp_pass - smtp_port - smtp_sender_name @@ -4543,15 +4636,93 @@ components: UpdateAuthConfigBody: type: object properties: + jwt_exp: + type: integer + minimum: 0 + maximum: 604800 + smtp_max_frequency: + type: integer + minimum: 0 + maximum: 32767 + mfa_max_enrolled_factors: + type: integer + minimum: 0 + maximum: 2147483647 + sessions_timebox: + type: integer + minimum: 0 + sessions_inactivity_timeout: + type: integer + minimum: 0 + rate_limit_anonymous_users: + type: integer + minimum: 1 + maximum: 2147483647 + rate_limit_email_sent: + type: integer + minimum: 1 + maximum: 2147483647 + rate_limit_sms_sent: + type: integer + minimum: 1 + maximum: 2147483647 + rate_limit_verify: + type: integer + minimum: 1 + maximum: 2147483647 + rate_limit_token_refresh: + type: integer + minimum: 1 + maximum: 2147483647 + rate_limit_otp: + type: integer + minimum: 1 + maximum: 2147483647 + password_min_length: + type: integer + minimum: 6 + maximum: 32767 + security_refresh_token_reuse_interval: + type: integer + minimum: 0 + maximum: 2147483647 + mailer_otp_exp: + type: integer + minimum: 0 + maximum: 2147483647 + mailer_otp_length: + type: integer + minimum: 6 + maximum: 10 + sms_max_frequency: + type: integer + minimum: 0 + maximum: 32767 + sms_otp_exp: + type: integer + minimum: 0 + maximum: 2147483647 + sms_otp_length: + type: integer + minimum: 0 + maximum: 32767 + db_max_pool_size: + type: integer + api_max_request_duration: + type: integer + mfa_phone_max_frequency: + type: integer + minimum: 0 + maximum: 32767 + mfa_phone_otp_length: + type: integer + minimum: 0 + maximum: 32767 site_url: type: string pattern: /^[^,]+$/ disable_signup: type: boolean - jwt_exp: - type: number - minimum: 0 - maximum: 604800 smtp_admin_email: type: string smtp_host: @@ -4562,10 +4733,6 @@ components: type: string smtp_pass: type: string - smtp_max_frequency: - type: number - minimum: 0 - maximum: 32767 smtp_sender_name: type: string mailer_allow_unverified_email_sign_ins: @@ -4596,10 +4763,6 @@ components: type: string mailer_templates_reauthentication_content: type: string - mfa_max_enrolled_factors: - type: number - minimum: 0 - maximum: 2147483647 uri_allow_list: type: string external_anonymous_users_enabled: @@ -4619,51 +4782,17 @@ components: type: string security_captcha_secret: type: string - sessions_timebox: - type: number - minimum: 0 - sessions_inactivity_timeout: - type: number - minimum: 0 sessions_single_per_user: type: boolean sessions_tags: type: string pattern: /^\s*([a-z0-9_-]+(\s*,+\s*)?)*\s*$/i - rate_limit_anonymous_users: - type: number - minimum: 1 - maximum: 2147483647 - rate_limit_email_sent: - type: number - minimum: 1 - maximum: 2147483647 - rate_limit_sms_sent: - type: number - minimum: 1 - maximum: 2147483647 - rate_limit_verify: - type: number - minimum: 1 - maximum: 2147483647 - rate_limit_token_refresh: - type: number - minimum: 1 - maximum: 2147483647 - rate_limit_otp: - type: number - minimum: 1 - maximum: 2147483647 mailer_secure_email_change_enabled: type: boolean refresh_token_rotation_enabled: type: boolean password_hibp_enabled: type: boolean - password_min_length: - type: number - minimum: 6 - maximum: 32767 password_required_characters: type: string enum: @@ -4675,32 +4804,8 @@ components: type: boolean security_update_password_require_reauthentication: type: boolean - security_refresh_token_reuse_interval: - type: number - minimum: 0 - maximum: 2147483647 - mailer_otp_exp: - type: number - minimum: 0 - maximum: 2147483647 - mailer_otp_length: - type: number - minimum: 6 - maximum: 10 sms_autoconfirm: type: boolean - sms_max_frequency: - type: number - minimum: 0 - maximum: 32767 - sms_otp_exp: - type: number - minimum: 0 - maximum: 2147483647 - sms_otp_length: - type: number - minimum: 0 - maximum: 32767 sms_provider: type: string sms_messagebird_access_key: @@ -4902,10 +5007,6 @@ components: type: string external_zoom_secret: type: string - db_max_pool_size: - type: number - api_max_request_duration: - type: number mfa_totp_enroll_enabled: type: boolean mfa_totp_verify_enabled: @@ -4918,14 +5019,6 @@ components: type: boolean mfa_phone_verify_enabled: type: boolean - mfa_phone_max_frequency: - type: number - minimum: 0 - maximum: 32767 - mfa_phone_otp_length: - type: number - minimum: 0 - maximum: 32767 mfa_phone_template: type: string CreateThirdPartyAuthBody: @@ -4994,6 +5087,14 @@ components: FunctionResponse: type: object properties: + version: + type: integer + created_at: + type: integer + format: int64 + updated_at: + type: integer + format: int64 id: type: string slug: @@ -5006,12 +5107,6 @@ components: - REMOVED - THROTTLED type: string - version: - type: number - created_at: - type: number - updated_at: - type: number verify_jwt: type: boolean import_map: @@ -5021,16 +5116,24 @@ components: import_map_path: type: string required: + - version + - created_at + - updated_at - id - slug - name - status - - version - - created_at - - updated_at FunctionSlugResponse: type: object properties: + version: + type: integer + created_at: + type: integer + format: int64 + updated_at: + type: integer + format: int64 id: type: string slug: @@ -5043,12 +5146,6 @@ components: - REMOVED - THROTTLED type: string - version: - type: number - created_at: - type: number - updated_at: - type: number verify_jwt: type: boolean import_map: @@ -5058,13 +5155,13 @@ components: import_map_path: type: string required: + - version + - created_at + - updated_at - id - slug - name - status - - version - - created_at - - updated_at V1UpdateFunctionBody: type: object properties: @@ -5302,9 +5399,11 @@ components: type: object properties: earliest_physical_backup_date_unix: - type: number + type: integer + format: int64 latest_physical_backup_date_unix: - type: number + type: integer + format: int64 V1BackupsResponse: type: object properties: @@ -5330,7 +5429,9 @@ components: type: object properties: recovery_time_target_unix: - type: number + type: integer + minimum: 0 + format: int64 required: - recovery_time_target_unix V1OrganizationMemberResponse: diff --git a/cmd/functions.go b/cmd/functions.go index 6555bae9b..36dc47f94 100644 --- a/cmd/functions.go +++ b/cmd/functions.go @@ -13,6 +13,7 @@ import ( "github.com/supabase/cli/internal/functions/serve" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/internal/utils/flags" + "github.com/supabase/cli/pkg/cast" ) var ( @@ -106,9 +107,9 @@ var ( } if len(inspectMode.Value) > 0 { - runtimeOption.InspectMode = utils.Ptr(serve.InspectMode(inspectMode.Value)) + runtimeOption.InspectMode = cast.Ptr(serve.InspectMode(inspectMode.Value)) } else if inspectBrk { - runtimeOption.InspectMode = utils.Ptr(serve.InspectModeBrk) + runtimeOption.InspectMode = cast.Ptr(serve.InspectModeBrk) } if runtimeOption.InspectMode == nil && runtimeOption.InspectMain { return fmt.Errorf("--inspect-main must be used together with one of these flags: [inspect inspect-mode]") diff --git a/cmd/inspect.go b/cmd/inspect.go index 299524fbe..2b55c3876 100644 --- a/cmd/inspect.go +++ b/cmd/inspect.go @@ -22,6 +22,7 @@ import ( "github.com/supabase/cli/internal/inspect/long_running_queries" "github.com/supabase/cli/internal/inspect/outliers" "github.com/supabase/cli/internal/inspect/replication_slots" + "github.com/supabase/cli/internal/inspect/role_configs" "github.com/supabase/cli/internal/inspect/role_connections" "github.com/supabase/cli/internal/inspect/seq_scans" "github.com/supabase/cli/internal/inspect/table_index_sizes" @@ -194,6 +195,14 @@ var ( }, } + inspectRoleConfigsCmd = &cobra.Command{ + Use: "role-configs", + Short: "Show configuration settings for database roles when they have been modified", + RunE: func(cmd *cobra.Command, args []string) error { + return role_configs.Run(cmd.Context(), flags.DbConfig, afero.NewOsFs()) + }, + } + inspectRoleConnectionsCmd = &cobra.Command{ Use: "role-connections", Short: "Show number of active connections for all database roles", @@ -247,6 +256,7 @@ func init() { inspectDBCmd.AddCommand(inspectTableRecordCountsCmd) inspectDBCmd.AddCommand(inspectBloatCmd) inspectDBCmd.AddCommand(inspectVacuumStatsCmd) + inspectDBCmd.AddCommand(inspectRoleConfigsCmd) inspectDBCmd.AddCommand(inspectRoleConnectionsCmd) inspectCmd.AddCommand(inspectDBCmd) reportCmd.Flags().StringVar(&outputDir, "output-dir", "", "Path to save CSV files in") diff --git a/go.mod b/go.mod index cc84666d5..b1f94ae6b 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module github.com/supabase/cli -go 1.22.4 +go 1.23.2 require ( github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c - github.com/Netflix/go-env v0.1.0 + github.com/Netflix/go-env v0.1.2 github.com/andybalholm/brotli v1.1.1 github.com/cenkalti/backoff/v4 v4.3.0 github.com/charmbracelet/bubbles v0.18.0 @@ -12,7 +12,6 @@ require ( github.com/charmbracelet/glamour v0.7.0 github.com/charmbracelet/lipgloss v0.12.1 github.com/containers/common v0.60.4 - github.com/danieljoos/wincred v1.2.2 github.com/deepmap/oapi-codegen/v2 v2.2.0 github.com/docker/cli v27.3.1+incompatible github.com/docker/docker v27.3.1+incompatible @@ -25,6 +24,7 @@ require ( github.com/go-xmlfmt/xmlfmt v1.1.2 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/golangci/golangci-lint v1.61.0 + github.com/google/go-cmp v0.6.0 github.com/google/go-github/v62 v62.0.0 github.com/google/go-querystring v1.1.0 github.com/google/uuid v1.6.0 @@ -48,7 +48,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/stripe/pg-schema-diff v0.7.0 github.com/withfig/autocomplete-tools/packages/cobra v1.2.0 - github.com/zalando/go-keyring v0.2.5 + github.com/zalando/go-keyring v0.2.6 go.opentelemetry.io/otel v1.31.0 golang.org/x/mod v0.21.0 golang.org/x/oauth2 v0.23.0 @@ -61,6 +61,7 @@ require ( require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.1 // indirect + al.essio.dev/pkg/shellescape v1.5.1 // indirect dario.cat/mergo v1.0.0 // indirect github.com/4meepo/tagalign v1.3.4 // indirect github.com/Abirdcfly/dupword v0.1.1 // indirect @@ -77,7 +78,6 @@ require ( github.com/ProtonMail/go-crypto v1.0.0 // indirect github.com/alecthomas/chroma/v2 v2.8.0 // indirect github.com/alecthomas/go-check-sumtype v0.1.4 // indirect - github.com/alessio/shellescape v1.4.1 // indirect github.com/alexkohler/nakedret/v2 v2.0.4 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect @@ -115,6 +115,7 @@ require ( github.com/curioswitch/go-reassign v0.2.0 // indirect github.com/cyphar/filepath-securejoin v0.3.1 // indirect github.com/daixiang0/gci v0.13.5 // indirect + github.com/danieljoos/wincred v1.2.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/distribution/reference v0.6.0 // indirect @@ -169,7 +170,6 @@ require ( github.com/golangci/plugin-module-register v0.1.1 // indirect github.com/golangci/revgrep v0.5.3 // indirect github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect - github.com/google/go-cmp v0.6.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gordonklaus/ineffassign v0.1.0 // indirect github.com/gorilla/css v1.0.0 // indirect diff --git a/go.sum b/go.sum index ec11ef74c..224110f05 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ 4d63.com/gocheckcompilerdirectives v1.2.1/go.mod h1:yjDJSxmDTtIHHCqX0ufRYZDL6vQtMG7tJdKVeWwsqvs= 4d63.com/gochecknoglobals v0.2.1 h1:1eiorGsgHOFOuoOiJDy2psSrQbRdIHrlge0IJIkUgDc= 4d63.com/gochecknoglobals v0.2.1/go.mod h1:KRE8wtJB3CXCsb1xy421JfTHIIbmT3U5ruxw2Qu8fSU= +al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho= +al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -65,8 +67,8 @@ github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lpr github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Netflix/go-env v0.1.0 h1:qSMk2A4D6urE/YqOKpLeOkaATGmFmMLo56E7kNNKypk= -github.com/Netflix/go-env v0.1.0/go.mod h1:9IRTAm+pQDPMpUtMLR26JOrjHnAWz3KUbhaegqTdhfY= +github.com/Netflix/go-env v0.1.2 h1:0DRoLR9lECQ9Zqvkswuebm3jJ/2enaDX6Ei8/Z+EnK0= +github.com/Netflix/go-env v0.1.2/go.mod h1:WlIhYi++8FlKNJtrop1mjXYAJMzv1f43K4MqCoh0yGE= github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= @@ -87,8 +89,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= -github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/alexkohler/nakedret/v2 v2.0.4 h1:yZuKmjqGi0pSmjGpOC016LtPJysIL0WEUiaXW5SUnNg= github.com/alexkohler/nakedret/v2 v2.0.4/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= @@ -1012,8 +1012,8 @@ github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU= github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s= github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY= -github.com/zalando/go-keyring v0.2.5 h1:Bc2HHpjALryKD62ppdEzaFG6VxL6Bc+5v0LYpN8Lba8= -github.com/zalando/go-keyring v0.2.5/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= +github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s= +github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= diff --git a/internal/branches/create/create_test.go b/internal/branches/create/create_test.go index 6f56b586f..e07e10387 100644 --- a/internal/branches/create/create_test.go +++ b/internal/branches/create/create_test.go @@ -14,6 +14,7 @@ import ( "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/internal/utils/flags" "github.com/supabase/cli/pkg/api" + "github.com/supabase/cli/pkg/cast" ) func TestCreateCommand(t *testing.T) { @@ -36,7 +37,7 @@ func TestCreateCommand(t *testing.T) { }) // Run test err := Run(context.Background(), api.CreateBranchBody{ - Region: utils.Ptr("sin"), + Region: cast.Ptr("sin"), }, fsys) // Check error assert.NoError(t, err) @@ -53,7 +54,7 @@ func TestCreateCommand(t *testing.T) { ReplyError(net.ErrClosed) // Run test err := Run(context.Background(), api.CreateBranchBody{ - Region: utils.Ptr("sin"), + Region: cast.Ptr("sin"), }, fsys) // Check error assert.ErrorIs(t, err, net.ErrClosed) @@ -70,7 +71,7 @@ func TestCreateCommand(t *testing.T) { Reply(http.StatusServiceUnavailable) // Run test err := Run(context.Background(), api.CreateBranchBody{ - Region: utils.Ptr("sin"), + Region: cast.Ptr("sin"), }, fsys) // Check error assert.ErrorContains(t, err, "Unexpected error creating preview branch:") diff --git a/internal/db/start/start.go b/internal/db/start/start.go index 826d46c86..a300f5594 100644 --- a/internal/db/start/start.go +++ b/internal/db/start/start.go @@ -56,7 +56,6 @@ func NewContainerConfig() container.Config { env := []string{ "POSTGRES_PASSWORD=" + utils.Config.Db.Password, "POSTGRES_HOST=/var/run/postgresql", - "POSTGRES_INITDB_ARGS=--lc-ctype=C.UTF-8", "JWT_SECRET=" + utils.Config.Auth.JwtSecret, fmt.Sprintf("JWT_EXP=%d", utils.Config.Auth.JwtExpiry), } @@ -81,13 +80,18 @@ func NewContainerConfig() container.Config { Timeout: 2 * time.Second, Retries: 3, }, - Entrypoint: []string{"sh", "-c", `cat <<'EOF' > /etc/postgresql.schema.sql && cat <<'EOF' > /etc/postgresql-custom/pgsodium_root.key && docker-entrypoint.sh postgres -D /etc/postgresql + Entrypoint: []string{"sh", "-c", ` +cat <<'EOF' > /etc/postgresql.schema.sql && \ +cat <<'EOF' > /etc/postgresql-custom/pgsodium_root.key && \ +cat <<'EOF' >> /etc/postgresql/postgresql.conf && \ +docker-entrypoint.sh postgres -D /etc/postgresql ` + initialSchema + ` ` + _supabaseSchema + ` EOF ` + utils.Config.Db.RootKey + ` EOF -`}, +` + utils.Config.Db.Settings.ToPostgresConfig() + ` +EOF`}, } if utils.Config.Db.MajorVersion >= 14 { config.Cmd = []string{"postgres", @@ -124,11 +128,13 @@ func StartDatabase(ctx context.Context, fsys afero.Fs, w io.Writer, options ...f } if utils.Config.Db.MajorVersion <= 14 { config.Entrypoint = []string{"sh", "-c", ` - cat <<'EOF' > /docker-entrypoint-initdb.d/supabase_schema.sql +cat <<'EOF' > /docker-entrypoint-initdb.d/supabase_schema.sql && \ +cat <<'EOF' >> /etc/postgresql/postgresql.conf && \ +docker-entrypoint.sh postgres -D /etc/postgresql ` + _supabaseSchema + ` EOF - docker-entrypoint.sh postgres -D /etc/postgresql - `} +` + utils.Config.Db.Settings.ToPostgresConfig() + ` +EOF`} hostConfig.Tmpfs = map[string]string{"/docker-entrypoint-initdb.d": ""} } // Creating volume will not override existing volume, so we must inspect explicitly diff --git a/internal/db/start/start_test.go b/internal/db/start/start_test.go index 96d97da8c..475562f2f 100644 --- a/internal/db/start/start_test.go +++ b/internal/db/start/start_test.go @@ -17,6 +17,7 @@ import ( "github.com/supabase/cli/internal/testing/apitest" "github.com/supabase/cli/internal/testing/fstest" "github.com/supabase/cli/internal/utils" + "github.com/supabase/cli/pkg/cast" "github.com/supabase/cli/pkg/pgtest" ) @@ -308,3 +309,55 @@ func TestSetupDatabase(t *testing.T) { assert.Empty(t, apitest.ListUnmatchedRequests()) }) } +func TestStartDatabaseWithCustomSettings(t *testing.T) { + t.Run("starts database with custom MaxConnections", func(t *testing.T) { + // Setup + utils.Config.Db.MajorVersion = 15 + utils.DbId = "supabase_db_test" + utils.ConfigId = "supabase_config_test" + utils.Config.Db.Port = 5432 + utils.Config.Db.Settings.MaxConnections = cast.Ptr(uint(50)) + + // Setup in-memory fs + fsys := afero.NewMemMapFs() + + // Setup mock docker + require.NoError(t, apitest.MockDocker(utils.Docker)) + defer gock.OffAll() + gock.New(utils.Docker.DaemonHost()). + Get("/v" + utils.Docker.ClientVersion() + "/volumes/" + utils.DbId). + Reply(http.StatusNotFound). + JSON(volume.Volume{}) + apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.Db.Image), utils.DbId) + gock.New(utils.Docker.DaemonHost()). + Get("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.DbId + "/json"). + Reply(http.StatusOK). + JSON(types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ + State: &types.ContainerState{ + Running: true, + Health: &types.Health{Status: types.Healthy}, + }, + }}) + + apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.Realtime.Image), "test-realtime") + require.NoError(t, apitest.MockDockerLogs(utils.Docker, "test-realtime", "")) + apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.Storage.Image), "test-storage") + require.NoError(t, apitest.MockDockerLogs(utils.Docker, "test-storage", "")) + apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.Auth.Image), "test-auth") + require.NoError(t, apitest.MockDockerLogs(utils.Docker, "test-auth", "")) + // Setup mock postgres + conn := pgtest.NewConn() + defer conn.Close(t) + + // Run test + err := StartDatabase(context.Background(), fsys, io.Discard, conn.Intercept) + + // Check error + assert.NoError(t, err) + assert.Empty(t, apitest.ListUnmatchedRequests()) + + // Check if the custom MaxConnections setting was applied + config := NewContainerConfig() + assert.Contains(t, config.Entrypoint[2], "max_connections = 50") + }) +} diff --git a/internal/functions/deploy/bundle.go b/internal/functions/deploy/bundle.go index 0b178b0f7..0119a559c 100644 --- a/internal/functions/deploy/bundle.go +++ b/internal/functions/deploy/bundle.go @@ -44,8 +44,7 @@ func (b *dockerBundler) Bundle(ctx context.Context, entrypoint string, importMap } }() // Create bind mounts - hostEntrypointDir := filepath.Dir(entrypoint) - binds, err := GetBindMounts(cwd, utils.FunctionsDir, hostOutputDir, hostEntrypointDir, importMap, b.fsys) + binds, err := GetBindMounts(cwd, utils.FunctionsDir, hostOutputDir, entrypoint, importMap, b.fsys) if err != nil { return err } @@ -86,7 +85,7 @@ func (b *dockerBundler) Bundle(ctx context.Context, entrypoint string, importMap return function.Compress(eszipBytes, output) } -func GetBindMounts(cwd, hostFuncDir, hostOutputDir, hostEntrypointDir, hostImportMapPath string, fsys afero.Fs) ([]string, error) { +func GetBindMounts(cwd, hostFuncDir, hostOutputDir, hostEntrypointPath, hostImportMapPath string, fsys afero.Fs) ([]string, error) { sep := string(filepath.Separator) // Docker requires all host paths to be absolute if !filepath.IsAbs(hostFuncDir) { @@ -116,6 +115,7 @@ func GetBindMounts(cwd, hostFuncDir, hostOutputDir, hostEntrypointDir, hostImpor } } // Allow entrypoints outside the functions directory + hostEntrypointDir := filepath.Dir(hostEntrypointPath) if len(hostEntrypointDir) > 0 { if !filepath.IsAbs(hostEntrypointDir) { hostEntrypointDir = filepath.Join(cwd, hostEntrypointDir) diff --git a/internal/functions/deploy/deploy.go b/internal/functions/deploy/deploy.go index 25ad3372a..855d44537 100644 --- a/internal/functions/deploy/deploy.go +++ b/internal/functions/deploy/deploy.go @@ -10,6 +10,7 @@ import ( "github.com/go-errors/errors" "github.com/spf13/afero" "github.com/supabase/cli/internal/utils" + "github.com/supabase/cli/pkg/cast" "github.com/supabase/cli/pkg/config" "github.com/supabase/cli/pkg/function" ) @@ -57,6 +58,10 @@ func GetFunctionSlugs(fsys afero.Fs) (slugs []string, err error) { slugs = append(slugs, slug) } } + // Add all function slugs declared in config file + for slug := range utils.Config.Functions { + slugs = append(slugs, slug) + } return slugs, nil } @@ -86,7 +91,7 @@ func GetFunctionConfig(slugs []string, importMapPath string, noVerifyJWT *bool, function.ImportMap = utils.FallbackImportMapPath } if noVerifyJWT != nil { - function.VerifyJWT = utils.Ptr(!*noVerifyJWT) + function.VerifyJWT = cast.Ptr(!*noVerifyJWT) } functionConfig[name] = function } diff --git a/internal/functions/deploy/deploy_test.go b/internal/functions/deploy/deploy_test.go index adf49e63f..558a33f32 100644 --- a/internal/functions/deploy/deploy_test.go +++ b/internal/functions/deploy/deploy_test.go @@ -15,6 +15,7 @@ import ( "github.com/supabase/cli/internal/testing/apitest" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/api" + "github.com/supabase/cli/pkg/cast" "github.com/supabase/cli/pkg/config" ) @@ -321,12 +322,16 @@ func TestImportMapPath(t *testing.T) { } // Setup in-memory fs fsys := afero.NewMemMapFs() + // Custom global import map loaded via cli flag + customImportMapPath := filepath.Join(utils.FunctionsDir, "custom_import_map.json") + require.NoError(t, afero.WriteFile(fsys, customImportMapPath, []byte("{}"), 0644)) + // Create fallback import map to test precedence order require.NoError(t, afero.WriteFile(fsys, utils.FallbackImportMapPath, []byte("{}"), 0644)) // Run test - fc, err := GetFunctionConfig([]string{slug}, utils.FallbackImportMapPath, utils.Ptr(false), fsys) + fc, err := GetFunctionConfig([]string{slug}, customImportMapPath, cast.Ptr(false), fsys) // Check error assert.NoError(t, err) - assert.Equal(t, utils.FallbackImportMapPath, fc[slug].ImportMap) + assert.Equal(t, customImportMapPath, fc[slug].ImportMap) }) t.Run("returns empty string if no fallback", func(t *testing.T) { diff --git a/internal/functions/list/list.go b/internal/functions/list/list.go index aeb17f2cd..d0d2d9ecc 100644 --- a/internal/functions/list/list.go +++ b/internal/functions/list/list.go @@ -25,14 +25,14 @@ func Run(ctx context.Context, projectRef string, fsys afero.Fs) error { |-|-|-|-|-|-| ` for _, function := range *resp.JSON200 { - t := time.UnixMilli(int64(function.UpdatedAt)) + t := time.UnixMilli(function.UpdatedAt) table += fmt.Sprintf( "|`%s`|`%s`|`%s`|`%s`|`%d`|`%s`|\n", function.Id, function.Name, function.Slug, function.Status, - uint64(function.Version), + function.Version, t.UTC().Format("2006-01-02 15:04:05"), ) } diff --git a/internal/functions/serve/serve.go b/internal/functions/serve/serve.go index 15320171d..62811d769 100644 --- a/internal/functions/serve/serve.go +++ b/internal/functions/serve/serve.go @@ -109,11 +109,6 @@ func ServeFunctions(ctx context.Context, envFilePath string, noVerifyJWT *bool, if err != nil { return err } - cwd, err := os.Getwd() - if err != nil { - return errors.Errorf("failed to get working directory: %w", err) - } - dockerFuncDir := utils.ToDockerPath(filepath.Join(cwd, utils.FunctionsDir)) env = append(env, fmt.Sprintf("SUPABASE_URL=http://%s:8000", utils.KongAliases[0]), "SUPABASE_ANON_KEY="+utils.Config.Auth.AnonKey, @@ -121,7 +116,6 @@ func ServeFunctions(ctx context.Context, envFilePath string, noVerifyJWT *bool, "SUPABASE_DB_URL="+dbUrl, "SUPABASE_INTERNAL_JWT_SECRET="+utils.Config.Auth.JwtSecret, fmt.Sprintf("SUPABASE_INTERNAL_HOST_PORT=%d", utils.Config.Api.Port), - "SUPABASE_INTERNAL_FUNCTIONS_PATH="+dockerFuncDir, ) if viper.GetBool("DEBUG") { env = append(env, "SUPABASE_INTERNAL_DEBUG=true") @@ -130,6 +124,10 @@ func ServeFunctions(ctx context.Context, envFilePath string, noVerifyJWT *bool, env = append(env, "SUPABASE_INTERNAL_WALLCLOCK_LIMIT_SEC=0") } // 3. Parse custom import map + cwd, err := os.Getwd() + if err != nil { + return errors.Errorf("failed to get working directory: %w", err) + } binds, functionsConfigString, err := populatePerFunctionConfigs(cwd, importMapPath, noVerifyJWT, fsys) if err != nil { return err diff --git a/internal/functions/serve/serve_test.go b/internal/functions/serve/serve_test.go index 5b98ece87..570c4b927 100644 --- a/internal/functions/serve/serve_test.go +++ b/internal/functions/serve/serve_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/supabase/cli/internal/testing/apitest" "github.com/supabase/cli/internal/utils" + "github.com/supabase/cli/pkg/cast" ) func TestServeCommand(t *testing.T) { @@ -100,7 +101,7 @@ func TestServeCommand(t *testing.T) { Reply(http.StatusOK). JSON(types.ContainerJSON{}) // Run test - err := Run(context.Background(), ".env", utils.Ptr(true), "import_map.json", RuntimeOption{}, fsys) + err := Run(context.Background(), ".env", cast.Ptr(true), "import_map.json", RuntimeOption{}, fsys) // Check error assert.ErrorIs(t, err, os.ErrNotExist) }) diff --git a/internal/functions/serve/templates/main.ts b/internal/functions/serve/templates/main.ts index c96847e3e..e94b183ac 100644 --- a/internal/functions/serve/templates/main.ts +++ b/internal/functions/serve/templates/main.ts @@ -2,6 +2,7 @@ import { STATUS_CODE, STATUS_TEXT, } from "https://deno.land/std/http/status.ts"; +import * as posix from "https://deno.land/std/path/posix/mod.ts"; import * as jose from "https://deno.land/x/jose@v4.13.1/index.ts"; @@ -28,7 +29,6 @@ const EXCLUDED_ENVS = ["HOME", "HOSTNAME", "PATH", "PWD"]; const JWT_SECRET = Deno.env.get("SUPABASE_INTERNAL_JWT_SECRET")!; const HOST_PORT = Deno.env.get("SUPABASE_INTERNAL_HOST_PORT")!; -const FUNCTIONS_PATH = Deno.env.get("SUPABASE_INTERNAL_FUNCTIONS_PATH")!; const DEBUG = Deno.env.get("SUPABASE_INTERNAL_DEBUG") === "true"; const FUNCTIONS_CONFIG_STRING = Deno.env.get( "SUPABASE_INTERNAL_FUNCTIONS_CONFIG", @@ -43,6 +43,7 @@ const DENO_SB_ERROR_MAP = new Map([ ]); interface FunctionConfig { + entrypointPath: string; importMapPath: string; verifyJWT: boolean; } @@ -144,7 +145,7 @@ Deno.serve({ } } - const servicePath = `${FUNCTIONS_PATH}/${functionName}`; + const servicePath = posix.dirname(functionsConfig[functionName].entrypointPath); console.error(`serving the request with ${servicePath}`); // Ref: https://supabase.com/docs/guides/functions/limits @@ -167,6 +168,9 @@ Deno.serve({ // point, as their migration process will not be easy. const decoratorType = "tc39"; + const absEntrypoint = posix.join(Deno.cwd(), functionsConfig[functionName].entrypointPath); + const maybeEntrypoint = posix.toFileUrl(absEntrypoint).href; + try { const worker = await EdgeRuntime.userWorkers.create({ servicePath, @@ -179,7 +183,8 @@ Deno.serve({ customModuleRoot, cpuTimeSoftLimitMs, cpuTimeHardLimitMs, - decoratorType + decoratorType, + maybeEntrypoint }); const controller = new AbortController(); diff --git a/internal/init/init_test.go b/internal/init/init_test.go index 47e35b89e..99a96dce5 100644 --- a/internal/init/init_test.go +++ b/internal/init/init_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/supabase/cli/internal/testing/fstest" "github.com/supabase/cli/internal/utils" + "github.com/supabase/cli/pkg/cast" ) func TestInitCommand(t *testing.T) { @@ -70,7 +71,7 @@ func TestInitCommand(t *testing.T) { // Setup in-memory fs fsys := &afero.MemMapFs{} // Run test - assert.NoError(t, Run(context.Background(), fsys, utils.Ptr(true), nil, utils.InitParams{})) + assert.NoError(t, Run(context.Background(), fsys, cast.Ptr(true), nil, utils.InitParams{})) // Validate generated vscode settings exists, err := afero.Exists(fsys, settingsPath) assert.NoError(t, err) @@ -84,7 +85,7 @@ func TestInitCommand(t *testing.T) { // Setup in-memory fs fsys := &afero.MemMapFs{} // Run test - assert.NoError(t, Run(context.Background(), fsys, utils.Ptr(false), nil, utils.InitParams{})) + assert.NoError(t, Run(context.Background(), fsys, cast.Ptr(false), nil, utils.InitParams{})) // Validate vscode settings file isn't generated exists, err := afero.Exists(fsys, settingsPath) assert.NoError(t, err) @@ -98,7 +99,7 @@ func TestInitCommand(t *testing.T) { // Setup in-memory fs fsys := &afero.MemMapFs{} // Run test - assert.NoError(t, Run(context.Background(), fsys, nil, utils.Ptr(true), utils.InitParams{})) + assert.NoError(t, Run(context.Background(), fsys, nil, cast.Ptr(true), utils.InitParams{})) // Validate generated intellij deno config exists, err := afero.Exists(fsys, denoPath) assert.NoError(t, err) @@ -109,7 +110,7 @@ func TestInitCommand(t *testing.T) { // Setup in-memory fs fsys := &afero.MemMapFs{} // Run test - assert.NoError(t, Run(context.Background(), fsys, nil, utils.Ptr(false), utils.InitParams{})) + assert.NoError(t, Run(context.Background(), fsys, nil, cast.Ptr(false), utils.InitParams{})) // Validate intellij deno config file isn't generated exists, err := afero.Exists(fsys, denoPath) assert.NoError(t, err) diff --git a/internal/inspect/report_test.go b/internal/inspect/report_test.go index 4de1ab602..6b4220451 100644 --- a/internal/inspect/report_test.go +++ b/internal/inspect/report_test.go @@ -18,6 +18,7 @@ import ( "github.com/supabase/cli/internal/inspect/long_running_queries" "github.com/supabase/cli/internal/inspect/outliers" "github.com/supabase/cli/internal/inspect/replication_slots" + "github.com/supabase/cli/internal/inspect/role_configs" "github.com/supabase/cli/internal/inspect/role_connections" "github.com/supabase/cli/internal/inspect/seq_scans" "github.com/supabase/cli/internal/inspect/table_index_sizes" @@ -65,6 +66,8 @@ func TestReportCommand(t *testing.T) { Reply("COPY 0"). Query(wrapQuery(replication_slots.ReplicationSlotsQuery)). Reply("COPY 0"). + Query(wrapQuery(role_configs.RoleConfigsQuery)). + Reply("COPY 0"). Query(wrapQuery(role_connections.RoleConnectionsQuery)). Reply("COPY 0"). Query(wrapQuery(seq_scans.SeqScansQuery)). @@ -89,7 +92,7 @@ func TestReportCommand(t *testing.T) { assert.NoError(t, err) matches, err := afero.Glob(fsys, "*.csv") assert.NoError(t, err) - assert.Len(t, matches, 19) + assert.Len(t, matches, 20) }) } diff --git a/internal/inspect/role_configs/role_configs.go b/internal/inspect/role_configs/role_configs.go new file mode 100644 index 000000000..f4fb79382 --- /dev/null +++ b/internal/inspect/role_configs/role_configs.go @@ -0,0 +1,46 @@ +package role_configs + +import ( + "context" + _ "embed" + "fmt" + + "github.com/go-errors/errors" + "github.com/jackc/pgconn" + "github.com/jackc/pgx/v4" + "github.com/spf13/afero" + "github.com/supabase/cli/internal/migration/list" + "github.com/supabase/cli/internal/utils" + "github.com/supabase/cli/pkg/pgxv5" +) + +//go:embed role_configs.sql +var RoleConfigsQuery string + +type Result struct { + Role_name string + Custom_config string +} + +func Run(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error { + conn, err := utils.ConnectByConfig(ctx, config, options...) + if err != nil { + return err + } + defer conn.Close(context.Background()) + rows, err := conn.Query(ctx, RoleConfigsQuery) + if err != nil { + return errors.Errorf("failed to query rows: %w", err) + } + result, err := pgxv5.CollectRows[Result](rows) + if err != nil { + return err + } + + table := "|Role name|Custom config|\n|-|-|\n" + for _, r := range result { + table += fmt.Sprintf("|`%s`|`%s`|\n", r.Role_name, r.Custom_config) + } + + return list.RenderTable(table) +} diff --git a/internal/inspect/role_configs/role_configs.sql b/internal/inspect/role_configs/role_configs.sql new file mode 100644 index 000000000..d568d6862 --- /dev/null +++ b/internal/inspect/role_configs/role_configs.sql @@ -0,0 +1,5 @@ +select + rolname as role_name, + array_to_string(rolconfig, ',', '*') as custom_config +from + pg_roles where rolconfig is not null; diff --git a/internal/inspect/role_configs/role_configs_test.go b/internal/inspect/role_configs/role_configs_test.go new file mode 100644 index 000000000..554a12526 --- /dev/null +++ b/internal/inspect/role_configs/role_configs_test.go @@ -0,0 +1,38 @@ +package role_configs + +import ( + "context" + "testing" + + "github.com/jackc/pgconn" + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/supabase/cli/pkg/pgtest" +) + +var dbConfig = pgconn.Config{ + Host: "127.0.0.1", + Port: 5432, + User: "admin", + Password: "password", + Database: "postgres", +} + +func TestRoleCommand(t *testing.T) { + t.Run("inspects role connections", func(t *testing.T) { + // Setup in-memory fs + fsys := afero.NewMemMapFs() + // Setup mock postgres + conn := pgtest.NewConn() + defer conn.Close(t) + conn.Query(RoleConfigsQuery). + Reply("SELECT 1", Result{ + Role_name: "postgres", + Custom_config: "statement_timeout=3s", + }) + // Run test + err := Run(context.Background(), dbConfig, fsys, conn.Intercept) + // Check error + assert.NoError(t, err) + }) +} diff --git a/internal/link/link.go b/internal/link/link.go index 04e5c565b..9d72302c4 100644 --- a/internal/link/link.go +++ b/internal/link/link.go @@ -232,10 +232,10 @@ func updatePoolerConfig(config api.SupavisorConfigResponse) { utils.Config.Db.Pooler.ConnectionString = config.ConnectionString utils.Config.Db.Pooler.PoolMode = cliConfig.PoolMode(config.PoolMode) if config.DefaultPoolSize != nil { - utils.Config.Db.Pooler.DefaultPoolSize = uint(*config.DefaultPoolSize) + utils.Config.Db.Pooler.DefaultPoolSize = cast.IntToUint(*config.DefaultPoolSize) } if config.MaxClientConn != nil { - utils.Config.Db.Pooler.MaxClientConn = uint(*config.MaxClientConn) + utils.Config.Db.Pooler.MaxClientConn = cast.IntToUint(*config.MaxClientConn) } } diff --git a/internal/seed/buckets/buckets.go b/internal/seed/buckets/buckets.go index 2af80b43d..365c3a395 100644 --- a/internal/seed/buckets/buckets.go +++ b/internal/seed/buckets/buckets.go @@ -3,12 +3,10 @@ package buckets import ( "context" "fmt" - "path/filepath" "github.com/spf13/afero" "github.com/supabase/cli/internal/storage/client" "github.com/supabase/cli/internal/utils" - "github.com/supabase/cli/pkg/config" ) func Run(ctx context.Context, projectRef string, interactive bool, fsys afero.Fs) error { @@ -31,12 +29,5 @@ func Run(ctx context.Context, projectRef string, interactive bool, fsys afero.Fs if err := api.UpsertBuckets(ctx, utils.Config.Storage.Buckets, filter); err != nil { return err } - resolved := config.BucketConfig{} - for name, bucket := range utils.Config.Storage.Buckets { - if len(bucket.ObjectsPath) > 0 && !filepath.IsAbs(bucket.ObjectsPath) { - bucket.ObjectsPath = filepath.Join(utils.SupabaseDirPath, bucket.ObjectsPath) - } - resolved[name] = bucket - } - return api.UpsertObjects(ctx, resolved, utils.NewRootFS(fsys)) + return api.UpsertObjects(ctx, utils.Config.Storage.Buckets, utils.NewRootFS(fsys)) } diff --git a/internal/start/start.go b/internal/start/start.go index 72f34b8e7..ebddb6d40 100644 --- a/internal/start/start.go +++ b/internal/start/start.go @@ -485,6 +485,8 @@ EOF fmt.Sprintf("GOTRUE_EXTERNAL_EMAIL_ENABLED=%v", utils.Config.Auth.Email.EnableSignup), fmt.Sprintf("GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED=%v", utils.Config.Auth.Email.DoubleConfirmChanges), fmt.Sprintf("GOTRUE_MAILER_AUTOCONFIRM=%v", !utils.Config.Auth.Email.EnableConfirmations), + fmt.Sprintf("GOTRUE_MAILER_OTP_LENGTH=%v", utils.Config.Auth.Email.OtpLength), + fmt.Sprintf("GOTRUE_MAILER_OTP_EXP=%v", utils.Config.Auth.Email.OtpExpiry), fmt.Sprintf("GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED=%v", utils.Config.Auth.EnableAnonymousSignIns), @@ -518,6 +520,8 @@ EOF fmt.Sprintf("GOTRUE_MFA_PHONE_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.Phone.VerifyEnabled), fmt.Sprintf("GOTRUE_MFA_TOTP_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.TOTP.EnrollEnabled), fmt.Sprintf("GOTRUE_MFA_TOTP_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.TOTP.VerifyEnabled), + fmt.Sprintf("GOTRUE_MFA_WEB_AUTHN_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.WebAuthn.EnrollEnabled), + fmt.Sprintf("GOTRUE_MFA_WEB_AUTHN_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.WebAuthn.VerifyEnabled), fmt.Sprintf("GOTRUE_MFA_MAX_ENROLLED_FACTORS=%v", utils.Config.Auth.MFA.MaxEnrolledFactors), } diff --git a/internal/storage/cp/cp_test.go b/internal/storage/cp/cp_test.go index fe988c21c..75a0cf3cd 100644 --- a/internal/storage/cp/cp_test.go +++ b/internal/storage/cp/cp_test.go @@ -14,16 +14,17 @@ import ( "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/internal/utils/flags" "github.com/supabase/cli/pkg/api" + "github.com/supabase/cli/pkg/cast" "github.com/supabase/cli/pkg/fetcher" "github.com/supabase/cli/pkg/storage" ) var mockFile = storage.ObjectResponse{ Name: "abstract.pdf", - Id: utils.Ptr("9b7f9f48-17a6-4ca8-b14a-39b0205a63e9"), - UpdatedAt: utils.Ptr("2023-10-13T18:08:22.068Z"), - CreatedAt: utils.Ptr("2023-10-13T18:08:22.068Z"), - LastAccessedAt: utils.Ptr("2023-10-13T18:08:22.068Z"), + Id: cast.Ptr("9b7f9f48-17a6-4ca8-b14a-39b0205a63e9"), + UpdatedAt: cast.Ptr("2023-10-13T18:08:22.068Z"), + CreatedAt: cast.Ptr("2023-10-13T18:08:22.068Z"), + LastAccessedAt: cast.Ptr("2023-10-13T18:08:22.068Z"), Metadata: &storage.ObjectMetadata{ ETag: `"887ea9be3c68e6f2fca7fd2d7c77d8fe"`, Size: 82702, diff --git a/internal/storage/ls/ls_test.go b/internal/storage/ls/ls_test.go index f1682759c..e0e2cd207 100644 --- a/internal/storage/ls/ls_test.go +++ b/internal/storage/ls/ls_test.go @@ -14,16 +14,17 @@ import ( "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/internal/utils/flags" "github.com/supabase/cli/pkg/api" + "github.com/supabase/cli/pkg/cast" "github.com/supabase/cli/pkg/fetcher" "github.com/supabase/cli/pkg/storage" ) var mockFile = storage.ObjectResponse{ Name: "abstract.pdf", - Id: utils.Ptr("9b7f9f48-17a6-4ca8-b14a-39b0205a63e9"), - UpdatedAt: utils.Ptr("2023-10-13T18:08:22.068Z"), - CreatedAt: utils.Ptr("2023-10-13T18:08:22.068Z"), - LastAccessedAt: utils.Ptr("2023-10-13T18:08:22.068Z"), + Id: cast.Ptr("9b7f9f48-17a6-4ca8-b14a-39b0205a63e9"), + UpdatedAt: cast.Ptr("2023-10-13T18:08:22.068Z"), + CreatedAt: cast.Ptr("2023-10-13T18:08:22.068Z"), + LastAccessedAt: cast.Ptr("2023-10-13T18:08:22.068Z"), Metadata: &storage.ObjectMetadata{ ETag: `"887ea9be3c68e6f2fca7fd2d7c77d8fe"`, Size: 82702, diff --git a/internal/storage/mv/mv_test.go b/internal/storage/mv/mv_test.go index 2cb1c57e5..fd8ecfbcc 100644 --- a/internal/storage/mv/mv_test.go +++ b/internal/storage/mv/mv_test.go @@ -12,16 +12,17 @@ import ( "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/internal/utils/flags" "github.com/supabase/cli/pkg/api" + "github.com/supabase/cli/pkg/cast" "github.com/supabase/cli/pkg/fetcher" "github.com/supabase/cli/pkg/storage" ) var mockFile = storage.ObjectResponse{ Name: "abstract.pdf", - Id: utils.Ptr("9b7f9f48-17a6-4ca8-b14a-39b0205a63e9"), - UpdatedAt: utils.Ptr("2023-10-13T18:08:22.068Z"), - CreatedAt: utils.Ptr("2023-10-13T18:08:22.068Z"), - LastAccessedAt: utils.Ptr("2023-10-13T18:08:22.068Z"), + Id: cast.Ptr("9b7f9f48-17a6-4ca8-b14a-39b0205a63e9"), + UpdatedAt: cast.Ptr("2023-10-13T18:08:22.068Z"), + CreatedAt: cast.Ptr("2023-10-13T18:08:22.068Z"), + LastAccessedAt: cast.Ptr("2023-10-13T18:08:22.068Z"), Metadata: &storage.ObjectMetadata{ ETag: `"887ea9be3c68e6f2fca7fd2d7c77d8fe"`, Size: 82702, diff --git a/internal/storage/rm/rm_test.go b/internal/storage/rm/rm_test.go index 6032c5b9b..46d204cf0 100644 --- a/internal/storage/rm/rm_test.go +++ b/internal/storage/rm/rm_test.go @@ -13,16 +13,17 @@ import ( "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/internal/utils/flags" "github.com/supabase/cli/pkg/api" + "github.com/supabase/cli/pkg/cast" "github.com/supabase/cli/pkg/fetcher" "github.com/supabase/cli/pkg/storage" ) var mockFile = storage.ObjectResponse{ Name: "abstract.pdf", - Id: utils.Ptr("9b7f9f48-17a6-4ca8-b14a-39b0205a63e9"), - UpdatedAt: utils.Ptr("2023-10-13T18:08:22.068Z"), - CreatedAt: utils.Ptr("2023-10-13T18:08:22.068Z"), - LastAccessedAt: utils.Ptr("2023-10-13T18:08:22.068Z"), + Id: cast.Ptr("9b7f9f48-17a6-4ca8-b14a-39b0205a63e9"), + UpdatedAt: cast.Ptr("2023-10-13T18:08:22.068Z"), + CreatedAt: cast.Ptr("2023-10-13T18:08:22.068Z"), + LastAccessedAt: cast.Ptr("2023-10-13T18:08:22.068Z"), Metadata: &storage.ObjectMetadata{ ETag: `"887ea9be3c68e6f2fca7fd2d7c77d8fe"`, Size: 82702, diff --git a/internal/utils/api.go b/internal/utils/api.go index 3dc63d30d..7dc59a088 100644 --- a/internal/utils/api.go +++ b/internal/utils/api.go @@ -16,6 +16,7 @@ import ( "github.com/spf13/viper" "github.com/supabase/cli/internal/utils/cloudflare" supabase "github.com/supabase/cli/pkg/api" + "github.com/supabase/cli/pkg/cast" ) const ( @@ -60,7 +61,7 @@ func FallbackLookupIP(ctx context.Context, host string) ([]string, error) { func ResolveCNAME(ctx context.Context, host string) (string, error) { // Ref: https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/make-api-requests/dns-json cf := cloudflare.NewCloudflareAPI() - data, err := cf.DNSQuery(ctx, cloudflare.DNSParams{Name: host, Type: Ptr(cloudflare.TypeCNAME)}) + data, err := cf.DNSQuery(ctx, cloudflare.DNSParams{Name: host, Type: cast.Ptr(cloudflare.TypeCNAME)}) if err != nil { return "", err } diff --git a/internal/utils/console.go b/internal/utils/console.go index dfd014afc..85bebdc1e 100644 --- a/internal/utils/console.go +++ b/internal/utils/console.go @@ -10,6 +10,7 @@ import ( "time" "github.com/go-errors/errors" + "github.com/supabase/cli/pkg/cast" "golang.org/x/term" ) @@ -78,10 +79,10 @@ func (c *Console) PromptYesNo(ctx context.Context, label string, def bool) (bool func parseYesNo(s string) *bool { s = strings.ToLower(s) if s == "y" || s == "yes" { - return Ptr(true) + return cast.Ptr(true) } if s == "n" || s == "no" { - return Ptr(false) + return cast.Ptr(false) } return nil } diff --git a/internal/utils/credentials/keyring_darwin.go b/internal/utils/credentials/keyring_darwin.go deleted file mode 100644 index cca5d72fa..000000000 --- a/internal/utils/credentials/keyring_darwin.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build darwin - -package credentials - -import ( - "os/exec" - - "github.com/go-errors/errors" -) - -const execPathKeychain = "/usr/bin/security" - -func deleteAll(service string) error { - if len(service) == 0 { - return errors.New("missing service name") - } - // Delete each secret in a while loop until there is no more left - for { - if err := exec.Command( - execPathKeychain, - "delete-generic-password", - "-s", service, - ).Run(); err == nil { - continue - } else if errors.Is(err, exec.ErrNotFound) { - return errors.New(ErrNotSupported) - } else if exitError, ok := err.(*exec.ExitError); ok && exitError.ExitCode() == 44 { - // Exit 44 means no item exists for this service name - return nil - } else { - return errors.Errorf("failed to delete all credentials: %w", err) - } - } -} diff --git a/internal/utils/credentials/keyring_linux.go b/internal/utils/credentials/keyring_linux.go deleted file mode 100644 index 08095cfb7..000000000 --- a/internal/utils/credentials/keyring_linux.go +++ /dev/null @@ -1,33 +0,0 @@ -//go:build linux - -package credentials - -import ( - "github.com/go-errors/errors" - ss "github.com/zalando/go-keyring/secret_service" -) - -func deleteAll(service string) error { - svc, err := ss.NewSecretService() - if err != nil { - return errors.Errorf("failed to create secret service: %w", err) - } - - collection := svc.GetLoginCollection() - if err := svc.Unlock(collection.Path()); err != nil { - return errors.Errorf("failed to unlock collection: %w", err) - } - - search := map[string]string{"service": service} - results, err := svc.SearchItems(collection, search) - if err != nil { - return errors.Errorf("failed to search items: %w", err) - } - - for _, item := range results { - if err := svc.Delete(item); err != nil { - return errors.Errorf("failed to delete all credentials: %w", err) - } - } - return nil -} diff --git a/internal/utils/credentials/keyring_test.go b/internal/utils/credentials/keyring_test.go index 0569276aa..4f450078f 100644 --- a/internal/utils/credentials/keyring_test.go +++ b/internal/utils/credentials/keyring_test.go @@ -10,7 +10,7 @@ import ( func TestDeleteAll(t *testing.T) { service := "test-cli" // Nothing to delete - err := deleteAll(service) + err := keyring.DeleteAll(service) assert.NoError(t, err) // Setup 2 items err = keyring.Set(service, "key1", "value") @@ -18,7 +18,7 @@ func TestDeleteAll(t *testing.T) { err = keyring.Set(service, "key2", "value") assert.NoError(t, err) // Delete all items - err = deleteAll(service) + err = keyring.DeleteAll(service) assert.NoError(t, err) // Check items are gone _, err = keyring.Get(service, "key1") diff --git a/internal/utils/credentials/keyring_windows.go b/internal/utils/credentials/keyring_windows.go deleted file mode 100644 index 0366a686d..000000000 --- a/internal/utils/credentials/keyring_windows.go +++ /dev/null @@ -1,25 +0,0 @@ -//go:build windows - -package credentials - -import ( - "github.com/danieljoos/wincred" - "github.com/go-errors/errors" -) - -func deleteAll(service string) error { - if err := assertKeyringSupported(); err != nil { - return err - } - creds, err := wincred.FilteredList(service + ":") - if err != nil { - return errors.Errorf("failed to list credentials: %w", err) - } - for _, c := range creds { - gc := wincred.GenericCredential{Credential: *c} - if err := gc.Delete(); err != nil { - return errors.Errorf("failed to delete all credentials: %w", err) - } - } - return nil -} diff --git a/internal/utils/credentials/store.go b/internal/utils/credentials/store.go index 02f97e200..d7bf9d2b2 100644 --- a/internal/utils/credentials/store.go +++ b/internal/utils/credentials/store.go @@ -65,7 +65,16 @@ func (ks *KeyringStore) Delete(project string) error { } func (ks *KeyringStore) DeleteAll() error { - return deleteAll(namespace) + if err := assertKeyringSupported(); err != nil { + return err + } + if err := keyring.DeleteAll(namespace); err != nil { + if errors.Is(err, exec.ErrNotFound) { + return ErrNotSupported + } + return errors.Errorf("failed to delete all credentials in %s: %w", namespace, err) + } + return nil } func assertKeyringSupported() error { diff --git a/internal/utils/misc.go b/internal/utils/misc.go index adb0efa9c..0993ae806 100644 --- a/internal/utils/misc.go +++ b/internal/utils/misc.go @@ -295,10 +295,6 @@ func ValidateFunctionSlug(slug string) error { return nil } -func Ptr[T any](v T) *T { - return &v -} - func GetHostname() string { host := Docker.DaemonHost() if parsed, err := client.ParseHostURL(host); err == nil && parsed.Scheme == "tcp" { diff --git a/internal/utils/release_test.go b/internal/utils/release_test.go index 00b44f49d..25aa920e8 100644 --- a/internal/utils/release_test.go +++ b/internal/utils/release_test.go @@ -10,6 +10,7 @@ import ( "github.com/h2non/gock" "github.com/stretchr/testify/assert" "github.com/supabase/cli/internal/testing/apitest" + "github.com/supabase/cli/pkg/cast" ) func TestLatestRelease(t *testing.T) { @@ -19,7 +20,7 @@ func TestLatestRelease(t *testing.T) { gock.New("https://api.github.com"). Get("/repos/supabase/cli/releases/latest"). Reply(http.StatusOK). - JSON(github.RepositoryRelease{TagName: Ptr("v2")}) + JSON(github.RepositoryRelease{TagName: cast.Ptr("v2")}) // Run test version, err := GetLatestRelease(context.Background()) // Check error diff --git a/pkg/api/client.gen.go b/pkg/api/client.gen.go index b7c450896..05ed76708 100644 --- a/pkg/api/client.gen.go +++ b/pkg/api/client.gen.go @@ -226,6 +226,14 @@ type ClientInterface interface { V1UpdatePostgresConfig(ctx context.Context, ref string, body V1UpdatePostgresConfigJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // V1GetStorageConfig request + V1GetStorageConfig(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // V1UpdateStorageConfigWithBody request with any body + V1UpdateStorageConfigWithBody(ctx context.Context, ref string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + V1UpdateStorageConfig(ctx context.Context, ref string, body V1UpdateStorageConfigJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // V1DeleteHostnameConfig request V1DeleteHostnameConfig(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -262,10 +270,10 @@ type ClientInterface interface { // V1ListAllFunctions request V1ListAllFunctions(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) - // CreateFunctionWithBody request with any body - CreateFunctionWithBody(ctx context.Context, ref string, params *CreateFunctionParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + // V1CreateAFunctionWithBody request with any body + V1CreateAFunctionWithBody(ctx context.Context, ref string, params *V1CreateAFunctionParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - CreateFunction(ctx context.Context, ref string, params *CreateFunctionParams, body CreateFunctionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + V1CreateAFunction(ctx context.Context, ref string, params *V1CreateAFunctionParams, body V1CreateAFunctionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // V1DeleteAFunction request V1DeleteAFunction(ctx context.Context, ref string, functionSlug string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -993,6 +1001,42 @@ func (c *Client) V1UpdatePostgresConfig(ctx context.Context, ref string, body V1 return c.Client.Do(req) } +func (c *Client) V1GetStorageConfig(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1GetStorageConfigRequest(c.Server, ref) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) V1UpdateStorageConfigWithBody(ctx context.Context, ref string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1UpdateStorageConfigRequestWithBody(c.Server, ref, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) V1UpdateStorageConfig(ctx context.Context, ref string, body V1UpdateStorageConfigJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1UpdateStorageConfigRequest(c.Server, ref, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) V1DeleteHostnameConfig(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewV1DeleteHostnameConfigRequest(c.Server, ref) if err != nil { @@ -1149,8 +1193,8 @@ func (c *Client) V1ListAllFunctions(ctx context.Context, ref string, reqEditors return c.Client.Do(req) } -func (c *Client) CreateFunctionWithBody(ctx context.Context, ref string, params *CreateFunctionParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateFunctionRequestWithBody(c.Server, ref, params, contentType, body) +func (c *Client) V1CreateAFunctionWithBody(ctx context.Context, ref string, params *V1CreateAFunctionParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1CreateAFunctionRequestWithBody(c.Server, ref, params, contentType, body) if err != nil { return nil, err } @@ -1161,8 +1205,8 @@ func (c *Client) CreateFunctionWithBody(ctx context.Context, ref string, params return c.Client.Do(req) } -func (c *Client) CreateFunction(ctx context.Context, ref string, params *CreateFunctionParams, body CreateFunctionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateFunctionRequest(c.Server, ref, params, body) +func (c *Client) V1CreateAFunction(ctx context.Context, ref string, params *V1CreateAFunctionParams, body V1CreateAFunctionJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewV1CreateAFunctionRequest(c.Server, ref, params, body) if err != nil { return nil, err } @@ -3281,6 +3325,87 @@ func NewV1UpdatePostgresConfigRequestWithBody(server string, ref string, content return req, nil } +// NewV1GetStorageConfigRequest generates requests for V1GetStorageConfig +func NewV1GetStorageConfigRequest(server string, ref string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "ref", runtime.ParamLocationPath, ref) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/projects/%s/config/storage", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewV1UpdateStorageConfigRequest calls the generic V1UpdateStorageConfig builder with application/json body +func NewV1UpdateStorageConfigRequest(server string, ref string, body V1UpdateStorageConfigJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewV1UpdateStorageConfigRequestWithBody(server, ref, "application/json", bodyReader) +} + +// NewV1UpdateStorageConfigRequestWithBody generates requests for V1UpdateStorageConfig with any type of body +func NewV1UpdateStorageConfigRequestWithBody(server string, ref string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "ref", runtime.ParamLocationPath, ref) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/projects/%s/config/storage", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PATCH", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewV1DeleteHostnameConfigRequest generates requests for V1DeleteHostnameConfig func NewV1DeleteHostnameConfigRequest(server string, ref string) (*http.Request, error) { var err error @@ -3660,19 +3785,19 @@ func NewV1ListAllFunctionsRequest(server string, ref string) (*http.Request, err return req, nil } -// NewCreateFunctionRequest calls the generic CreateFunction builder with application/json body -func NewCreateFunctionRequest(server string, ref string, params *CreateFunctionParams, body CreateFunctionJSONRequestBody) (*http.Request, error) { +// NewV1CreateAFunctionRequest calls the generic V1CreateAFunction builder with application/json body +func NewV1CreateAFunctionRequest(server string, ref string, params *V1CreateAFunctionParams, body V1CreateAFunctionJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewCreateFunctionRequestWithBody(server, ref, params, "application/json", bodyReader) + return NewV1CreateAFunctionRequestWithBody(server, ref, params, "application/json", bodyReader) } -// NewCreateFunctionRequestWithBody generates requests for CreateFunction with any type of body -func NewCreateFunctionRequestWithBody(server string, ref string, params *CreateFunctionParams, contentType string, body io.Reader) (*http.Request, error) { +// NewV1CreateAFunctionRequestWithBody generates requests for V1CreateAFunction with any type of body +func NewV1CreateAFunctionRequestWithBody(server string, ref string, params *V1CreateAFunctionParams, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -5503,6 +5628,14 @@ type ClientWithResponsesInterface interface { V1UpdatePostgresConfigWithResponse(ctx context.Context, ref string, body V1UpdatePostgresConfigJSONRequestBody, reqEditors ...RequestEditorFn) (*V1UpdatePostgresConfigResponse, error) + // V1GetStorageConfigWithResponse request + V1GetStorageConfigWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1GetStorageConfigResponse, error) + + // V1UpdateStorageConfigWithBodyWithResponse request with any body + V1UpdateStorageConfigWithBodyWithResponse(ctx context.Context, ref string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1UpdateStorageConfigResponse, error) + + V1UpdateStorageConfigWithResponse(ctx context.Context, ref string, body V1UpdateStorageConfigJSONRequestBody, reqEditors ...RequestEditorFn) (*V1UpdateStorageConfigResponse, error) + // V1DeleteHostnameConfigWithResponse request V1DeleteHostnameConfigWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1DeleteHostnameConfigResponse, error) @@ -5539,10 +5672,10 @@ type ClientWithResponsesInterface interface { // V1ListAllFunctionsWithResponse request V1ListAllFunctionsWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1ListAllFunctionsResponse, error) - // CreateFunctionWithBodyWithResponse request with any body - CreateFunctionWithBodyWithResponse(ctx context.Context, ref string, params *CreateFunctionParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateFunctionResponse, error) + // V1CreateAFunctionWithBodyWithResponse request with any body + V1CreateAFunctionWithBodyWithResponse(ctx context.Context, ref string, params *V1CreateAFunctionParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1CreateAFunctionResponse, error) - CreateFunctionWithResponse(ctx context.Context, ref string, params *CreateFunctionParams, body CreateFunctionJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateFunctionResponse, error) + V1CreateAFunctionWithResponse(ctx context.Context, ref string, params *V1CreateAFunctionParams, body V1CreateAFunctionJSONRequestBody, reqEditors ...RequestEditorFn) (*V1CreateAFunctionResponse, error) // V1DeleteAFunctionWithResponse request V1DeleteAFunctionWithResponse(ctx context.Context, ref string, functionSlug string, reqEditors ...RequestEditorFn) (*V1DeleteAFunctionResponse, error) @@ -6482,6 +6615,49 @@ func (r V1UpdatePostgresConfigResponse) StatusCode() int { return 0 } +type V1GetStorageConfigResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *StorageConfigResponse +} + +// Status returns HTTPResponse.Status +func (r V1GetStorageConfigResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r V1GetStorageConfigResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type V1UpdateStorageConfigResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r V1UpdateStorageConfigResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r V1UpdateStorageConfigResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type V1DeleteHostnameConfigResponse struct { Body []byte HTTPResponse *http.Response @@ -6699,14 +6875,14 @@ func (r V1ListAllFunctionsResponse) StatusCode() int { return 0 } -type CreateFunctionResponse struct { +type V1CreateAFunctionResponse struct { Body []byte HTTPResponse *http.Response JSON201 *FunctionResponse } // Status returns HTTPResponse.Status -func (r CreateFunctionResponse) Status() string { +func (r V1CreateAFunctionResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -6714,7 +6890,7 @@ func (r CreateFunctionResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r CreateFunctionResponse) StatusCode() int { +func (r V1CreateAFunctionResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -7876,6 +8052,32 @@ func (c *ClientWithResponses) V1UpdatePostgresConfigWithResponse(ctx context.Con return ParseV1UpdatePostgresConfigResponse(rsp) } +// V1GetStorageConfigWithResponse request returning *V1GetStorageConfigResponse +func (c *ClientWithResponses) V1GetStorageConfigWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1GetStorageConfigResponse, error) { + rsp, err := c.V1GetStorageConfig(ctx, ref, reqEditors...) + if err != nil { + return nil, err + } + return ParseV1GetStorageConfigResponse(rsp) +} + +// V1UpdateStorageConfigWithBodyWithResponse request with arbitrary body returning *V1UpdateStorageConfigResponse +func (c *ClientWithResponses) V1UpdateStorageConfigWithBodyWithResponse(ctx context.Context, ref string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1UpdateStorageConfigResponse, error) { + rsp, err := c.V1UpdateStorageConfigWithBody(ctx, ref, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseV1UpdateStorageConfigResponse(rsp) +} + +func (c *ClientWithResponses) V1UpdateStorageConfigWithResponse(ctx context.Context, ref string, body V1UpdateStorageConfigJSONRequestBody, reqEditors ...RequestEditorFn) (*V1UpdateStorageConfigResponse, error) { + rsp, err := c.V1UpdateStorageConfig(ctx, ref, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseV1UpdateStorageConfigResponse(rsp) +} + // V1DeleteHostnameConfigWithResponse request returning *V1DeleteHostnameConfigResponse func (c *ClientWithResponses) V1DeleteHostnameConfigWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1DeleteHostnameConfigResponse, error) { rsp, err := c.V1DeleteHostnameConfig(ctx, ref, reqEditors...) @@ -7990,21 +8192,21 @@ func (c *ClientWithResponses) V1ListAllFunctionsWithResponse(ctx context.Context return ParseV1ListAllFunctionsResponse(rsp) } -// CreateFunctionWithBodyWithResponse request with arbitrary body returning *CreateFunctionResponse -func (c *ClientWithResponses) CreateFunctionWithBodyWithResponse(ctx context.Context, ref string, params *CreateFunctionParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateFunctionResponse, error) { - rsp, err := c.CreateFunctionWithBody(ctx, ref, params, contentType, body, reqEditors...) +// V1CreateAFunctionWithBodyWithResponse request with arbitrary body returning *V1CreateAFunctionResponse +func (c *ClientWithResponses) V1CreateAFunctionWithBodyWithResponse(ctx context.Context, ref string, params *V1CreateAFunctionParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*V1CreateAFunctionResponse, error) { + rsp, err := c.V1CreateAFunctionWithBody(ctx, ref, params, contentType, body, reqEditors...) if err != nil { return nil, err } - return ParseCreateFunctionResponse(rsp) + return ParseV1CreateAFunctionResponse(rsp) } -func (c *ClientWithResponses) CreateFunctionWithResponse(ctx context.Context, ref string, params *CreateFunctionParams, body CreateFunctionJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateFunctionResponse, error) { - rsp, err := c.CreateFunction(ctx, ref, params, body, reqEditors...) +func (c *ClientWithResponses) V1CreateAFunctionWithResponse(ctx context.Context, ref string, params *V1CreateAFunctionParams, body V1CreateAFunctionJSONRequestBody, reqEditors ...RequestEditorFn) (*V1CreateAFunctionResponse, error) { + rsp, err := c.V1CreateAFunction(ctx, ref, params, body, reqEditors...) if err != nil { return nil, err } - return ParseCreateFunctionResponse(rsp) + return ParseV1CreateAFunctionResponse(rsp) } // V1DeleteAFunctionWithResponse request returning *V1DeleteAFunctionResponse @@ -9350,6 +9552,48 @@ func ParseV1UpdatePostgresConfigResponse(rsp *http.Response) (*V1UpdatePostgresC return response, nil } +// ParseV1GetStorageConfigResponse parses an HTTP response from a V1GetStorageConfigWithResponse call +func ParseV1GetStorageConfigResponse(rsp *http.Response) (*V1GetStorageConfigResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &V1GetStorageConfigResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest StorageConfigResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseV1UpdateStorageConfigResponse parses an HTTP response from a V1UpdateStorageConfigWithResponse call +func ParseV1UpdateStorageConfigResponse(rsp *http.Response) (*V1UpdateStorageConfigResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &V1UpdateStorageConfigResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + // ParseV1DeleteHostnameConfigResponse parses an HTTP response from a V1DeleteHostnameConfigWithResponse call func ParseV1DeleteHostnameConfigResponse(rsp *http.Response) (*V1DeleteHostnameConfigResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -9580,15 +9824,15 @@ func ParseV1ListAllFunctionsResponse(rsp *http.Response) (*V1ListAllFunctionsRes return response, nil } -// ParseCreateFunctionResponse parses an HTTP response from a CreateFunctionWithResponse call -func ParseCreateFunctionResponse(rsp *http.Response) (*CreateFunctionResponse, error) { +// ParseV1CreateAFunctionResponse parses an HTTP response from a V1CreateAFunctionWithResponse call +func ParseV1CreateAFunctionResponse(rsp *http.Response) (*V1CreateAFunctionResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateFunctionResponse{ + response := &V1CreateAFunctionResponse{ Body: bodyBytes, HTTPResponse: rsp, } diff --git a/pkg/api/types.gen.go b/pkg/api/types.gen.go index 77714f6c3..66d467611 100644 --- a/pkg/api/types.gen.go +++ b/pkg/api/types.gen.go @@ -14,6 +14,11 @@ const ( Oauth2Scopes = "oauth2.Scopes" ) +// Defines values for AuthHealthResponseName. +const ( + GoTrue AuthHealthResponseName = "GoTrue" +) + // Defines values for BillingPlanId. const ( BillingPlanIdEnterprise BillingPlanId = "enterprise" @@ -33,6 +38,7 @@ const ( BranchDetailResponseStatusPAUSEFAILED BranchDetailResponseStatus = "PAUSE_FAILED" BranchDetailResponseStatusPAUSING BranchDetailResponseStatus = "PAUSING" BranchDetailResponseStatusREMOVED BranchDetailResponseStatus = "REMOVED" + BranchDetailResponseStatusRESIZING BranchDetailResponseStatus = "RESIZING" BranchDetailResponseStatusRESTARTING BranchDetailResponseStatus = "RESTARTING" BranchDetailResponseStatusRESTOREFAILED BranchDetailResponseStatus = "RESTORE_FAILED" BranchDetailResponseStatusRESTORING BranchDetailResponseStatus = "RESTORING" @@ -335,6 +341,7 @@ const ( V1ProjectResponseStatusPAUSEFAILED V1ProjectResponseStatus = "PAUSE_FAILED" V1ProjectResponseStatusPAUSING V1ProjectResponseStatus = "PAUSING" V1ProjectResponseStatusREMOVED V1ProjectResponseStatus = "REMOVED" + V1ProjectResponseStatusRESIZING V1ProjectResponseStatus = "RESIZING" V1ProjectResponseStatusRESTARTING V1ProjectResponseStatus = "RESTARTING" V1ProjectResponseStatusRESTOREFAILED V1ProjectResponseStatus = "RESTORE_FAILED" V1ProjectResponseStatusRESTORING V1ProjectResponseStatus = "RESTORING" @@ -446,185 +453,186 @@ type AttributeValue_Default struct { // AuthConfigResponse defines model for AuthConfigResponse. type AuthConfigResponse struct { - ApiMaxRequestDuration *float32 `json:"api_max_request_duration"` - DbMaxPoolSize *float32 `json:"db_max_pool_size"` - DisableSignup *bool `json:"disable_signup"` - ExternalAnonymousUsersEnabled *bool `json:"external_anonymous_users_enabled"` - ExternalAppleAdditionalClientIds *string `json:"external_apple_additional_client_ids"` - ExternalAppleClientId *string `json:"external_apple_client_id"` - ExternalAppleEnabled *bool `json:"external_apple_enabled"` - ExternalAppleSecret *string `json:"external_apple_secret"` - ExternalAzureClientId *string `json:"external_azure_client_id"` - ExternalAzureEnabled *bool `json:"external_azure_enabled"` - ExternalAzureSecret *string `json:"external_azure_secret"` - ExternalAzureUrl *string `json:"external_azure_url"` - ExternalBitbucketClientId *string `json:"external_bitbucket_client_id"` - ExternalBitbucketEnabled *bool `json:"external_bitbucket_enabled"` - ExternalBitbucketSecret *string `json:"external_bitbucket_secret"` - ExternalDiscordClientId *string `json:"external_discord_client_id"` - ExternalDiscordEnabled *bool `json:"external_discord_enabled"` - ExternalDiscordSecret *string `json:"external_discord_secret"` - ExternalEmailEnabled *bool `json:"external_email_enabled"` - ExternalFacebookClientId *string `json:"external_facebook_client_id"` - ExternalFacebookEnabled *bool `json:"external_facebook_enabled"` - ExternalFacebookSecret *string `json:"external_facebook_secret"` - ExternalFigmaClientId *string `json:"external_figma_client_id"` - ExternalFigmaEnabled *bool `json:"external_figma_enabled"` - ExternalFigmaSecret *string `json:"external_figma_secret"` - ExternalGithubClientId *string `json:"external_github_client_id"` - ExternalGithubEnabled *bool `json:"external_github_enabled"` - ExternalGithubSecret *string `json:"external_github_secret"` - ExternalGitlabClientId *string `json:"external_gitlab_client_id"` - ExternalGitlabEnabled *bool `json:"external_gitlab_enabled"` - ExternalGitlabSecret *string `json:"external_gitlab_secret"` - ExternalGitlabUrl *string `json:"external_gitlab_url"` - ExternalGoogleAdditionalClientIds *string `json:"external_google_additional_client_ids"` - ExternalGoogleClientId *string `json:"external_google_client_id"` - ExternalGoogleEnabled *bool `json:"external_google_enabled"` - ExternalGoogleSecret *string `json:"external_google_secret"` - ExternalGoogleSkipNonceCheck *bool `json:"external_google_skip_nonce_check"` - ExternalKakaoClientId *string `json:"external_kakao_client_id"` - ExternalKakaoEnabled *bool `json:"external_kakao_enabled"` - ExternalKakaoSecret *string `json:"external_kakao_secret"` - ExternalKeycloakClientId *string `json:"external_keycloak_client_id"` - ExternalKeycloakEnabled *bool `json:"external_keycloak_enabled"` - ExternalKeycloakSecret *string `json:"external_keycloak_secret"` - ExternalKeycloakUrl *string `json:"external_keycloak_url"` - ExternalLinkedinOidcClientId *string `json:"external_linkedin_oidc_client_id"` - ExternalLinkedinOidcEnabled *bool `json:"external_linkedin_oidc_enabled"` - ExternalLinkedinOidcSecret *string `json:"external_linkedin_oidc_secret"` - ExternalNotionClientId *string `json:"external_notion_client_id"` - ExternalNotionEnabled *bool `json:"external_notion_enabled"` - ExternalNotionSecret *string `json:"external_notion_secret"` - ExternalPhoneEnabled *bool `json:"external_phone_enabled"` - ExternalSlackClientId *string `json:"external_slack_client_id"` - ExternalSlackEnabled *bool `json:"external_slack_enabled"` - ExternalSlackOidcClientId *string `json:"external_slack_oidc_client_id"` - ExternalSlackOidcEnabled *bool `json:"external_slack_oidc_enabled"` - ExternalSlackOidcSecret *string `json:"external_slack_oidc_secret"` - ExternalSlackSecret *string `json:"external_slack_secret"` - ExternalSpotifyClientId *string `json:"external_spotify_client_id"` - ExternalSpotifyEnabled *bool `json:"external_spotify_enabled"` - ExternalSpotifySecret *string `json:"external_spotify_secret"` - ExternalTwitchClientId *string `json:"external_twitch_client_id"` - ExternalTwitchEnabled *bool `json:"external_twitch_enabled"` - ExternalTwitchSecret *string `json:"external_twitch_secret"` - ExternalTwitterClientId *string `json:"external_twitter_client_id"` - ExternalTwitterEnabled *bool `json:"external_twitter_enabled"` - ExternalTwitterSecret *string `json:"external_twitter_secret"` - ExternalWorkosClientId *string `json:"external_workos_client_id"` - ExternalWorkosEnabled *bool `json:"external_workos_enabled"` - ExternalWorkosSecret *string `json:"external_workos_secret"` - ExternalWorkosUrl *string `json:"external_workos_url"` - ExternalZoomClientId *string `json:"external_zoom_client_id"` - ExternalZoomEnabled *bool `json:"external_zoom_enabled"` - ExternalZoomSecret *string `json:"external_zoom_secret"` - HookCustomAccessTokenEnabled *bool `json:"hook_custom_access_token_enabled"` - HookCustomAccessTokenSecrets *string `json:"hook_custom_access_token_secrets"` - HookCustomAccessTokenUri *string `json:"hook_custom_access_token_uri"` - HookMfaVerificationAttemptEnabled *bool `json:"hook_mfa_verification_attempt_enabled"` - HookMfaVerificationAttemptSecrets *string `json:"hook_mfa_verification_attempt_secrets"` - HookMfaVerificationAttemptUri *string `json:"hook_mfa_verification_attempt_uri"` - HookPasswordVerificationAttemptEnabled *bool `json:"hook_password_verification_attempt_enabled"` - HookPasswordVerificationAttemptSecrets *string `json:"hook_password_verification_attempt_secrets"` - HookPasswordVerificationAttemptUri *string `json:"hook_password_verification_attempt_uri"` - HookSendEmailEnabled *bool `json:"hook_send_email_enabled"` - HookSendEmailSecrets *string `json:"hook_send_email_secrets"` - HookSendEmailUri *string `json:"hook_send_email_uri"` - HookSendSmsEnabled *bool `json:"hook_send_sms_enabled"` - HookSendSmsSecrets *string `json:"hook_send_sms_secrets"` - HookSendSmsUri *string `json:"hook_send_sms_uri"` - JwtExp *float32 `json:"jwt_exp"` - MailerAllowUnverifiedEmailSignIns *bool `json:"mailer_allow_unverified_email_sign_ins"` - MailerAutoconfirm *bool `json:"mailer_autoconfirm"` - MailerOtpExp float32 `json:"mailer_otp_exp"` - MailerOtpLength *float32 `json:"mailer_otp_length"` - MailerSecureEmailChangeEnabled *bool `json:"mailer_secure_email_change_enabled"` - MailerSubjectsConfirmation *string `json:"mailer_subjects_confirmation"` - MailerSubjectsEmailChange *string `json:"mailer_subjects_email_change"` - MailerSubjectsInvite *string `json:"mailer_subjects_invite"` - MailerSubjectsMagicLink *string `json:"mailer_subjects_magic_link"` - MailerSubjectsReauthentication *string `json:"mailer_subjects_reauthentication"` - MailerSubjectsRecovery *string `json:"mailer_subjects_recovery"` - MailerTemplatesConfirmationContent *string `json:"mailer_templates_confirmation_content"` - MailerTemplatesEmailChangeContent *string `json:"mailer_templates_email_change_content"` - MailerTemplatesInviteContent *string `json:"mailer_templates_invite_content"` - MailerTemplatesMagicLinkContent *string `json:"mailer_templates_magic_link_content"` - MailerTemplatesReauthenticationContent *string `json:"mailer_templates_reauthentication_content"` - MailerTemplatesRecoveryContent *string `json:"mailer_templates_recovery_content"` - MfaMaxEnrolledFactors *float32 `json:"mfa_max_enrolled_factors"` - MfaPhoneEnrollEnabled *bool `json:"mfa_phone_enroll_enabled"` - MfaPhoneMaxFrequency *float32 `json:"mfa_phone_max_frequency"` - MfaPhoneOtpLength float32 `json:"mfa_phone_otp_length"` - MfaPhoneTemplate *string `json:"mfa_phone_template"` - MfaPhoneVerifyEnabled *bool `json:"mfa_phone_verify_enabled"` - MfaTotpEnrollEnabled *bool `json:"mfa_totp_enroll_enabled"` - MfaTotpVerifyEnabled *bool `json:"mfa_totp_verify_enabled"` - MfaWebAuthnEnrollEnabled *bool `json:"mfa_web_authn_enroll_enabled"` - MfaWebAuthnVerifyEnabled *bool `json:"mfa_web_authn_verify_enabled"` - PasswordHibpEnabled *bool `json:"password_hibp_enabled"` - PasswordMinLength *float32 `json:"password_min_length"` - PasswordRequiredCharacters *string `json:"password_required_characters"` - RateLimitAnonymousUsers *float32 `json:"rate_limit_anonymous_users"` - RateLimitEmailSent *float32 `json:"rate_limit_email_sent"` - RateLimitOtp *float32 `json:"rate_limit_otp"` - RateLimitSmsSent *float32 `json:"rate_limit_sms_sent"` - RateLimitTokenRefresh *float32 `json:"rate_limit_token_refresh"` - RateLimitVerify *float32 `json:"rate_limit_verify"` - RefreshTokenRotationEnabled *bool `json:"refresh_token_rotation_enabled"` - SamlAllowEncryptedAssertions *bool `json:"saml_allow_encrypted_assertions"` - SamlEnabled *bool `json:"saml_enabled"` - SamlExternalUrl *string `json:"saml_external_url"` - SecurityCaptchaEnabled *bool `json:"security_captcha_enabled"` - SecurityCaptchaProvider *string `json:"security_captcha_provider"` - SecurityCaptchaSecret *string `json:"security_captcha_secret"` - SecurityManualLinkingEnabled *bool `json:"security_manual_linking_enabled"` - SecurityRefreshTokenReuseInterval *float32 `json:"security_refresh_token_reuse_interval"` - SecurityUpdatePasswordRequireReauthentication *bool `json:"security_update_password_require_reauthentication"` - SessionsInactivityTimeout *float32 `json:"sessions_inactivity_timeout"` - SessionsSinglePerUser *bool `json:"sessions_single_per_user"` - SessionsTags *string `json:"sessions_tags"` - SessionsTimebox *float32 `json:"sessions_timebox"` - SiteUrl *string `json:"site_url"` - SmsAutoconfirm *bool `json:"sms_autoconfirm"` - SmsMaxFrequency *float32 `json:"sms_max_frequency"` - SmsMessagebirdAccessKey *string `json:"sms_messagebird_access_key"` - SmsMessagebirdOriginator *string `json:"sms_messagebird_originator"` - SmsOtpExp *float32 `json:"sms_otp_exp"` - SmsOtpLength float32 `json:"sms_otp_length"` - SmsProvider *string `json:"sms_provider"` - SmsTemplate *string `json:"sms_template"` - SmsTestOtp *string `json:"sms_test_otp"` - SmsTestOtpValidUntil *string `json:"sms_test_otp_valid_until"` - SmsTextlocalApiKey *string `json:"sms_textlocal_api_key"` - SmsTextlocalSender *string `json:"sms_textlocal_sender"` - SmsTwilioAccountSid *string `json:"sms_twilio_account_sid"` - SmsTwilioAuthToken *string `json:"sms_twilio_auth_token"` - SmsTwilioContentSid *string `json:"sms_twilio_content_sid"` - SmsTwilioMessageServiceSid *string `json:"sms_twilio_message_service_sid"` - SmsTwilioVerifyAccountSid *string `json:"sms_twilio_verify_account_sid"` - SmsTwilioVerifyAuthToken *string `json:"sms_twilio_verify_auth_token"` - SmsTwilioVerifyMessageServiceSid *string `json:"sms_twilio_verify_message_service_sid"` - SmsVonageApiKey *string `json:"sms_vonage_api_key"` - SmsVonageApiSecret *string `json:"sms_vonage_api_secret"` - SmsVonageFrom *string `json:"sms_vonage_from"` - SmtpAdminEmail *string `json:"smtp_admin_email"` - SmtpHost *string `json:"smtp_host"` - SmtpMaxFrequency *float32 `json:"smtp_max_frequency"` - SmtpPass *string `json:"smtp_pass"` - SmtpPort *string `json:"smtp_port"` - SmtpSenderName *string `json:"smtp_sender_name"` - SmtpUser *string `json:"smtp_user"` - UriAllowList *string `json:"uri_allow_list"` + ApiMaxRequestDuration *int `json:"api_max_request_duration"` + DbMaxPoolSize *int `json:"db_max_pool_size"` + DisableSignup *bool `json:"disable_signup"` + ExternalAnonymousUsersEnabled *bool `json:"external_anonymous_users_enabled"` + ExternalAppleAdditionalClientIds *string `json:"external_apple_additional_client_ids"` + ExternalAppleClientId *string `json:"external_apple_client_id"` + ExternalAppleEnabled *bool `json:"external_apple_enabled"` + ExternalAppleSecret *string `json:"external_apple_secret"` + ExternalAzureClientId *string `json:"external_azure_client_id"` + ExternalAzureEnabled *bool `json:"external_azure_enabled"` + ExternalAzureSecret *string `json:"external_azure_secret"` + ExternalAzureUrl *string `json:"external_azure_url"` + ExternalBitbucketClientId *string `json:"external_bitbucket_client_id"` + ExternalBitbucketEnabled *bool `json:"external_bitbucket_enabled"` + ExternalBitbucketSecret *string `json:"external_bitbucket_secret"` + ExternalDiscordClientId *string `json:"external_discord_client_id"` + ExternalDiscordEnabled *bool `json:"external_discord_enabled"` + ExternalDiscordSecret *string `json:"external_discord_secret"` + ExternalEmailEnabled *bool `json:"external_email_enabled"` + ExternalFacebookClientId *string `json:"external_facebook_client_id"` + ExternalFacebookEnabled *bool `json:"external_facebook_enabled"` + ExternalFacebookSecret *string `json:"external_facebook_secret"` + ExternalFigmaClientId *string `json:"external_figma_client_id"` + ExternalFigmaEnabled *bool `json:"external_figma_enabled"` + ExternalFigmaSecret *string `json:"external_figma_secret"` + ExternalGithubClientId *string `json:"external_github_client_id"` + ExternalGithubEnabled *bool `json:"external_github_enabled"` + ExternalGithubSecret *string `json:"external_github_secret"` + ExternalGitlabClientId *string `json:"external_gitlab_client_id"` + ExternalGitlabEnabled *bool `json:"external_gitlab_enabled"` + ExternalGitlabSecret *string `json:"external_gitlab_secret"` + ExternalGitlabUrl *string `json:"external_gitlab_url"` + ExternalGoogleAdditionalClientIds *string `json:"external_google_additional_client_ids"` + ExternalGoogleClientId *string `json:"external_google_client_id"` + ExternalGoogleEnabled *bool `json:"external_google_enabled"` + ExternalGoogleSecret *string `json:"external_google_secret"` + ExternalGoogleSkipNonceCheck *bool `json:"external_google_skip_nonce_check"` + ExternalKakaoClientId *string `json:"external_kakao_client_id"` + ExternalKakaoEnabled *bool `json:"external_kakao_enabled"` + ExternalKakaoSecret *string `json:"external_kakao_secret"` + ExternalKeycloakClientId *string `json:"external_keycloak_client_id"` + ExternalKeycloakEnabled *bool `json:"external_keycloak_enabled"` + ExternalKeycloakSecret *string `json:"external_keycloak_secret"` + ExternalKeycloakUrl *string `json:"external_keycloak_url"` + ExternalLinkedinOidcClientId *string `json:"external_linkedin_oidc_client_id"` + ExternalLinkedinOidcEnabled *bool `json:"external_linkedin_oidc_enabled"` + ExternalLinkedinOidcSecret *string `json:"external_linkedin_oidc_secret"` + ExternalNotionClientId *string `json:"external_notion_client_id"` + ExternalNotionEnabled *bool `json:"external_notion_enabled"` + ExternalNotionSecret *string `json:"external_notion_secret"` + ExternalPhoneEnabled *bool `json:"external_phone_enabled"` + ExternalSlackClientId *string `json:"external_slack_client_id"` + ExternalSlackEnabled *bool `json:"external_slack_enabled"` + ExternalSlackOidcClientId *string `json:"external_slack_oidc_client_id"` + ExternalSlackOidcEnabled *bool `json:"external_slack_oidc_enabled"` + ExternalSlackOidcSecret *string `json:"external_slack_oidc_secret"` + ExternalSlackSecret *string `json:"external_slack_secret"` + ExternalSpotifyClientId *string `json:"external_spotify_client_id"` + ExternalSpotifyEnabled *bool `json:"external_spotify_enabled"` + ExternalSpotifySecret *string `json:"external_spotify_secret"` + ExternalTwitchClientId *string `json:"external_twitch_client_id"` + ExternalTwitchEnabled *bool `json:"external_twitch_enabled"` + ExternalTwitchSecret *string `json:"external_twitch_secret"` + ExternalTwitterClientId *string `json:"external_twitter_client_id"` + ExternalTwitterEnabled *bool `json:"external_twitter_enabled"` + ExternalTwitterSecret *string `json:"external_twitter_secret"` + ExternalWorkosClientId *string `json:"external_workos_client_id"` + ExternalWorkosEnabled *bool `json:"external_workos_enabled"` + ExternalWorkosSecret *string `json:"external_workos_secret"` + ExternalWorkosUrl *string `json:"external_workos_url"` + ExternalZoomClientId *string `json:"external_zoom_client_id"` + ExternalZoomEnabled *bool `json:"external_zoom_enabled"` + ExternalZoomSecret *string `json:"external_zoom_secret"` + HookCustomAccessTokenEnabled *bool `json:"hook_custom_access_token_enabled"` + HookCustomAccessTokenSecrets *string `json:"hook_custom_access_token_secrets"` + HookCustomAccessTokenUri *string `json:"hook_custom_access_token_uri"` + HookMfaVerificationAttemptEnabled *bool `json:"hook_mfa_verification_attempt_enabled"` + HookMfaVerificationAttemptSecrets *string `json:"hook_mfa_verification_attempt_secrets"` + HookMfaVerificationAttemptUri *string `json:"hook_mfa_verification_attempt_uri"` + HookPasswordVerificationAttemptEnabled *bool `json:"hook_password_verification_attempt_enabled"` + HookPasswordVerificationAttemptSecrets *string `json:"hook_password_verification_attempt_secrets"` + HookPasswordVerificationAttemptUri *string `json:"hook_password_verification_attempt_uri"` + HookSendEmailEnabled *bool `json:"hook_send_email_enabled"` + HookSendEmailSecrets *string `json:"hook_send_email_secrets"` + HookSendEmailUri *string `json:"hook_send_email_uri"` + HookSendSmsEnabled *bool `json:"hook_send_sms_enabled"` + HookSendSmsSecrets *string `json:"hook_send_sms_secrets"` + HookSendSmsUri *string `json:"hook_send_sms_uri"` + JwtExp *int `json:"jwt_exp"` + MailerAllowUnverifiedEmailSignIns *bool `json:"mailer_allow_unverified_email_sign_ins"` + MailerAutoconfirm *bool `json:"mailer_autoconfirm"` + MailerOtpExp int `json:"mailer_otp_exp"` + MailerOtpLength *int `json:"mailer_otp_length"` + MailerSecureEmailChangeEnabled *bool `json:"mailer_secure_email_change_enabled"` + MailerSubjectsConfirmation *string `json:"mailer_subjects_confirmation"` + MailerSubjectsEmailChange *string `json:"mailer_subjects_email_change"` + MailerSubjectsInvite *string `json:"mailer_subjects_invite"` + MailerSubjectsMagicLink *string `json:"mailer_subjects_magic_link"` + MailerSubjectsReauthentication *string `json:"mailer_subjects_reauthentication"` + MailerSubjectsRecovery *string `json:"mailer_subjects_recovery"` + MailerTemplatesConfirmationContent *string `json:"mailer_templates_confirmation_content"` + MailerTemplatesEmailChangeContent *string `json:"mailer_templates_email_change_content"` + MailerTemplatesInviteContent *string `json:"mailer_templates_invite_content"` + MailerTemplatesMagicLinkContent *string `json:"mailer_templates_magic_link_content"` + MailerTemplatesReauthenticationContent *string `json:"mailer_templates_reauthentication_content"` + MailerTemplatesRecoveryContent *string `json:"mailer_templates_recovery_content"` + MfaMaxEnrolledFactors *int `json:"mfa_max_enrolled_factors"` + MfaPhoneEnrollEnabled *bool `json:"mfa_phone_enroll_enabled"` + MfaPhoneMaxFrequency *int `json:"mfa_phone_max_frequency"` + MfaPhoneOtpLength int `json:"mfa_phone_otp_length"` + MfaPhoneTemplate *string `json:"mfa_phone_template"` + MfaPhoneVerifyEnabled *bool `json:"mfa_phone_verify_enabled"` + MfaTotpEnrollEnabled *bool `json:"mfa_totp_enroll_enabled"` + MfaTotpVerifyEnabled *bool `json:"mfa_totp_verify_enabled"` + MfaWebAuthnEnrollEnabled *bool `json:"mfa_web_authn_enroll_enabled"` + MfaWebAuthnVerifyEnabled *bool `json:"mfa_web_authn_verify_enabled"` + PasswordHibpEnabled *bool `json:"password_hibp_enabled"` + PasswordMinLength *int `json:"password_min_length"` + PasswordRequiredCharacters *string `json:"password_required_characters"` + RateLimitAnonymousUsers *int `json:"rate_limit_anonymous_users"` + RateLimitEmailSent *int `json:"rate_limit_email_sent"` + RateLimitOtp *int `json:"rate_limit_otp"` + RateLimitSmsSent *int `json:"rate_limit_sms_sent"` + RateLimitTokenRefresh *int `json:"rate_limit_token_refresh"` + RateLimitVerify *int `json:"rate_limit_verify"` + RefreshTokenRotationEnabled *bool `json:"refresh_token_rotation_enabled"` + SamlAllowEncryptedAssertions *bool `json:"saml_allow_encrypted_assertions"` + SamlEnabled *bool `json:"saml_enabled"` + SamlExternalUrl *string `json:"saml_external_url"` + SecurityCaptchaEnabled *bool `json:"security_captcha_enabled"` + SecurityCaptchaProvider *string `json:"security_captcha_provider"` + SecurityCaptchaSecret *string `json:"security_captcha_secret"` + SecurityManualLinkingEnabled *bool `json:"security_manual_linking_enabled"` + SecurityRefreshTokenReuseInterval *int `json:"security_refresh_token_reuse_interval"` + SecurityUpdatePasswordRequireReauthentication *bool `json:"security_update_password_require_reauthentication"` + SessionsInactivityTimeout *int `json:"sessions_inactivity_timeout"` + SessionsSinglePerUser *bool `json:"sessions_single_per_user"` + SessionsTags *string `json:"sessions_tags"` + SessionsTimebox *int `json:"sessions_timebox"` + SiteUrl *string `json:"site_url"` + SmsAutoconfirm *bool `json:"sms_autoconfirm"` + SmsMaxFrequency *int `json:"sms_max_frequency"` + SmsMessagebirdAccessKey *string `json:"sms_messagebird_access_key"` + SmsMessagebirdOriginator *string `json:"sms_messagebird_originator"` + SmsOtpExp *int `json:"sms_otp_exp"` + SmsOtpLength int `json:"sms_otp_length"` + SmsProvider *string `json:"sms_provider"` + SmsTemplate *string `json:"sms_template"` + SmsTestOtp *string `json:"sms_test_otp"` + SmsTestOtpValidUntil *string `json:"sms_test_otp_valid_until"` + SmsTextlocalApiKey *string `json:"sms_textlocal_api_key"` + SmsTextlocalSender *string `json:"sms_textlocal_sender"` + SmsTwilioAccountSid *string `json:"sms_twilio_account_sid"` + SmsTwilioAuthToken *string `json:"sms_twilio_auth_token"` + SmsTwilioContentSid *string `json:"sms_twilio_content_sid"` + SmsTwilioMessageServiceSid *string `json:"sms_twilio_message_service_sid"` + SmsTwilioVerifyAccountSid *string `json:"sms_twilio_verify_account_sid"` + SmsTwilioVerifyAuthToken *string `json:"sms_twilio_verify_auth_token"` + SmsTwilioVerifyMessageServiceSid *string `json:"sms_twilio_verify_message_service_sid"` + SmsVonageApiKey *string `json:"sms_vonage_api_key"` + SmsVonageApiSecret *string `json:"sms_vonage_api_secret"` + SmsVonageFrom *string `json:"sms_vonage_from"` + SmtpAdminEmail *string `json:"smtp_admin_email"` + SmtpHost *string `json:"smtp_host"` + SmtpMaxFrequency *int `json:"smtp_max_frequency"` + SmtpPass *string `json:"smtp_pass"` + SmtpPort *string `json:"smtp_port"` + SmtpSenderName *string `json:"smtp_sender_name"` + SmtpUser *string `json:"smtp_user"` + UriAllowList *string `json:"uri_allow_list"` } // AuthHealthResponse defines model for AuthHealthResponse. type AuthHealthResponse struct { - Description string `json:"description"` - Name string `json:"name"` - Version string `json:"version"` + Name AuthHealthResponseName `json:"name"` } +// AuthHealthResponseName defines model for AuthHealthResponse.Name. +type AuthHealthResponseName string + // BillingPlanId defines model for BillingPlanId. type BillingPlanId string @@ -652,7 +660,8 @@ type BranchDetailResponseStatus string // BranchResetResponse defines model for BranchResetResponse. type BranchResetResponse struct { - Message string `json:"message"` + Message string `json:"message"` + WorkflowRunId string `json:"workflow_run_id"` } // BranchResponse defines model for BranchResponse. @@ -661,11 +670,11 @@ type BranchResponse struct { GitBranch *string `json:"git_branch,omitempty"` Id string `json:"id"` IsDefault bool `json:"is_default"` - LatestCheckRunId *float32 `json:"latest_check_run_id,omitempty"` + LatestCheckRunId *int64 `json:"latest_check_run_id,omitempty"` Name string `json:"name"` ParentProjectRef string `json:"parent_project_ref"` Persistent bool `json:"persistent"` - PrNumber *float32 `json:"pr_number,omitempty"` + PrNumber *int32 `json:"pr_number,omitempty"` ProjectRef string `json:"project_ref"` ResetOnPush bool `json:"reset_on_push"` Status BranchResponseStatus `json:"status"` @@ -766,7 +775,7 @@ type DatabaseUpgradeStatus struct { LatestStatusAt string `json:"latest_status_at"` Progress *DatabaseUpgradeStatusProgress `json:"progress,omitempty"` Status DatabaseUpgradeStatusStatus `json:"status"` - TargetVersion float32 `json:"target_version"` + TargetVersion int `json:"target_version"` } // DatabaseUpgradeStatusError defines model for DatabaseUpgradeStatus.Error. @@ -776,7 +785,7 @@ type DatabaseUpgradeStatusError string type DatabaseUpgradeStatusProgress string // DatabaseUpgradeStatusStatus defines model for DatabaseUpgradeStatus.Status. -type DatabaseUpgradeStatusStatus float32 +type DatabaseUpgradeStatusStatus int // DatabaseUpgradeStatusResponse defines model for DatabaseUpgradeStatusResponse. type DatabaseUpgradeStatusResponse struct { @@ -805,7 +814,7 @@ type Domain struct { // FunctionResponse defines model for FunctionResponse. type FunctionResponse struct { - CreatedAt float32 `json:"created_at"` + CreatedAt int64 `json:"created_at"` EntrypointPath *string `json:"entrypoint_path,omitempty"` Id string `json:"id"` ImportMap *bool `json:"import_map,omitempty"` @@ -813,9 +822,9 @@ type FunctionResponse struct { Name string `json:"name"` Slug string `json:"slug"` Status FunctionResponseStatus `json:"status"` - UpdatedAt float32 `json:"updated_at"` + UpdatedAt int64 `json:"updated_at"` VerifyJwt *bool `json:"verify_jwt,omitempty"` - Version float32 `json:"version"` + Version int `json:"version"` } // FunctionResponseStatus defines model for FunctionResponse.Status. @@ -823,7 +832,7 @@ type FunctionResponseStatus string // FunctionSlugResponse defines model for FunctionSlugResponse. type FunctionSlugResponse struct { - CreatedAt float32 `json:"created_at"` + CreatedAt int64 `json:"created_at"` EntrypointPath *string `json:"entrypoint_path,omitempty"` Id string `json:"id"` ImportMap *bool `json:"import_map,omitempty"` @@ -831,9 +840,9 @@ type FunctionSlugResponse struct { Name string `json:"name"` Slug string `json:"slug"` Status FunctionSlugResponseStatus `json:"status"` - UpdatedAt float32 `json:"updated_at"` + UpdatedAt int64 `json:"updated_at"` VerifyJwt *bool `json:"verify_jwt,omitempty"` - Version float32 `json:"version"` + Version int `json:"version"` } // FunctionSlugResponseStatus defines model for FunctionSlugResponse.Status. @@ -895,7 +904,7 @@ type OAuthTokenBodyGrantType string // OAuthTokenResponse defines model for OAuthTokenResponse. type OAuthTokenResponse struct { AccessToken string `json:"access_token"` - ExpiresIn float32 `json:"expires_in"` + ExpiresIn int64 `json:"expires_in"` RefreshToken string `json:"refresh_token"` TokenType OAuthTokenResponseTokenType `json:"token_type"` } @@ -967,7 +976,7 @@ type PostgrestConfigWithJWTSecretResponse struct { type ProjectUpgradeEligibilityResponse struct { CurrentAppVersion string `json:"current_app_version"` CurrentAppVersionReleaseChannel ReleaseChannel `json:"current_app_version_release_channel"` - DurationEstimateHours float32 `json:"duration_estimate_hours"` + DurationEstimateHours int `json:"duration_estimate_hours"` Eligible bool `json:"eligible"` ExtensionDependentObjects []string `json:"extension_dependent_objects"` LatestAppVersion string `json:"latest_app_version"` @@ -1008,9 +1017,7 @@ type ReadOnlyStatusResponse struct { // RealtimeHealthResponse defines model for RealtimeHealthResponse. type RealtimeHealthResponse struct { - ConnectedCluster float32 `json:"connected_cluster"` - DbConnected bool `json:"db_connected"` - Healthy bool `json:"healthy"` + ConnectedCluster int `json:"connected_cluster"` } // ReleaseChannel defines model for ReleaseChannel. @@ -1084,8 +1091,8 @@ type SnippetMetaVisibility string // SnippetProject defines model for SnippetProject. type SnippetProject struct { - Id float32 `json:"id"` - Name string `json:"name"` + Id int64 `json:"id"` + Name string `json:"name"` } // SnippetResponse defines model for SnippetResponse. @@ -1111,8 +1118,8 @@ type SnippetResponseVisibility string // SnippetUser defines model for SnippetUser. type SnippetUser struct { - Id float32 `json:"id"` - Username string `json:"username"` + Id int64 `json:"id"` + Username string `json:"username"` } // SslEnforcementRequest defines model for SslEnforcementRequest. @@ -1138,6 +1145,22 @@ type SslValidation struct { ValidationRecords []ValidationRecord `json:"validation_records"` } +// StorageConfigResponse defines model for StorageConfigResponse. +type StorageConfigResponse struct { + Features StorageFeatures `json:"features"` + FileSizeLimit int64 `json:"fileSizeLimit"` +} + +// StorageFeatureImageTransformation defines model for StorageFeatureImageTransformation. +type StorageFeatureImageTransformation struct { + Enabled bool `json:"enabled"` +} + +// StorageFeatures defines model for StorageFeatures. +type StorageFeatures struct { + ImageTransformation StorageFeatureImageTransformation `json:"imageTransformation"` +} + // SubdomainAvailabilityResponse defines model for SubdomainAvailabilityResponse. type SubdomainAvailabilityResponse struct { Available bool `json:"available"` @@ -1149,12 +1172,12 @@ type SupavisorConfigResponse struct { DatabaseType SupavisorConfigResponseDatabaseType `json:"database_type"` DbHost string `json:"db_host"` DbName string `json:"db_name"` - DbPort float32 `json:"db_port"` + DbPort int `json:"db_port"` DbUser string `json:"db_user"` - DefaultPoolSize *float32 `json:"default_pool_size"` + DefaultPoolSize *int `json:"default_pool_size"` Identifier string `json:"identifier"` IsUsingScramAuth bool `json:"is_using_scram_auth"` - MaxClientConn *float32 `json:"max_client_conn"` + MaxClientConn *int `json:"max_client_conn"` PoolMode SupavisorConfigResponsePoolMode `json:"pool_mode"` } @@ -1190,8 +1213,8 @@ type UpdateApiKeyBody struct { // UpdateAuthConfigBody defines model for UpdateAuthConfigBody. type UpdateAuthConfigBody struct { - ApiMaxRequestDuration *float32 `json:"api_max_request_duration,omitempty"` - DbMaxPoolSize *float32 `json:"db_max_pool_size,omitempty"` + ApiMaxRequestDuration *int `json:"api_max_request_duration,omitempty"` + DbMaxPoolSize *int `json:"db_max_pool_size,omitempty"` DisableSignup *bool `json:"disable_signup,omitempty"` ExternalAnonymousUsersEnabled *bool `json:"external_anonymous_users_enabled,omitempty"` ExternalAppleAdditionalClientIds *string `json:"external_apple_additional_client_ids,omitempty"` @@ -1278,11 +1301,11 @@ type UpdateAuthConfigBody struct { HookSendSmsEnabled *bool `json:"hook_send_sms_enabled,omitempty"` HookSendSmsSecrets *string `json:"hook_send_sms_secrets,omitempty"` HookSendSmsUri *string `json:"hook_send_sms_uri,omitempty"` - JwtExp *float32 `json:"jwt_exp,omitempty"` + JwtExp *int `json:"jwt_exp,omitempty"` MailerAllowUnverifiedEmailSignIns *bool `json:"mailer_allow_unverified_email_sign_ins,omitempty"` MailerAutoconfirm *bool `json:"mailer_autoconfirm,omitempty"` - MailerOtpExp *float32 `json:"mailer_otp_exp,omitempty"` - MailerOtpLength *float32 `json:"mailer_otp_length,omitempty"` + MailerOtpExp *int `json:"mailer_otp_exp,omitempty"` + MailerOtpLength *int `json:"mailer_otp_length,omitempty"` MailerSecureEmailChangeEnabled *bool `json:"mailer_secure_email_change_enabled,omitempty"` MailerSubjectsConfirmation *string `json:"mailer_subjects_confirmation,omitempty"` MailerSubjectsEmailChange *string `json:"mailer_subjects_email_change,omitempty"` @@ -1296,10 +1319,10 @@ type UpdateAuthConfigBody struct { MailerTemplatesMagicLinkContent *string `json:"mailer_templates_magic_link_content,omitempty"` MailerTemplatesReauthenticationContent *string `json:"mailer_templates_reauthentication_content,omitempty"` MailerTemplatesRecoveryContent *string `json:"mailer_templates_recovery_content,omitempty"` - MfaMaxEnrolledFactors *float32 `json:"mfa_max_enrolled_factors,omitempty"` + MfaMaxEnrolledFactors *int `json:"mfa_max_enrolled_factors,omitempty"` MfaPhoneEnrollEnabled *bool `json:"mfa_phone_enroll_enabled,omitempty"` - MfaPhoneMaxFrequency *float32 `json:"mfa_phone_max_frequency,omitempty"` - MfaPhoneOtpLength *float32 `json:"mfa_phone_otp_length,omitempty"` + MfaPhoneMaxFrequency *int `json:"mfa_phone_max_frequency,omitempty"` + MfaPhoneOtpLength *int `json:"mfa_phone_otp_length,omitempty"` MfaPhoneTemplate *string `json:"mfa_phone_template,omitempty"` MfaPhoneVerifyEnabled *bool `json:"mfa_phone_verify_enabled,omitempty"` MfaTotpEnrollEnabled *bool `json:"mfa_totp_enroll_enabled,omitempty"` @@ -1307,14 +1330,14 @@ type UpdateAuthConfigBody struct { MfaWebAuthnEnrollEnabled *bool `json:"mfa_web_authn_enroll_enabled,omitempty"` MfaWebAuthnVerifyEnabled *bool `json:"mfa_web_authn_verify_enabled,omitempty"` PasswordHibpEnabled *bool `json:"password_hibp_enabled,omitempty"` - PasswordMinLength *float32 `json:"password_min_length,omitempty"` + PasswordMinLength *int `json:"password_min_length,omitempty"` PasswordRequiredCharacters *UpdateAuthConfigBodyPasswordRequiredCharacters `json:"password_required_characters,omitempty"` - RateLimitAnonymousUsers *float32 `json:"rate_limit_anonymous_users,omitempty"` - RateLimitEmailSent *float32 `json:"rate_limit_email_sent,omitempty"` - RateLimitOtp *float32 `json:"rate_limit_otp,omitempty"` - RateLimitSmsSent *float32 `json:"rate_limit_sms_sent,omitempty"` - RateLimitTokenRefresh *float32 `json:"rate_limit_token_refresh,omitempty"` - RateLimitVerify *float32 `json:"rate_limit_verify,omitempty"` + RateLimitAnonymousUsers *int `json:"rate_limit_anonymous_users,omitempty"` + RateLimitEmailSent *int `json:"rate_limit_email_sent,omitempty"` + RateLimitOtp *int `json:"rate_limit_otp,omitempty"` + RateLimitSmsSent *int `json:"rate_limit_sms_sent,omitempty"` + RateLimitTokenRefresh *int `json:"rate_limit_token_refresh,omitempty"` + RateLimitVerify *int `json:"rate_limit_verify,omitempty"` RefreshTokenRotationEnabled *bool `json:"refresh_token_rotation_enabled,omitempty"` SamlEnabled *bool `json:"saml_enabled,omitempty"` SamlExternalUrl *string `json:"saml_external_url,omitempty"` @@ -1322,19 +1345,19 @@ type UpdateAuthConfigBody struct { SecurityCaptchaProvider *string `json:"security_captcha_provider,omitempty"` SecurityCaptchaSecret *string `json:"security_captcha_secret,omitempty"` SecurityManualLinkingEnabled *bool `json:"security_manual_linking_enabled,omitempty"` - SecurityRefreshTokenReuseInterval *float32 `json:"security_refresh_token_reuse_interval,omitempty"` + SecurityRefreshTokenReuseInterval *int `json:"security_refresh_token_reuse_interval,omitempty"` SecurityUpdatePasswordRequireReauthentication *bool `json:"security_update_password_require_reauthentication,omitempty"` - SessionsInactivityTimeout *float32 `json:"sessions_inactivity_timeout,omitempty"` + SessionsInactivityTimeout *int `json:"sessions_inactivity_timeout,omitempty"` SessionsSinglePerUser *bool `json:"sessions_single_per_user,omitempty"` SessionsTags *string `json:"sessions_tags,omitempty"` - SessionsTimebox *float32 `json:"sessions_timebox,omitempty"` + SessionsTimebox *int `json:"sessions_timebox,omitempty"` SiteUrl *string `json:"site_url,omitempty"` SmsAutoconfirm *bool `json:"sms_autoconfirm,omitempty"` - SmsMaxFrequency *float32 `json:"sms_max_frequency,omitempty"` + SmsMaxFrequency *int `json:"sms_max_frequency,omitempty"` SmsMessagebirdAccessKey *string `json:"sms_messagebird_access_key,omitempty"` SmsMessagebirdOriginator *string `json:"sms_messagebird_originator,omitempty"` - SmsOtpExp *float32 `json:"sms_otp_exp,omitempty"` - SmsOtpLength *float32 `json:"sms_otp_length,omitempty"` + SmsOtpExp *int `json:"sms_otp_exp,omitempty"` + SmsOtpLength *int `json:"sms_otp_length,omitempty"` SmsProvider *string `json:"sms_provider,omitempty"` SmsTemplate *string `json:"sms_template,omitempty"` SmsTestOtp *string `json:"sms_test_otp,omitempty"` @@ -1353,7 +1376,7 @@ type UpdateAuthConfigBody struct { SmsVonageFrom *string `json:"sms_vonage_from,omitempty"` SmtpAdminEmail *string `json:"smtp_admin_email,omitempty"` SmtpHost *string `json:"smtp_host,omitempty"` - SmtpMaxFrequency *float32 `json:"smtp_max_frequency,omitempty"` + SmtpMaxFrequency *int `json:"smtp_max_frequency,omitempty"` SmtpPass *string `json:"smtp_pass,omitempty"` SmtpPort *string `json:"smtp_port,omitempty"` SmtpSenderName *string `json:"smtp_sender_name,omitempty"` @@ -1450,6 +1473,12 @@ type UpdateProviderResponse struct { UpdatedAt *string `json:"updated_at,omitempty"` } +// UpdateStorageConfigBody defines model for UpdateStorageConfigBody. +type UpdateStorageConfigBody struct { + Features *StorageFeatures `json:"features,omitempty"` + FileSizeLimit *int64 `json:"fileSizeLimit,omitempty"` +} + // UpdateSupavisorConfigBody defines model for UpdateSupavisorConfigBody. type UpdateSupavisorConfigBody struct { DefaultPoolSize *int `json:"default_pool_size"` @@ -1464,7 +1493,7 @@ type UpdateSupavisorConfigBodyPoolMode string // UpdateSupavisorConfigResponse defines model for UpdateSupavisorConfigResponse. type UpdateSupavisorConfigResponse struct { - DefaultPoolSize *float32 `json:"default_pool_size"` + DefaultPoolSize *int `json:"default_pool_size"` PoolMode UpdateSupavisorConfigResponsePoolMode `json:"pool_mode"` } @@ -1591,8 +1620,8 @@ type V1PgbouncerConfigResponsePoolMode string // V1PhysicalBackup defines model for V1PhysicalBackup. type V1PhysicalBackup struct { - EarliestPhysicalBackupDateUnix *float32 `json:"earliest_physical_backup_date_unix,omitempty"` - LatestPhysicalBackupDateUnix *float32 `json:"latest_physical_backup_date_unix,omitempty"` + EarliestPhysicalBackupDateUnix *int64 `json:"earliest_physical_backup_date_unix,omitempty"` + LatestPhysicalBackupDateUnix *int64 `json:"latest_physical_backup_date_unix,omitempty"` } // V1PostgrestConfigResponse defines model for V1PostgrestConfigResponse. @@ -1607,9 +1636,9 @@ type V1PostgrestConfigResponse struct { // V1ProjectRefResponse defines model for V1ProjectRefResponse. type V1ProjectRefResponse struct { - Id float32 `json:"id"` - Name string `json:"name"` - Ref string `json:"ref"` + Id int64 `json:"id"` + Name string `json:"name"` + Ref string `json:"ref"` } // V1ProjectResponse defines model for V1ProjectResponse. @@ -1637,7 +1666,7 @@ type V1ProjectResponseStatus string // V1RestorePitrBody defines model for V1RestorePitrBody. type V1RestorePitrBody struct { - RecoveryTimeTargetUnix float32 `json:"recovery_time_target_unix"` + RecoveryTimeTargetUnix int64 `json:"recovery_time_target_unix"` } // V1RunQueryBody defines model for V1RunQueryBody. @@ -1725,8 +1754,8 @@ type V1AuthorizeUserParamsResponseType string // V1AuthorizeUserParamsCodeChallengeMethod defines parameters for V1AuthorizeUser. type V1AuthorizeUserParamsCodeChallengeMethod string -// CreateFunctionParams defines parameters for CreateFunction. -type CreateFunctionParams struct { +// V1CreateAFunctionParams defines parameters for V1CreateAFunction. +type V1CreateAFunctionParams struct { Slug *string `form:"slug,omitempty" json:"slug,omitempty"` Name *string `form:"name,omitempty" json:"name,omitempty"` VerifyJwt *bool `form:"verify_jwt,omitempty" json:"verify_jwt,omitempty"` @@ -1814,6 +1843,9 @@ type V1UpdateSupavisorConfigJSONRequestBody = UpdateSupavisorConfigBody // V1UpdatePostgresConfigJSONRequestBody defines body for V1UpdatePostgresConfig for application/json ContentType. type V1UpdatePostgresConfigJSONRequestBody = UpdatePostgresConfigBody +// V1UpdateStorageConfigJSONRequestBody defines body for V1UpdateStorageConfig for application/json ContentType. +type V1UpdateStorageConfigJSONRequestBody = UpdateStorageConfigBody + // V1UpdateHostnameConfigJSONRequestBody defines body for V1UpdateHostnameConfig for application/json ContentType. type V1UpdateHostnameConfigJSONRequestBody = UpdateCustomHostnameBody @@ -1823,8 +1855,8 @@ type V1RestorePitrBackupJSONRequestBody = V1RestorePitrBody // V1RunAQueryJSONRequestBody defines body for V1RunAQuery for application/json ContentType. type V1RunAQueryJSONRequestBody = V1RunQueryBody -// CreateFunctionJSONRequestBody defines body for CreateFunction for application/json ContentType. -type CreateFunctionJSONRequestBody = V1CreateFunctionBody +// V1CreateAFunctionJSONRequestBody defines body for V1CreateAFunction for application/json ContentType. +type V1CreateAFunctionJSONRequestBody = V1CreateFunctionBody // V1UpdateAFunctionJSONRequestBody defines body for V1UpdateAFunction for application/json ContentType. type V1UpdateAFunctionJSONRequestBody = V1UpdateFunctionBody diff --git a/pkg/cast/cast.go b/pkg/cast/cast.go index b72cadbbf..3c7067163 100644 --- a/pkg/cast/cast.go +++ b/pkg/cast/cast.go @@ -20,6 +20,20 @@ func IntToUint(value int) uint { return uint(value) } +func UintToIntPtr(value *uint) *int { + if value == nil { + return nil + } + return Ptr(UintToInt(*value)) +} + +func IntToUintPtr(value *int) *uint { + if value == nil { + return nil + } + return Ptr(IntToUint(*value)) +} + func Ptr[T any](v T) *T { return &v } diff --git a/pkg/config/config.go b/pkg/config/config.go index f6329f459..6589fb4e5 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -30,9 +30,8 @@ import ( "github.com/joho/godotenv" "github.com/mitchellh/mapstructure" "github.com/spf13/viper" - "golang.org/x/mod/semver" - "github.com/supabase/cli/pkg/fetcher" + "golang.org/x/mod/semver" ) // Type for turning human-friendly bytes string ("5MB", "32kB") into an int64 during toml decoding. @@ -57,13 +56,6 @@ const ( LogflareBigQuery LogflareBackend = "bigquery" ) -type PoolMode string - -const ( - TransactionMode PoolMode = "transaction" - SessionMode PoolMode = "session" -) - type AddressFamily string const ( @@ -146,36 +138,6 @@ type ( Remotes map[string]baseConfig `toml:"-"` } - db struct { - Image string `toml:"-"` - Port uint16 `toml:"port"` - ShadowPort uint16 `toml:"shadow_port"` - MajorVersion uint `toml:"major_version"` - Password string `toml:"-"` - RootKey string `toml:"-" mapstructure:"root_key"` - Pooler pooler `toml:"pooler"` - Seed seed `toml:"seed"` - } - - seed struct { - Enabled bool `toml:"enabled"` - GlobPatterns []string `toml:"sql_paths"` - SqlPaths []string `toml:"-"` - } - - pooler struct { - Enabled bool `toml:"enabled"` - Image string `toml:"-"` - Port uint16 `toml:"port"` - PoolMode PoolMode `toml:"pool_mode"` - DefaultPoolSize uint `toml:"default_pool_size"` - MaxClientConn uint `toml:"max_client_conn"` - ConnectionString string `toml:"-"` - TenantId string `toml:"-"` - EncryptionKey string `toml:"-"` - SecretKeyBase string `toml:"-"` - } - realtime struct { Enabled bool `toml:"enabled"` Image string `toml:"-"` @@ -203,35 +165,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:"-"` @@ -295,6 +228,8 @@ type ( Template map[string]emailTemplate `toml:"template"` Smtp smtp `toml:"smtp"` MaxFrequency time.Duration `toml:"max_frequency"` + OtpLength uint `toml:"otp_length"` + OtpExpiry uint `toml:"otp_expiry"` } smtp struct { @@ -346,6 +281,7 @@ type ( mfa struct { TOTP factorTypeConfiguration `toml:"totp"` Phone phoneFactorTypeConfiguration `toml:"phone"` + WebAuthn factorTypeConfiguration `toml:"web_authn"` MaxEnrolledFactors uint `toml:"max_enrolled_factors"` } @@ -405,10 +341,10 @@ type ( FunctionConfig map[string]function function struct { - Enabled *bool `toml:"enabled"` + Enabled *bool `toml:"enabled" json:"-"` VerifyJWT *bool `toml:"verify_jwt" json:"verifyJWT"` ImportMap string `toml:"import_map" json:"importMapPath,omitempty"` - Entrypoint string `json:"-"` + Entrypoint string `toml:"entrypoint" json:"entrypointPath,omitempty"` } analytics struct { @@ -425,12 +361,17 @@ type ( VectorPort uint16 `toml:"vector_port"` } + webhooks struct { + Enabled bool `toml:"enabled"` + } + experimental struct { - OrioleDBVersion string `toml:"orioledb_version"` - S3Host string `toml:"s3_host"` - S3Region string `toml:"s3_region"` - S3AccessKey string `toml:"s3_access_key"` - S3SecretKey string `toml:"s3_secret_key"` + OrioleDBVersion string `toml:"orioledb_version"` + S3Host string `toml:"s3_host"` + S3Region string `toml:"s3_region"` + S3AccessKey string `toml:"s3_access_key"` + S3SecretKey string `toml:"s3_secret_key"` + Webhooks *webhooks `toml:"webhooks"` } ) @@ -707,23 +648,34 @@ func (c *config) Load(path string, fsys fs.FS) error { if bucket.FileSizeLimit == 0 { bucket.FileSizeLimit = c.Storage.FileSizeLimit } + if len(bucket.ObjectsPath) > 0 && !filepath.IsAbs(bucket.ObjectsPath) { + bucket.ObjectsPath = filepath.Join(builder.SupabaseDirPath, bucket.ObjectsPath) + } c.Storage.Buckets[name] = bucket } + // Resolve functions config for slug, function := range c.Functions { - // TODO: support configuring alternative entrypoint path, such as index.js if len(function.Entrypoint) == 0 { function.Entrypoint = filepath.Join(builder.FunctionsDir, slug, "index.ts") } else if !filepath.IsAbs(function.Entrypoint) { // Append supabase/ because paths in configs are specified relative to config.toml function.Entrypoint = filepath.Join(builder.SupabaseDirPath, function.Entrypoint) } - // Functions may not use import map so we don't set a default value - if len(function.ImportMap) > 0 && !filepath.IsAbs(function.ImportMap) { + if len(function.ImportMap) == 0 { + functionDir := filepath.Dir(function.Entrypoint) + denoJsonPath := filepath.Join(functionDir, "deno.json") + denoJsoncPath := filepath.Join(functionDir, "deno.jsonc") + if _, err := fs.Stat(fsys, denoJsonPath); err == nil { + function.ImportMap = denoJsonPath + } else if _, err := fs.Stat(fsys, denoJsoncPath); err == nil { + function.ImportMap = denoJsoncPath + } + // Functions may not use import map so we don't set a default value + } else if !filepath.IsAbs(function.ImportMap) { function.ImportMap = filepath.Join(builder.SupabaseDirPath, function.ImportMap) } c.Functions[slug] = function } - if err := c.Db.Seed.loadSeedPaths(builder.SupabaseDirPath, fsys); err != nil { return err } @@ -734,6 +686,8 @@ func (c *config) Load(path string, fsys fs.FS) error { c.Remotes = make(map[string]baseConfig, len(c.Overrides)) for name, remote := range c.Overrides { base := c.baseConfig.Clone() + // On remotes branches set seed as disabled by default + base.Db.Seed.Enabled = false // Encode a toml file with only config overrides var buf bytes.Buffer if err := toml.NewEncoder(&buf).Encode(remote); err != nil { @@ -775,6 +729,12 @@ func (c *baseConfig) Validate(fsys fs.FS) error { } } // Validate db config + if c.Db.Settings.SessionReplicationRole != nil { + allowedRoles := []SessionReplicationRole{SessionReplicationRoleOrigin, SessionReplicationRoleReplica, SessionReplicationRoleLocal} + if !sliceContains(allowedRoles, *c.Db.Settings.SessionReplicationRole) { + return errors.Errorf("Invalid config for db.session_replication_role: %s. Must be one of: %v", *c.Db.Settings.SessionReplicationRole, allowedRoles) + } + } if c.Db.Port == 0 { return errors.New("Missing required field in config: db.port") } @@ -1015,6 +975,9 @@ func (c *baseConfig) Validate(fsys fs.FS) error { return errors.Errorf("Invalid config for analytics.backend. Must be one of: %v", allowed) } } + if err := c.Experimental.validateWebhooks(); err != nil { + return err + } return nil } @@ -1380,3 +1343,12 @@ func ToTomlBytes(config any) ([]byte, error) { } return buf.Bytes(), nil } + +func (e *experimental) validateWebhooks() error { + if e.Webhooks != nil { + if !e.Webhooks.Enabled { + return errors.Errorf("Webhooks cannot be deactivated. [experimental.webhooks] enabled can either be true or left undefined") + } + } + return nil +} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index c10669350..d80ea915b 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -93,9 +93,12 @@ func TestConfigParsing(t *testing.T) { assert.Equal(t, false, production.Auth.EnableSignup) assert.Equal(t, false, production.Auth.External["azure"].Enabled) assert.Equal(t, "nope", production.Auth.External["azure"].ClientId) + // Check seed should be disabled by default for remote configs + assert.Equal(t, false, production.Db.Seed.Enabled) // Check the values for the staging override assert.Equal(t, "staging-project", staging.ProjectId) assert.Equal(t, []string{"image/png"}, staging.Storage.Buckets["images"].AllowedMimeTypes) + assert.Equal(t, true, staging.Db.Seed.Enabled) }) } @@ -353,3 +356,54 @@ func TestLoadEnv(t *testing.T) { assert.Equal(t, "test-secret", config.Auth.JwtSecret) assert.Equal(t, "test-root-key", config.Db.RootKey) } + +func TestLoadFunctionImportMap(t *testing.T) { + t.Run("uses deno.json as import map when present", func(t *testing.T) { + config := NewConfig() + fsys := fs.MapFS{ + "supabase/config.toml": &fs.MapFile{Data: []byte(` + project_id = "test" + [functions.hello] + `)}, + "supabase/functions/hello/deno.json": &fs.MapFile{}, + "supabase/functions/hello/index.ts": &fs.MapFile{}, + } + // Run test + assert.NoError(t, config.Load("", fsys)) + // Check that deno.json was set as import map + assert.Equal(t, "supabase/functions/hello/deno.json", config.Functions["hello"].ImportMap) + }) + + t.Run("uses deno.jsonc as import map when present", func(t *testing.T) { + config := NewConfig() + fsys := fs.MapFS{ + "supabase/config.toml": &fs.MapFile{Data: []byte(` + project_id = "test" + [functions.hello] + `)}, + "supabase/functions/hello/deno.jsonc": &fs.MapFile{}, + "supabase/functions/hello/index.ts": &fs.MapFile{}, + } + // Run test + assert.NoError(t, config.Load("", fsys)) + // Check that deno.json was set as import map + assert.Equal(t, "supabase/functions/hello/deno.jsonc", config.Functions["hello"].ImportMap) + }) + + t.Run("config.toml takes precedence over deno.json", func(t *testing.T) { + config := NewConfig() + fsys := fs.MapFS{ + "supabase/config.toml": &fs.MapFile{Data: []byte(` + project_id = "test" + [functions] + hello.import_map = "custom_import_map.json" + `)}, + "supabase/functions/hello/deno.json": &fs.MapFile{}, + "supabase/functions/hello/index.ts": &fs.MapFile{}, + } + // Run test + assert.NoError(t, config.Load("", fsys)) + // Check that config.toml takes precedence over deno.json + assert.Equal(t, "supabase/custom_import_map.json", config.Functions["hello"].ImportMap) + }) +} diff --git a/pkg/config/constants.go b/pkg/config/constants.go index 0b2edfcf3..c8aa224f4 100644 --- a/pkg/config/constants.go +++ b/pkg/config/constants.go @@ -10,12 +10,12 @@ const ( inbucketImage = "inbucket/inbucket:3.0.3" postgrestImage = "postgrest/postgrest:v12.2.0" pgmetaImage = "supabase/postgres-meta:v0.84.2" - studioImage = "supabase/studio:20241014-c083b3b" + studioImage = "supabase/studio:20241029-46e1e40" imageProxyImage = "darthsim/imgproxy:v3.8.0" - edgeRuntimeImage = "supabase/edge-runtime:v1.59.0" + edgeRuntimeImage = "supabase/edge-runtime:v1.60.1" vectorImage = "timberio/vector:0.28.1-alpine" supavisorImage = "supabase/supavisor:1.1.56" - gotrueImage = "supabase/gotrue:v2.158.1" + gotrueImage = "supabase/gotrue:v2.163.2" realtimeImage = "supabase/realtime:v2.30.34" storageImage = "supabase/storage-api:v1.11.13" logflareImage = "supabase/logflare:1.4.0" diff --git a/pkg/config/db.go b/pkg/config/db.go new file mode 100644 index 000000000..e7c5f820b --- /dev/null +++ b/pkg/config/db.go @@ -0,0 +1,173 @@ +package config + +import ( + "bytes" + + "github.com/google/go-cmp/cmp" + v1API "github.com/supabase/cli/pkg/api" + "github.com/supabase/cli/pkg/cast" + "github.com/supabase/cli/pkg/diff" +) + +type PoolMode string + +const ( + TransactionMode PoolMode = "transaction" + SessionMode PoolMode = "session" +) + +type SessionReplicationRole string + +const ( + SessionReplicationRoleOrigin SessionReplicationRole = "origin" + SessionReplicationRoleReplica SessionReplicationRole = "replica" + SessionReplicationRoleLocal SessionReplicationRole = "local" +) + +type ( + settings struct { + EffectiveCacheSize *string `toml:"effective_cache_size"` + LogicalDecodingWorkMem *string `toml:"logical_decoding_work_mem"` + MaintenanceWorkMem *string `toml:"maintenance_work_mem"` + MaxConnections *uint `toml:"max_connections"` + MaxLocksPerTransaction *uint `toml:"max_locks_per_transaction"` + MaxParallelMaintenanceWorkers *uint `toml:"max_parallel_maintenance_workers"` + MaxParallelWorkers *uint `toml:"max_parallel_workers"` + MaxParallelWorkersPerGather *uint `toml:"max_parallel_workers_per_gather"` + MaxReplicationSlots *uint `toml:"max_replication_slots"` + MaxSlotWalKeepSize *string `toml:"max_slot_wal_keep_size"` + MaxStandbyArchiveDelay *string `toml:"max_standby_archive_delay"` + MaxStandbyStreamingDelay *string `toml:"max_standby_streaming_delay"` + MaxWalSize *string `toml:"max_wal_size"` + MaxWalSenders *uint `toml:"max_wal_senders"` + MaxWorkerProcesses *uint `toml:"max_worker_processes"` + SessionReplicationRole *SessionReplicationRole `toml:"session_replication_role"` + SharedBuffers *string `toml:"shared_buffers"` + StatementTimeout *string `toml:"statement_timeout"` + WalKeepSize *string `toml:"wal_keep_size"` + WalSenderTimeout *string `toml:"wal_sender_timeout"` + WorkMem *string `toml:"work_mem"` + } + + db struct { + Image string `toml:"-"` + Port uint16 `toml:"port"` + ShadowPort uint16 `toml:"shadow_port"` + MajorVersion uint `toml:"major_version"` + Password string `toml:"-"` + RootKey string `toml:"-" mapstructure:"root_key"` + Pooler pooler `toml:"pooler"` + Seed seed `toml:"seed"` + Settings settings `toml:"settings"` + } + + seed struct { + Enabled bool `toml:"enabled"` + GlobPatterns []string `toml:"sql_paths"` + SqlPaths []string `toml:"-"` + } + + pooler struct { + Enabled bool `toml:"enabled"` + Image string `toml:"-"` + Port uint16 `toml:"port"` + PoolMode PoolMode `toml:"pool_mode"` + DefaultPoolSize uint `toml:"default_pool_size"` + MaxClientConn uint `toml:"max_client_conn"` + ConnectionString string `toml:"-"` + TenantId string `toml:"-"` + EncryptionKey string `toml:"-"` + SecretKeyBase string `toml:"-"` + } +) + +// Compare two db config, if changes requires restart return true, return false otherwise +func (a settings) requireDbRestart(b settings) bool { + return !cmp.Equal(a.MaxConnections, b.MaxConnections) || + !cmp.Equal(a.MaxWorkerProcesses, b.MaxWorkerProcesses) || + !cmp.Equal(a.MaxParallelWorkers, b.MaxParallelWorkers) || + !cmp.Equal(a.MaxWalSenders, b.MaxWalSenders) || + !cmp.Equal(a.MaxReplicationSlots, b.MaxReplicationSlots) || + !cmp.Equal(a.SharedBuffers, b.SharedBuffers) +} + +func (a *settings) ToUpdatePostgresConfigBody() v1API.UpdatePostgresConfigBody { + body := v1API.UpdatePostgresConfigBody{} + + // Parameters that require restart + body.MaxConnections = cast.UintToIntPtr(a.MaxConnections) + body.MaxWorkerProcesses = cast.UintToIntPtr(a.MaxWorkerProcesses) + body.MaxParallelWorkers = cast.UintToIntPtr(a.MaxParallelWorkers) + body.MaxWalSenders = cast.UintToIntPtr(a.MaxWalSenders) + body.MaxReplicationSlots = cast.UintToIntPtr(a.MaxReplicationSlots) + body.SharedBuffers = a.SharedBuffers + + // Parameters that can be changed without restart + body.EffectiveCacheSize = a.EffectiveCacheSize + body.LogicalDecodingWorkMem = a.LogicalDecodingWorkMem + body.MaintenanceWorkMem = a.MaintenanceWorkMem + body.MaxLocksPerTransaction = cast.UintToIntPtr(a.MaxLocksPerTransaction) + body.MaxParallelMaintenanceWorkers = cast.UintToIntPtr(a.MaxParallelMaintenanceWorkers) + body.MaxParallelWorkersPerGather = cast.UintToIntPtr(a.MaxParallelWorkersPerGather) + body.MaxSlotWalKeepSize = a.MaxSlotWalKeepSize + body.MaxStandbyArchiveDelay = a.MaxStandbyArchiveDelay + body.MaxStandbyStreamingDelay = a.MaxStandbyStreamingDelay + body.MaxWalSize = a.MaxWalSize + body.SessionReplicationRole = (*v1API.UpdatePostgresConfigBodySessionReplicationRole)(a.SessionReplicationRole) + body.StatementTimeout = a.StatementTimeout + body.WalKeepSize = a.WalKeepSize + body.WalSenderTimeout = a.WalSenderTimeout + body.WorkMem = a.WorkMem + return body +} + +func (a *settings) fromRemoteConfig(remoteConfig v1API.PostgresConfigResponse) settings { + result := *a + + result.EffectiveCacheSize = remoteConfig.EffectiveCacheSize + result.LogicalDecodingWorkMem = remoteConfig.LogicalDecodingWorkMem + result.MaintenanceWorkMem = remoteConfig.MaintenanceWorkMem + result.MaxConnections = cast.IntToUintPtr(remoteConfig.MaxConnections) + result.MaxLocksPerTransaction = cast.IntToUintPtr(remoteConfig.MaxLocksPerTransaction) + result.MaxParallelMaintenanceWorkers = cast.IntToUintPtr(remoteConfig.MaxParallelMaintenanceWorkers) + result.MaxParallelWorkers = cast.IntToUintPtr(remoteConfig.MaxParallelWorkers) + result.MaxParallelWorkersPerGather = cast.IntToUintPtr(remoteConfig.MaxParallelWorkersPerGather) + result.MaxReplicationSlots = cast.IntToUintPtr(remoteConfig.MaxReplicationSlots) + result.MaxSlotWalKeepSize = remoteConfig.MaxSlotWalKeepSize + result.MaxStandbyArchiveDelay = remoteConfig.MaxStandbyArchiveDelay + result.MaxStandbyStreamingDelay = remoteConfig.MaxStandbyStreamingDelay + result.MaxWalSenders = cast.IntToUintPtr(remoteConfig.MaxWalSenders) + result.MaxWalSize = remoteConfig.MaxWalSize + result.MaxWorkerProcesses = cast.IntToUintPtr(remoteConfig.MaxWorkerProcesses) + result.SessionReplicationRole = (*SessionReplicationRole)(remoteConfig.SessionReplicationRole) + result.SharedBuffers = remoteConfig.SharedBuffers + result.StatementTimeout = remoteConfig.StatementTimeout + result.WalKeepSize = remoteConfig.WalKeepSize + result.WalSenderTimeout = remoteConfig.WalSenderTimeout + result.WorkMem = remoteConfig.WorkMem + return result +} + +const pgConfHeader = "\n# supabase [db.settings] configuration\n" + +// create a valid string to append to /etc/postgresql/postgresql.conf +func (a *settings) ToPostgresConfig() string { + // Assuming postgres settings is always a flat struct, we can serialise + // using toml, then replace double quotes with single. + data, _ := ToTomlBytes(*a) + body := bytes.ReplaceAll(data, []byte{'"'}, []byte{'\''}) + return pgConfHeader + string(body) +} + +func (a *settings) DiffWithRemote(remoteConfig v1API.PostgresConfigResponse) ([]byte, error) { + // Convert the config values into easily comparable remoteConfig values + currentValue, err := ToTomlBytes(a) + if err != nil { + return nil, err + } + remoteCompare, err := ToTomlBytes(a.fromRemoteConfig(remoteConfig)) + if err != nil { + return nil, err + } + return diff.Diff("remote[db.settings]", remoteCompare, "local[db.settings]", currentValue), nil +} diff --git a/pkg/config/db_test.go b/pkg/config/db_test.go new file mode 100644 index 000000000..8d70ec21b --- /dev/null +++ b/pkg/config/db_test.go @@ -0,0 +1,193 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" + v1API "github.com/supabase/cli/pkg/api" + "github.com/supabase/cli/pkg/cast" +) + +func TestDbSettingsToUpdatePostgresConfigBody(t *testing.T) { + t.Run("converts all fields correctly", func(t *testing.T) { + db := &db{ + Settings: settings{ + EffectiveCacheSize: cast.Ptr("4GB"), + MaxConnections: cast.Ptr(uint(100)), + SharedBuffers: cast.Ptr("1GB"), + StatementTimeout: cast.Ptr("30s"), + SessionReplicationRole: cast.Ptr(SessionReplicationRoleReplica), + }, + } + + body := db.Settings.ToUpdatePostgresConfigBody() + + assert.Equal(t, "4GB", *body.EffectiveCacheSize) + assert.Equal(t, 100, *body.MaxConnections) + assert.Equal(t, "1GB", *body.SharedBuffers) + assert.Equal(t, "30s", *body.StatementTimeout) + assert.Equal(t, v1API.UpdatePostgresConfigBodySessionReplicationRoleReplica, *body.SessionReplicationRole) + }) + + t.Run("handles empty fields", func(t *testing.T) { + db := &db{} + + body := db.Settings.ToUpdatePostgresConfigBody() + + assert.Nil(t, body.EffectiveCacheSize) + assert.Nil(t, body.MaxConnections) + assert.Nil(t, body.SharedBuffers) + assert.Nil(t, body.StatementTimeout) + assert.Nil(t, body.SessionReplicationRole) + }) +} + +func TestDbSettingsDiffWithRemote(t *testing.T) { + t.Run("detects differences", func(t *testing.T) { + db := &db{ + Settings: settings{ + EffectiveCacheSize: cast.Ptr("4GB"), + MaxConnections: cast.Ptr(uint(100)), + SharedBuffers: cast.Ptr("1GB"), + }, + } + + remoteConfig := v1API.PostgresConfigResponse{ + EffectiveCacheSize: cast.Ptr("8GB"), + MaxConnections: cast.Ptr(200), + SharedBuffers: cast.Ptr("2GB"), + } + + diff, err := db.Settings.DiffWithRemote(remoteConfig) + assert.NoError(t, err) + + assert.Contains(t, string(diff), "-effective_cache_size = \"8GB\"") + assert.Contains(t, string(diff), "+effective_cache_size = \"4GB\"") + assert.Contains(t, string(diff), "-max_connections = 200") + assert.Contains(t, string(diff), "+max_connections = 100") + assert.Contains(t, string(diff), "-shared_buffers = \"2GB\"") + assert.Contains(t, string(diff), "+shared_buffers = \"1GB\"") + }) + + t.Run("handles no differences", func(t *testing.T) { + db := &db{ + Settings: settings{ + EffectiveCacheSize: cast.Ptr("4GB"), + MaxConnections: cast.Ptr(uint(100)), + SharedBuffers: cast.Ptr("1GB"), + }, + } + + remoteConfig := v1API.PostgresConfigResponse{ + EffectiveCacheSize: cast.Ptr("4GB"), + MaxConnections: cast.Ptr(100), + SharedBuffers: cast.Ptr("1GB"), + } + + diff, err := db.Settings.DiffWithRemote(remoteConfig) + assert.NoError(t, err) + + assert.Empty(t, diff) + }) + + t.Run("handles multiple schemas and search paths with spaces", func(t *testing.T) { + db := &db{ + Settings: settings{ + EffectiveCacheSize: cast.Ptr("4GB"), + MaxConnections: cast.Ptr(uint(100)), + SharedBuffers: cast.Ptr("1GB"), + }, + } + + remoteConfig := v1API.PostgresConfigResponse{ + EffectiveCacheSize: cast.Ptr("4GB"), + MaxConnections: cast.Ptr(100), + SharedBuffers: cast.Ptr("1GB"), + } + + diff, err := db.Settings.DiffWithRemote(remoteConfig) + assert.NoError(t, err) + + assert.Empty(t, diff) + }) + + t.Run("handles api disabled on remote side", func(t *testing.T) { + db := &db{ + Settings: settings{ + EffectiveCacheSize: cast.Ptr("4GB"), + MaxConnections: cast.Ptr(uint(100)), + SharedBuffers: cast.Ptr("1GB"), + }, + } + + remoteConfig := v1API.PostgresConfigResponse{ + // All fields are nil to simulate disabled API + } + + diff, err := db.Settings.DiffWithRemote(remoteConfig) + assert.NoError(t, err) + + assert.Contains(t, string(diff), "+effective_cache_size = \"4GB\"") + assert.Contains(t, string(diff), "+max_connections = 100") + assert.Contains(t, string(diff), "+shared_buffers = \"1GB\"") + }) + + t.Run("handles api disabled on local side", func(t *testing.T) { + db := &db{ + Settings: settings{ + // All fields are nil to simulate disabled API + }, + } + + remoteConfig := v1API.PostgresConfigResponse{ + EffectiveCacheSize: cast.Ptr("4GB"), + MaxConnections: cast.Ptr(100), + SharedBuffers: cast.Ptr("1GB"), + } + + diff, err := db.Settings.DiffWithRemote(remoteConfig) + assert.NoError(t, err) + + assert.Contains(t, string(diff), "-effective_cache_size = \"4GB\"") + assert.Contains(t, string(diff), "-max_connections = 100") + assert.Contains(t, string(diff), "-shared_buffers = \"1GB\"") + }) +} + +func TestSettingsToPostgresConfig(t *testing.T) { + t.Run("Only set values should appear", func(t *testing.T) { + settings := settings{ + MaxConnections: cast.Ptr(uint(100)), + MaxLocksPerTransaction: cast.Ptr(uint(64)), + SharedBuffers: cast.Ptr("128MB"), + WorkMem: cast.Ptr("4MB"), + } + got := settings.ToPostgresConfig() + + assert.Contains(t, got, "max_connections = 100") + assert.Contains(t, got, "max_locks_per_transaction = 64") + assert.Contains(t, got, "shared_buffers = '128MB'") + assert.Contains(t, got, "work_mem = '4MB'") + + assert.NotContains(t, got, "effective_cache_size") + assert.NotContains(t, got, "maintenance_work_mem") + assert.NotContains(t, got, "max_parallel_workers") + }) + + t.Run("SessionReplicationRole should be handled correctly", func(t *testing.T) { + settings := settings{ + SessionReplicationRole: cast.Ptr(SessionReplicationRoleOrigin), + } + got := settings.ToPostgresConfig() + + assert.Contains(t, got, "session_replication_role = 'origin'") + }) + + t.Run("Empty settings should result in empty string", func(t *testing.T) { + settings := settings{} + got := settings.ToPostgresConfig() + + assert.Equal(t, got, "\n# supabase [db.settings] configuration\n") + assert.NotContains(t, got, "=") + }) +} diff --git a/pkg/config/storage.go b/pkg/config/storage.go new file mode 100644 index 000000000..d2799ce04 --- /dev/null +++ b/pkg/config/storage.go @@ -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 +} diff --git a/pkg/config/templates/config.toml b/pkg/config/templates/config.toml index 112237748..e47487c21 100644 --- a/pkg/config/templates/config.toml +++ b/pkg/config/templates/config.toml @@ -121,6 +121,10 @@ enable_confirmations = false secure_password_change = false # Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email. max_frequency = "1s" +# Number of characters used in the email OTP. +otp_length = 6 +# Number of seconds before the email OTP expires (defaults to 1 hour). +otp_expiry = 3600 # Use a production-ready SMTP server # [auth.email.smtp] @@ -187,6 +191,11 @@ verify_enabled = true # template = "Your code is {{ `{{ .Code }}` }} ." # max_frequency = "10s" +# Configure Multi-factor-authentication via WebAuthn +# [auth.mfa.web_authn] +# enroll_enabled = true +# verify_enabled = true + # Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, # `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`, # `twitter`, `slack`, `spotify`, `workos`, `zoom`. diff --git a/pkg/config/testdata/config.toml b/pkg/config/testdata/config.toml index 0d591d979..b8314644d 100644 --- a/pkg/config/testdata/config.toml +++ b/pkg/config/testdata/config.toml @@ -117,6 +117,10 @@ double_confirm_changes = true enable_confirmations = false # Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email. max_frequency = "1s" +# Number of characters used in the email OTP. +otp_length = 6 +# Number of seconds before the email OTP expires. +otp_expiry = 300 # Use a production-ready SMTP server [auth.email.smtp] @@ -187,6 +191,11 @@ otp_length = 6 template = "Your code is {{ `{{ .Code }}` }} ." max_frequency = "10s" +# Configure Multi-factor-authentication via Phone Messaging +[auth.mfa.web_authn] +enroll_enabled = true +verify_enabled = true + # Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, # `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`, # `twitter`, `slack`, `spotify`, `workos`, `zoom`. @@ -237,5 +246,8 @@ client_id = "nope" [remotes.staging] project_id = "staging-project" +[remotes.staging.db.seed] +enabled = true + [remotes.staging.storage.buckets.images] allowed_mime_types = ["image/png"] diff --git a/pkg/config/updater.go b/pkg/config/updater.go index 467b7bb63..04924124a 100644 --- a/pkg/config/updater.go +++ b/pkg/config/updater.go @@ -21,7 +21,15 @@ func (u *ConfigUpdater) UpdateRemoteConfig(ctx context.Context, remote baseConfi if err := u.UpdateApiConfig(ctx, remote.ProjectId, remote.Api); err != nil { return err } - // TODO: implement other service configs, ie. auth + 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 + } return nil } @@ -40,6 +48,7 @@ func (u *ConfigUpdater) UpdateApiConfig(ctx context.Context, projectRef string, return nil } fmt.Fprintln(os.Stderr, "Updating API service with config:", string(apiDiff)) + if resp, err := u.client.V1UpdatePostgrestServiceConfigWithResponse(ctx, projectRef, c.ToUpdatePostgrestConfigBody()); err != nil { return errors.Errorf("failed to update API config: %w", err) } else if resp.JSON200 == nil { @@ -47,3 +56,81 @@ func (u *ConfigUpdater) UpdateApiConfig(ctx context.Context, projectRef string, } return nil } + +func (u *ConfigUpdater) UpdateDbSettingsConfig(ctx context.Context, projectRef string, s settings) error { + dbConfig, err := u.client.V1GetPostgresConfigWithResponse(ctx, projectRef) + if err != nil { + return errors.Errorf("failed to read DB config: %w", err) + } else if dbConfig.JSON200 == nil { + return errors.Errorf("unexpected status %d: %s", dbConfig.StatusCode(), string(dbConfig.Body)) + } + dbDiff, err := s.DiffWithRemote(*dbConfig.JSON200) + if err != nil { + return err + } else if len(dbDiff) == 0 { + fmt.Fprintln(os.Stderr, "Remote DB config is up to date.") + return nil + } + fmt.Fprintln(os.Stderr, "Updating DB service with config:", string(dbDiff)) + + remoteConfig := s.fromRemoteConfig(*dbConfig.JSON200) + restartRequired := s.requireDbRestart(remoteConfig) + if restartRequired { + fmt.Fprintln(os.Stderr, "Database will be restarted to apply config updates...") + } + updateBody := s.ToUpdatePostgresConfigBody() + updateBody.RestartDatabase = &restartRequired + if resp, err := u.client.V1UpdatePostgresConfigWithResponse(ctx, projectRef, updateBody); err != nil { + return errors.Errorf("failed to update DB config: %w", err) + } else if resp.JSON200 == nil { + return errors.Errorf("unexpected status %d: %s", resp.StatusCode(), string(resp.Body)) + } + return nil +} + +func (u *ConfigUpdater) UpdateDbConfig(ctx context.Context, projectRef string, c db) error { + if err := u.UpdateDbSettingsConfig(ctx, projectRef, c.Settings); err != nil { + return err + } + 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 project:", projectRef) + + if resp, err := u.client.V1EnableDatabaseWebhookWithResponse(ctx, projectRef); err != nil { + return errors.Errorf("failed to enable webhooks: %w", err) + } else if status := resp.StatusCode(); status < 200 || status >= 300 { + return errors.Errorf("unexpected enable webhook status %d: %s", status, string(resp.Body)) + } + } + return nil +} diff --git a/pkg/config/updater_test.go b/pkg/config/updater_test.go index 241d612e9..e0507dbde 100644 --- a/pkg/config/updater_test.go +++ b/pkg/config/updater_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" v1API "github.com/supabase/cli/pkg/api" + "github.com/supabase/cli/pkg/cast" ) func TestUpdateApi(t *testing.T) { @@ -63,3 +64,216 @@ func TestUpdateApi(t *testing.T) { assert.True(t, gock.IsDone()) }) } + +func TestUpdateDbConfig(t *testing.T) { + server := "http://localhost" + client, err := v1API.NewClientWithResponses(server) + require.NoError(t, err) + + t.Run("updates remote DB config", func(t *testing.T) { + updater := NewConfigUpdater(*client) + // Setup mock server + defer gock.Off() + gock.New(server). + Get("/v1/projects/test-project/config/database"). + Reply(http.StatusOK). + JSON(v1API.PostgresConfigResponse{}) + gock.New(server). + Put("/v1/projects/test-project/config/database"). + Reply(http.StatusOK). + JSON(v1API.PostgresConfigResponse{ + MaxConnections: cast.Ptr(cast.UintToInt(100)), + }) + // Run test + err := updater.UpdateDbConfig(context.Background(), "test-project", db{ + Settings: settings{ + MaxConnections: cast.Ptr(cast.IntToUint(100)), + }, + }) + // Check result + assert.NoError(t, err) + assert.True(t, gock.IsDone()) + }) + + t.Run("skips update if no diff in DB config", func(t *testing.T) { + updater := NewConfigUpdater(*client) + // Setup mock server + defer gock.Off() + gock.New(server). + Get("/v1/projects/test-project/config/database"). + Reply(http.StatusOK). + JSON(v1API.PostgresConfigResponse{ + MaxConnections: cast.Ptr(cast.UintToInt(100)), + }) + // Run test + err := updater.UpdateDbConfig(context.Background(), "test-project", db{ + Settings: settings{ + MaxConnections: cast.Ptr(cast.IntToUint(100)), + }, + }) + // Check result + assert.NoError(t, err) + assert.True(t, gock.IsDone()) + }) +} + +func TestUpdateExperimentalConfig(t *testing.T) { + server := "http://localhost" + client, err := v1API.NewClientWithResponses(server) + require.NoError(t, err) + + t.Run("enables webhooks", func(t *testing.T) { + updater := NewConfigUpdater(*client) + // Setup mock server + defer gock.Off() + gock.New(server). + Post("/v1/projects/test-project/database/webhooks/enable"). + Reply(http.StatusOK). + JSON(map[string]interface{}{}) + // Run test + err := updater.UpdateExperimentalConfig(context.Background(), "test-project", experimental{ + Webhooks: &webhooks{ + Enabled: true, + }, + }) + // Check result + assert.NoError(t, err) + assert.True(t, gock.IsDone()) + }) + + t.Run("skips update if webhooks not enabled", func(t *testing.T) { + updater := NewConfigUpdater(*client) + // Run test + err := updater.UpdateExperimentalConfig(context.Background(), "test-project", experimental{ + Webhooks: &webhooks{ + Enabled: false, + }, + }) + // Check result + assert.NoError(t, err) + assert.True(t, gock.IsDone()) + }) +} + +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) + require.NoError(t, err) + + t.Run("updates all configs", func(t *testing.T) { + updater := NewConfigUpdater(*client) + // Setup mock server + defer gock.Off() + // API config + gock.New(server). + Get("/v1/projects/test-project/postgrest"). + Reply(http.StatusOK). + JSON(v1API.PostgrestConfigWithJWTSecretResponse{}) + gock.New(server). + Patch("/v1/projects/test-project/postgrest"). + Reply(http.StatusOK). + JSON(v1API.PostgrestConfigWithJWTSecretResponse{ + DbSchema: "public", + MaxRows: 1000, + }) + // DB config + gock.New(server). + Get("/v1/projects/test-project/config/database"). + Reply(http.StatusOK). + JSON(v1API.PostgresConfigResponse{}) + gock.New(server). + Put("/v1/projects/test-project/config/database"). + Reply(http.StatusOK). + 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"). + Reply(http.StatusOK). + JSON(map[string]interface{}{}) + // Run test + err := updater.UpdateRemoteConfig(context.Background(), baseConfig{ + ProjectId: "test-project", + Api: api{ + Enabled: true, + Schemas: []string{"public", "private"}, + MaxRows: 1000, + }, + Db: db{ + Settings: settings{ + MaxConnections: cast.Ptr(cast.IntToUint(100)), + }, + }, + Storage: storage{ + Enabled: true, + FileSizeLimit: 100, + ImageTransformation: imageTransformation{ + Enabled: true, + }, + }, + Experimental: experimental{ + Webhooks: &webhooks{ + Enabled: true, + }, + }, + }) + // Check result + assert.NoError(t, err) + assert.True(t, gock.IsDone()) + }) +} diff --git a/pkg/function/batch.go b/pkg/function/batch.go index cf5a2f161..1d52bace4 100644 --- a/pkg/function/batch.go +++ b/pkg/function/batch.go @@ -61,7 +61,7 @@ func (s *EdgeRuntimeAPI) UpsertFunctions(ctx context.Context, functionConfig con return errors.Errorf("unexpected status %d: %s", resp.StatusCode(), string(resp.Body)) } } else { - if resp, err := s.client.CreateFunctionWithBodyWithResponse(ctx, s.project, &api.CreateFunctionParams{ + if resp, err := s.client.V1CreateAFunctionWithBodyWithResponse(ctx, s.project, &api.V1CreateAFunctionParams{ Slug: &slug, Name: &slug, VerifyJwt: function.VerifyJWT, diff --git a/scripts/postinstall.js b/scripts/postinstall.js index cc28b12fe..76b759046 100755 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -98,45 +98,53 @@ async function main() { throw errUnsupported; } + // Read from package.json and prepare for the installation. const pkg = await readPackageJson(); if (platform === "windows") { // Update bin path in package.json pkg.bin[pkg.name] += ".exe"; } + // Prepare the installation path by creating the directory if it doesn't exist. const binPath = pkg.bin[pkg.name]; const binDir = path.dirname(binPath); await fs.promises.mkdir(binDir, { recursive: true }); - // First we will Un-GZip, then we will untar. - const ungz = zlib.createGunzip(); - const binName = path.basename(binPath); - const untar = extract({ cwd: binDir }, [binName]); - - const url = getDownloadUrl(pkg); - console.info("Downloading", url); + // Create the agent that will be used for all the fetch requests later. const proxyUrl = process.env.npm_config_https_proxy || process.env.npm_config_http_proxy || process.env.npm_config_proxy; - // Keeps the TCP connection alive when sending multiple requests // Ref: https://github.com/node-fetch/node-fetch/issues/1735 const agent = proxyUrl ? new HttpsProxyAgent(proxyUrl, { keepAlive: true }) : new Agent({ keepAlive: true }); - const resp = await fetch(url, { agent }); + // First, fetch the checksum map. + const checksumMap = await fetchAndParseCheckSumFile(pkg, agent); + + // Then, download the binary. + const url = getDownloadUrl(pkg); + console.info("Downloading", url); + const resp = await fetch(url, { agent }); const hash = createHash("sha256"); const pkgNameWithPlatform = `${pkg.name}_${platform}_${arch}.tar.gz`; - const checksumMap = await fetchAndParseCheckSumFile(pkg, agent); + // Then, decompress the binary -- we will first Un-GZip, then we will untar. + const ungz = zlib.createGunzip(); + const binName = path.basename(binPath); + const untar = extract({ cwd: binDir }, [binName]); + + // Update the hash with the binary data as it's being downloaded. resp.body .on("data", (chunk) => { hash.update(chunk); }) + // Pipe the data to the ungz stream. .pipe(ungz); + // After the ungz stream has ended, verify the checksum. ungz .on("end", () => { const expectedChecksum = checksumMap?.[pkgNameWithPlatform]; @@ -151,8 +159,10 @@ async function main() { } console.info("Checksum verified."); }) + // Pipe the data to the untar stream. .pipe(untar); + // Wait for the untar stream to finish. await new Promise((resolve, reject) => { untar.on("error", reject); untar.on("end", () => resolve());