diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38837411e..7ad1718cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: # Linter requires no cache cache: false - - uses: golangci/golangci-lint-action@v5 + - uses: golangci/golangci-lint-action@v6 with: args: --timeout 3m --verbose diff --git a/README.md b/README.md index bedc20cff..63ca688be 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,12 @@ To install the beta release channel: npm i supabase@beta --save-dev ``` +When installing with yarn 4, you need to disable experimental fetch with the following nodejs config. + +``` +NODE_OPTIONS=--no-experimental-fetch yarn add supabase +``` + > **Note** For Bun versions below v1.0.17, you must add `supabase` as a [trusted dependency](https://bun.sh/guides/install/trusted) before running `bun add -D supabase`. diff --git a/api/beta.yaml b/api/beta.yaml index 521b8f687..b86a320b6 100644 --- a/api/beta.yaml +++ b/api/beta.yaml @@ -69,12 +69,41 @@ paths: responses: '200': description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/BranchDeleteResponse' '500': description: Failed to delete database branch tags: - database branches (beta) security: - bearer: [] + /v1/branches/{branch_id}/reset: + post: + operationId: resetBranch + summary: Resets a database branch + description: Resets the specified database branch + parameters: + - name: branch_id + required: true + in: path + description: Branch ID + schema: + type: string + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/BranchResetResponse' + '500': + description: Failed to reset database branch + tags: + - database branches (beta) + security: + - bearer: [] /v1/projects: get: operationId: getProjects @@ -89,7 +118,7 @@ paths: schema: type: array items: - $ref: '#/components/schemas/ProjectResponse' + $ref: '#/components/schemas/V1ProjectResponse' tags: - projects security: @@ -103,14 +132,14 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CreateProjectBody' + $ref: '#/components/schemas/V1CreateProjectBody' responses: '201': description: '' content: application/json: schema: - $ref: '#/components/schemas/ProjectResponse' + $ref: '#/components/schemas/V1ProjectResponse' tags: - projects security: @@ -217,6 +246,9 @@ paths: description: '' tags: - oauth (beta) + security: + - oauth2: + - read /v1/oauth/token: post: operationId: token @@ -237,6 +269,9 @@ paths: $ref: '#/components/schemas/OAuthTokenResponse' tags: - oauth (beta) + security: + - oauth2: + - write /v1/snippets: get: operationId: listSnippets @@ -772,7 +807,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PostgrestConfigResponse' + $ref: '#/components/schemas/V1PostgrestConfigResponse' '403': description: '' '500': @@ -800,7 +835,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ProjectRefResponse' + $ref: '#/components/schemas/V1ProjectRefResponse' '403': description: '' tags: @@ -1357,7 +1392,7 @@ paths: schema: type: array items: - $ref: '#/components/schemas/ServiceHealthResponse' + $ref: '#/components/schemas/V1ServiceHealthResponse' '500': description: Failed to retrieve project's service health status tags: @@ -1506,6 +1541,126 @@ paths: - projects config security: - bearer: [] + /v1/projects/{ref}/config/auth/third-party-auth: + post: + operationId: createTPAForProject + summary: Creates a new third-party auth integration + 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/CreateThirdPartyAuthBody' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ThirdPartyAuth' + '403': + description: '' + tags: + - third-party-auth (alpha) + security: + - bearer: [] + get: + operationId: listTPAForProject + summary: Lists all third-party auth integrations + parameters: + - name: ref + required: true + in: path + description: Project ref + schema: + minLength: 20 + maxLength: 20 + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ThirdPartyAuth' + '403': + description: '' + tags: + - third-party-auth (alpha) + security: + - bearer: [] + /v1/projects/{ref}/config/auth/third-party-auth/{tpa_id}: + delete: + operationId: deleteTPAForProject + summary: Removes a third-party auth integration + parameters: + - name: ref + required: true + in: path + description: Project ref + schema: + minLength: 20 + maxLength: 20 + type: string + - name: tpa_id + required: true + in: path + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ThirdPartyAuth' + '403': + description: '' + tags: + - third-party-auth (alpha) + security: + - bearer: [] + get: + operationId: getTPAForProject + summary: Get a third-party integration + parameters: + - name: ref + required: true + in: path + description: Project ref + schema: + minLength: 20 + maxLength: 20 + type: string + - name: tpa_id + required: true + in: path + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ThirdPartyAuth' + '403': + description: '' + tags: + - third-party-auth (alpha) + security: + - bearer: [] /v1/projects/{ref}/database/query: post: operationId: v1RunQuery @@ -1524,7 +1679,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RunQueryBody' + $ref: '#/components/schemas/V1RunQueryBody' responses: '201': description: '' @@ -1614,10 +1769,10 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CreateFunctionBody' + $ref: '#/components/schemas/V1CreateFunctionBody' application/vnd.denoland.eszip: schema: - $ref: '#/components/schemas/CreateFunctionBody' + $ref: '#/components/schemas/V1CreateFunctionBody' responses: '201': description: '' @@ -1699,6 +1854,37 @@ paths: - functions security: - bearer: [] + delete: + operationId: deleteFunction + summary: Delete a function + description: Deletes a function with the specified slug from the specified project. + parameters: + - name: ref + required: true + in: path + description: Project ref + schema: + minLength: 20 + maxLength: 20 + type: string + - name: function_slug + required: true + in: path + description: Function slug + schema: + pattern: /^[A-Za-z0-9_-]+$/ + type: string + responses: + '200': + description: '' + '403': + description: '' + '500': + description: Failed to delete function with given slug + tags: + - functions + security: + - bearer: [] patch: operationId: updateFunction summary: Update a function @@ -1755,10 +1941,10 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/UpdateFunctionBody' + $ref: '#/components/schemas/V1UpdateFunctionBody' application/vnd.denoland.eszip: schema: - $ref: '#/components/schemas/UpdateFunctionBody' + $ref: '#/components/schemas/V1UpdateFunctionBody' responses: '200': description: '' @@ -1774,37 +1960,6 @@ paths: - functions security: - bearer: [] - delete: - operationId: deleteFunction - summary: Delete a function - description: Deletes a function with the specified slug from the specified project. - parameters: - - name: ref - required: true - in: path - description: Project ref - schema: - minLength: 20 - maxLength: 20 - type: string - - name: function_slug - required: true - in: path - description: Function slug - schema: - pattern: /^[A-Za-z0-9_-]+$/ - type: string - responses: - '200': - description: '' - '403': - description: '' - '500': - description: Failed to delete function with given slug - tags: - - functions - security: - - bearer: [] /v1/projects/{ref}/functions/{function_slug}/body: get: operationId: getFunctionBody @@ -2197,6 +2352,8 @@ components: type: string reset_on_push: type: boolean + persistent: + type: boolean BranchResponse: type: object properties: @@ -2216,6 +2373,8 @@ components: type: number reset_on_push: type: boolean + persistent: + type: boolean status: enum: - CREATING_PROJECT @@ -2234,10 +2393,25 @@ components: - parent_project_ref - is_default - reset_on_push + - persistent - status - created_at - updated_at - DatabaseResponse: + BranchDeleteResponse: + type: object + properties: + message: + type: string + required: + - message + BranchResetResponse: + type: object + properties: + message: + type: string + required: + - message + V1DatabaseResponse: type: object properties: host: @@ -2249,7 +2423,7 @@ components: required: - host - version - ProjectResponse: + V1ProjectResponse: type: object properties: id: @@ -2270,14 +2444,42 @@ components: description: Creation timestamp example: '2023-03-29T16:32:59Z' database: - $ref: '#/components/schemas/DatabaseResponse' + $ref: '#/components/schemas/V1DatabaseResponse' + status: + enum: + - ACTIVE_HEALTHY + - ACTIVE_UNHEALTHY + - COMING_UP + - GOING_DOWN + - INACTIVE + - INIT_FAILED + - REMOVED + - RESTORING + - UNKNOWN + - UPGRADING + - PAUSING + type: string required: - id - organization_id - name - region - created_at - CreateProjectBody: + - status + DesiredInstanceSize: + type: string + enum: + - micro + - small + - medium + - large + - xlarge + - 2xlarge + - 4xlarge + - 8xlarge + - 12xlarge + - 16xlarge + V1CreateProjectBody: type: object properties: db_pass: @@ -2322,11 +2524,13 @@ components: kps_enabled: type: boolean deprecated: true + desired_instance_size: + $ref: '#/components/schemas/DesiredInstanceSize' template_url: - template_url: type: string description: Template URL used to create the project from the CLI. - example: https://github.com/supabase/supabase/tree/master/examples/slack-clone/nextjs-slack-clone + example: >- + https://github.com/supabase/supabase/tree/master/examples/slack-clone/nextjs-slack-clone required: - db_pass - name @@ -2636,6 +2840,12 @@ components: properties: max_rows: type: integer + db_pool: + type: integer + nullable: true + description: >- + If `null`, the value is automatically configured based on compute + size. db_schema: type: string db_extra_search_path: @@ -2644,6 +2854,7 @@ components: type: string required: - max_rows + - db_pool - db_schema - db_extra_search_path UpdatePostgrestConfigBody: @@ -2653,24 +2864,35 @@ components: type: integer minimum: 0 maximum: 1000000 + db_pool: + type: integer + minimum: 0 + maximum: 1000 db_extra_search_path: type: string db_schema: type: string - PostgrestConfigResponse: + V1PostgrestConfigResponse: type: object properties: max_rows: type: integer + db_pool: + type: integer + nullable: true + description: >- + If `null`, the value is automatically configured based on compute + size. db_schema: type: string db_extra_search_path: type: string required: - max_rows + - db_pool - db_schema - db_extra_search_path - ProjectRefResponse: + V1ProjectRefResponse: type: object properties: id: @@ -2954,7 +3176,7 @@ components: - healthy - db_connected - connected_cluster - ServiceHealthResponse: + V1ServiceHealthResponse: type: object properties: info: @@ -3013,6 +3235,10 @@ components: type: integer minimum: 0 maximum: 1024 + max_standby_archive_delay: + type: string + max_standby_streaming_delay: + type: string max_worker_processes: type: integer minimum: 0 @@ -3056,6 +3282,10 @@ components: type: integer minimum: 0 maximum: 1024 + max_standby_archive_delay: + type: string + max_standby_streaming_delay: + type: string max_worker_processes: type: integer minimum: 0 @@ -3093,6 +3323,9 @@ components: disable_signup: type: boolean nullable: true + external_anonymous_users_enabled: + type: boolean + nullable: true external_apple_additional_client_ids: type: string nullable: true @@ -3339,6 +3572,9 @@ components: mailer_subjects_recovery: type: string nullable: true + mailer_subjects_reauthentication: + type: string + nullable: true mailer_templates_confirmation_content: type: string nullable: true @@ -3354,6 +3590,9 @@ components: mailer_templates_recovery_content: type: string nullable: true + mailer_templates_reauthentication_content: + type: string + nullable: true mfa_max_enrolled_factors: type: number nullable: true @@ -3366,6 +3605,9 @@ components: password_required_characters: type: string nullable: true + rate_limit_anonymous_users: + type: number + nullable: true rate_limit_email_sent: type: number nullable: true @@ -3509,6 +3751,7 @@ components: nullable: true required: - disable_signup + - external_anonymous_users_enabled - external_apple_additional_client_ids - external_apple_client_id - external_apple_enabled @@ -3591,15 +3834,18 @@ components: - mailer_subjects_invite - mailer_subjects_magic_link - mailer_subjects_recovery + - mailer_subjects_reauthentication - mailer_templates_confirmation_content - mailer_templates_email_change_content - mailer_templates_invite_content - mailer_templates_magic_link_content - mailer_templates_recovery_content + - mailer_templates_reauthentication_content - mfa_max_enrolled_factors - 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 @@ -3689,6 +3935,8 @@ components: type: string mailer_subjects_magic_link: type: string + mailer_subjects_reauthentication: + type: string mailer_templates_invite_content: type: string mailer_templates_confirmation_content: @@ -3699,12 +3947,16 @@ components: type: string mailer_templates_magic_link_content: 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: + type: boolean external_email_enabled: type: boolean external_phone_enabled: @@ -3728,6 +3980,10 @@ components: 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 @@ -3836,6 +4092,14 @@ components: type: boolean hook_custom_access_token_uri: type: string + hook_send_sms_enabled: + type: boolean + hook_send_sms_uri: + type: string + hook_send_email_enabled: + type: boolean + hook_send_email_uri: + type: string external_apple_enabled: type: boolean external_apple_client_id: @@ -3964,14 +4228,54 @@ components: type: string external_zoom_secret: type: string - RunQueryBody: + CreateThirdPartyAuthBody: + type: object + properties: + oidc_issuer_url: + type: string + jwks_url: + type: string + custom_jwks: + type: object + ThirdPartyAuth: + type: object + properties: + id: + type: string + type: + type: string + oidc_issuer_url: + type: string + nullable: true + jwks_url: + type: string + nullable: true + custom_jwks: + type: object + nullable: true + resolved_jwks: + type: object + nullable: true + inserted_at: + type: string + updated_at: + type: string + resolved_at: + type: string + nullable: true + required: + - id + - type + - inserted_at + - updated_at + V1RunQueryBody: type: object properties: query: type: string required: - query - CreateFunctionBody: + V1CreateFunctionBody: type: object properties: slug: @@ -4061,7 +4365,7 @@ components: - version - created_at - updated_at - UpdateFunctionBody: + V1UpdateFunctionBody: type: object properties: name: @@ -4291,7 +4595,7 @@ components: - status - is_physical_backup - inserted_at - PhysicalBackup: + V1PhysicalBackup: type: object properties: earliest_physical_backup_date_unix: @@ -4312,7 +4616,7 @@ components: items: $ref: '#/components/schemas/V1Backup' physical_backup_data: - $ref: '#/components/schemas/PhysicalBackup' + $ref: '#/components/schemas/V1PhysicalBackup' required: - region - walg_enabled @@ -4362,7 +4666,6 @@ components: type: string enum: - AI_SQL_GENERATOR_OPT_IN - - PREVIEW_BRANCHES_OPT_IN id: type: string name: diff --git a/cmd/projects.go b/cmd/projects.go index 7fe6eb6f9..9c72661fd 100644 --- a/cmd/projects.go +++ b/cmd/projects.go @@ -33,8 +33,8 @@ var ( Allowed: make([]string, len(utils.RegionMap)), } plan = utils.EnumFlag{ - Allowed: []string{string(api.CreateProjectBodyPlanFree), string(api.CreateProjectBodyPlanPro)}, - Value: string(api.CreateProjectBodyPlanFree), + Allowed: []string{string(api.V1CreateProjectBodyPlanFree), string(api.V1CreateProjectBodyPlanPro)}, + Value: string(api.V1CreateProjectBodyPlanFree), } projectsCreateCmd = &cobra.Command{ @@ -55,11 +55,11 @@ var ( if len(args) > 0 { projectName = args[0] } - return create.Run(cmd.Context(), api.CreateProjectBody{ + return create.Run(cmd.Context(), api.V1CreateProjectBody{ Name: projectName, OrganizationId: orgId, DbPass: dbPassword, - Region: api.CreateProjectBodyRegion(region.Value), + Region: api.V1CreateProjectBodyRegion(region.Value), }, afero.NewOsFs()) }, } diff --git a/cmd/storage.go b/cmd/storage.go index 58b015564..3e6eb8fea 100644 --- a/cmd/storage.go +++ b/cmd/storage.go @@ -3,12 +3,12 @@ package cmd import ( "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/supabase/cli/internal/storage" "github.com/supabase/cli/internal/storage/client" "github.com/supabase/cli/internal/storage/cp" "github.com/supabase/cli/internal/storage/ls" "github.com/supabase/cli/internal/storage/mv" "github.com/supabase/cli/internal/storage/rm" + "github.com/supabase/cli/pkg/storage" ) var ( @@ -26,7 +26,7 @@ var ( Short: "List objects by path prefix", Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - objectPath := storage.STORAGE_SCHEME + ":///" + objectPath := client.STORAGE_SCHEME + ":///" if len(args) > 0 { objectPath = args[0] } @@ -34,7 +34,7 @@ var ( }, } - options client.FileOptions + options storage.FileOptions maxJobs uint cpCmd = &cobra.Command{ @@ -46,7 +46,7 @@ cp -r ss:///bucket/docs . Short: "Copy objects from src to dst path", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { - opts := func(fo *client.FileOptions) { + opts := func(fo *storage.FileOptions) { fo.CacheControl = options.CacheControl fo.ContentType = options.ContentType } @@ -78,6 +78,10 @@ rm ss:///bucket/docs/example.md ss:///bucket/readme.md ) func init() { + storageFlags := storageCmd.PersistentFlags() + storageFlags.Bool("linked", true, "Connects to Storage API of the linked project.") + storageFlags.Bool("local", false, "Connects to Storage API of the local database.") + storageCmd.MarkFlagsMutuallyExclusive("linked", "local") lsCmd.Flags().BoolVarP(&recursive, "recursive", "r", false, "Recursively list a directory.") storageCmd.AddCommand(lsCmd) cpFlags := cpCmd.Flags() diff --git a/go.mod b/go.mod index bfb452052..4983a0c49 100644 --- a/go.mod +++ b/go.mod @@ -8,23 +8,24 @@ require ( github.com/andybalholm/brotli v1.1.0 github.com/cenkalti/backoff/v4 v4.3.0 github.com/charmbracelet/bubbles v0.18.0 - github.com/charmbracelet/bubbletea v0.25.0 + github.com/charmbracelet/bubbletea v0.26.2 github.com/charmbracelet/glamour v0.7.0 github.com/charmbracelet/lipgloss v0.10.0 - github.com/containers/common v0.58.2 + github.com/containers/common v0.58.3 github.com/deepmap/oapi-codegen v1.16.2 - github.com/docker/cli v26.1.0+incompatible - github.com/docker/docker v26.1.0+incompatible + github.com/docker/cli v26.1.2+incompatible + github.com/docker/docker v26.1.2+incompatible github.com/docker/go-connections v0.5.0 github.com/docker/go-units v0.5.0 github.com/getsentry/sentry-go v0.27.0 - github.com/gin-gonic/gin v1.9.1 + github.com/gin-gonic/gin v1.10.0 github.com/go-errors/errors v1.5.1 github.com/go-git/go-git/v5 v5.12.0 github.com/go-xmlfmt/xmlfmt v1.1.2 github.com/golang-jwt/jwt/v5 v5.2.1 - github.com/golangci/golangci-lint v1.57.2 + github.com/golangci/golangci-lint v1.58.1 github.com/google/go-github/v53 v53.2.0 + github.com/google/go-querystring v1.1.0 github.com/google/uuid v1.6.0 github.com/jackc/pgconn v1.14.3 github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa @@ -47,8 +48,8 @@ require ( github.com/withfig/autocomplete-tools/packages/cobra v1.2.0 github.com/zalando/go-keyring v0.2.4 golang.org/x/mod v0.17.0 - golang.org/x/oauth2 v0.19.0 - golang.org/x/term v0.19.0 + golang.org/x/oauth2 v0.20.0 + golang.org/x/term v0.20.0 google.golang.org/grpc v1.63.2 gopkg.in/h2non/gock.v1 v1.1.2 gopkg.in/yaml.v3 v3.0.1 @@ -59,15 +60,16 @@ require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.1 // indirect dario.cat/mergo v1.0.0 // indirect - github.com/4meepo/tagalign v1.3.3 // indirect + github.com/4meepo/tagalign v1.3.4 // indirect github.com/Abirdcfly/dupword v0.0.14 // indirect - github.com/Antonboom/errname v0.1.12 // indirect - github.com/Antonboom/nilnil v0.1.7 // indirect + github.com/Antonboom/errname v0.1.13 // indirect + github.com/Antonboom/nilnil v0.1.8 // indirect github.com/Antonboom/testifylint v1.2.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/Crocmagnon/fatcontext v0.2.2 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 // indirect - github.com/Masterminds/semver v1.5.0 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect github.com/ProtonMail/go-crypto v1.0.0 // indirect @@ -91,24 +93,24 @@ require ( github.com/breml/bidichk v0.2.7 // indirect github.com/breml/errchkjson v0.3.6 // indirect github.com/butuzov/ireturn v0.3.0 // indirect - github.com/butuzov/mirror v1.1.0 // indirect - github.com/bytedance/sonic v1.10.0-rc3 // indirect + github.com/butuzov/mirror v1.2.0 // indirect + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/catenacyber/perfsprint v0.7.1 // indirect github.com/ccojocar/zxcvbn-go v1.0.2 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/charithe/durationcheck v0.0.10 // indirect github.com/charmbracelet/harmonica v0.2.0 // indirect github.com/chavacava/garif v0.1.0 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect - github.com/chenzhuoyu/iasm v0.9.0 // indirect - github.com/ckaznocha/intrange v0.1.1 // indirect + github.com/ckaznocha/intrange v0.1.2 // indirect github.com/cloudflare/circl v1.3.7 // indirect - github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containers/storage v1.53.0 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect - github.com/daixiang0/gci v0.12.3 // indirect + github.com/daixiang0/gci v0.13.4 // indirect github.com/danieljoos/wincred v1.2.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect @@ -120,19 +122,20 @@ require ( github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/ettle/strcase v0.2.0 // indirect github.com/fatih/color v1.16.0 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/firefart/nonamedreturns v1.0.4 // indirect + github.com/firefart/nonamedreturns v1.0.5 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/getkin/kin-openapi v0.118.0 // indirect - github.com/ghostiam/protogetter v0.3.5 // indirect + github.com/ghostiam/protogetter v0.3.6 // indirect github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-critic/go-critic v0.11.2 // indirect + github.com/go-critic/go-critic v0.11.3 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-logr/logr v1.4.1 // indirect @@ -141,7 +144,7 @@ require ( github.com/go-openapi/swag v0.22.10 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.14.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.2.0 // indirect @@ -159,12 +162,12 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e // indirect - github.com/golangci/misspell v0.4.1 // indirect + github.com/golangci/misspell v0.5.1 // indirect + github.com/golangci/modinfo v0.3.4 // indirect github.com/golangci/plugin-module-register v0.1.1 // indirect - github.com/golangci/revgrep v0.5.2 // 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/go-querystring v1.1.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 @@ -189,24 +192,25 @@ require ( github.com/jgautheron/goconst v1.7.1 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect - github.com/jjti/go-spancheck v0.5.3 // indirect + github.com/jjti/go-spancheck v0.6.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/julz/importas v0.1.0 // indirect - github.com/karamaru-alpha/copyloopvar v1.0.10 // indirect + github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kisielk/errcheck v1.7.0 // indirect github.com/kkHAIKE/contextcheck v1.1.5 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/kulti/thelper v0.6.3 // indirect github.com/kunwardeep/paralleltest v1.0.10 // indirect github.com/kyoh86/exportloopref v0.1.11 // indirect + github.com/lasiar/canonicalheader v1.0.6 // indirect github.com/ldez/gomoddirectives v0.2.4 // indirect github.com/ldez/tagliatelle v0.5.0 // indirect - github.com/leodido/go-urn v1.2.4 // indirect - github.com/leonklingele/grouper v1.1.1 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/leonklingele/grouper v1.1.2 // indirect github.com/lib/pq v1.10.9 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lufeee/execinquery v1.2.1 // indirect @@ -234,7 +238,7 @@ require ( github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/moricho/tparallel v0.3.1 // indirect github.com/morikuni/aec v1.0.0 // indirect - github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.15.2 // indirect github.com/nakabonne/nestif v0.3.1 // indirect @@ -244,23 +248,24 @@ require ( github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/pelletier/go-toml/v2 v2.2.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/perimeterx/marshmallow v1.1.4 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/polyfloyd/go-errorlint v1.4.8 // indirect + github.com/polyfloyd/go-errorlint v1.5.1 // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect github.com/quasilyte/go-ruleguard v0.4.2 // indirect + github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect - github.com/ryancurrah/gomodguard v1.3.1 // indirect + github.com/ryancurrah/gomodguard v1.3.2 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect @@ -293,9 +298,9 @@ require ( github.com/tomarrell/wrapcheck/v2 v2.8.3 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.11 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect github.com/ultraware/funlen v0.1.0 // indirect - github.com/ultraware/whitespace v0.1.0 // indirect + github.com/ultraware/whitespace v0.1.1 // indirect github.com/uudashr/gocognit v1.1.2 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect @@ -303,13 +308,13 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xen0n/gosmopolitan v1.2.2 // indirect github.com/yagipy/maintidx v1.0.0 // indirect - github.com/yeya24/promlinter v0.2.0 // indirect + github.com/yeya24/promlinter v0.3.0 // indirect github.com/ykadowak/zerologlint v0.1.5 // indirect github.com/yuin/goldmark v1.5.4 // indirect github.com/yuin/goldmark-emoji v1.0.2 // indirect - gitlab.com/bosi/decorder v0.4.1 // indirect - go-simpler.org/musttag v0.9.0 // indirect - go-simpler.org/sloglint v0.5.0 // indirect + gitlab.com/bosi/decorder v0.4.2 // indirect + go-simpler.org/musttag v0.12.1 // indirect + go-simpler.org/sloglint v0.6.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel v1.25.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.25.0 // indirect @@ -325,22 +330,22 @@ require ( go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/arch v0.4.0 // indirect - golang.org/x/crypto v0.21.0 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.23.0 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.19.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + golang.org/x/tools v0.21.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect honnef.co/go/tools v0.4.7 // indirect mvdan.cc/gofumpt v0.6.0 // indirect - mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 // indirect + mvdan.cc/unparam v0.0.0-20240427195214-063aff900ca1 // indirect ) diff --git a/go.sum b/go.sum index 7bc0b2656..e443999aa 100644 --- a/go.sum +++ b/go.sum @@ -37,14 +37,14 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/4meepo/tagalign v1.3.3 h1:ZsOxcwGD/jP4U/aw7qeWu58i7dwYemfy5Y+IF1ACoNw= -github.com/4meepo/tagalign v1.3.3/go.mod h1:Q9c1rYMZJc9dPRkbQPpcBNCLEmY2njbAsXhQOZFE2dE= +github.com/4meepo/tagalign v1.3.4 h1:P51VcvBnf04YkHzjfclN6BbsopfJR5rxs1n+5zHt+w8= +github.com/4meepo/tagalign v1.3.4/go.mod h1:M+pnkHH2vG8+qhE5bVc/zeP7HS/j910Fwa9TUSyZVI0= github.com/Abirdcfly/dupword v0.0.14 h1:3U4ulkc8EUo+CaT105/GJ1BQwtgyj6+VaBVbAX11Ba8= github.com/Abirdcfly/dupword v0.0.14/go.mod h1:VKDAbxdY8YbKUByLGg8EETzYSuC4crm9WwI6Y3S0cLI= -github.com/Antonboom/errname v0.1.12 h1:oh9ak2zUtsLp5oaEd/erjB4GPu9w19NyoIskZClDcQY= -github.com/Antonboom/errname v0.1.12/go.mod h1:bK7todrzvlaZoQagP1orKzWXv59X/x0W0Io2XT1Ssro= -github.com/Antonboom/nilnil v0.1.7 h1:ofgL+BA7vlA1K2wNQOsHzLJ2Pw5B5DpWRLdDAVvvTow= -github.com/Antonboom/nilnil v0.1.7/go.mod h1:TP+ScQWVEq0eSIxqU8CbdT5DFWoHp0MbP+KMUO1BKYQ= +github.com/Antonboom/errname v0.1.13 h1:JHICqsewj/fNckzrfVSe+T33svwQxmjC+1ntDsHOVvM= +github.com/Antonboom/errname v0.1.13/go.mod h1:uWyefRYRN54lBg6HseYCFhs6Qjcy41Y3Jl/dVhA87Ns= +github.com/Antonboom/nilnil v0.1.8 h1:97QG7xrLq4TBK2U9aFq/I8Mcgz67pwMIiswnTA9gIn0= +github.com/Antonboom/nilnil v0.1.8/go.mod h1:iGe2rYwCq5/Me1khrysB4nwI7swQvjclR8/YRPl5ihQ= github.com/Antonboom/testifylint v1.2.0 h1:015bxD8zc5iY8QwTp4+RG9I4kIbqwvGX9TrBbb7jGdM= github.com/Antonboom/testifylint v1.2.0/go.mod h1:rkmEqjqVnHDRNsinyN6fPSLnoajzFwsCcguJgwADBkw= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= @@ -53,14 +53,15 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Crocmagnon/fatcontext v0.2.2 h1:OrFlsDdOj9hW/oBEJBNSuH7QWf+E9WPVHw+x52bXVbk= +github.com/Crocmagnon/fatcontext v0.2.2/go.mod h1:WSn/c/+MMNiD8Pri0ahRj0o9jVpeowzavOQplBJw6u0= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 h1:sATXp1x6/axKxz2Gjxv8MALP0bXaNRfQinEwyfMcx8c= github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0/go.mod h1:Nl76DrGNJTA1KJ0LePKBw/vznBX1EHbAZX8mwjR82nI= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= @@ -143,13 +144,13 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXe github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/butuzov/ireturn v0.3.0 h1:hTjMqWw3y5JC3kpnC5vXmFJAWI/m31jaCYQqzkS6PL0= github.com/butuzov/ireturn v0.3.0/go.mod h1:A09nIiwiqzN/IoVo9ogpa0Hzi9fex1kd9PSD6edP5ZA= -github.com/butuzov/mirror v1.1.0 h1:ZqX54gBVMXu78QLoiqdwpl2mgmoOJTk7s4p4o+0avZI= -github.com/butuzov/mirror v1.1.0/go.mod h1:8Q0BdQU6rC6WILDiBM60DBfvV78OLJmMmixe7GF45AE= +github.com/butuzov/mirror v1.2.0 h1:9YVK1qIjNspaqWutSv8gsge2e/Xpq1eqEkslEUHy5cs= +github.com/butuzov/mirror v1.2.0/go.mod h1:DqZZDtzm42wIAIyHXeN8W/qb1EPlb9Qn/if9icBOpdQ= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.10.0-rc3 h1:uNSnscRapXTwUgTyOF0GVljYD08p9X/Lbr9MweSV3V0= -github.com/bytedance/sonic v1.10.0-rc3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/catenacyber/perfsprint v0.7.1 h1:PGW5G/Kxn+YrN04cRAZKC+ZuvlVwolYMrIyyTJ/rMmc= github.com/catenacyber/perfsprint v0.7.1/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= @@ -165,8 +166,8 @@ github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iy github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0= github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw= -github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= -github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= +github.com/charmbracelet/bubbletea v0.26.2 h1:Eeb+n75Om9gQ+I6YpbCXQRKHt5Pn4vMwusQpwLiEgJQ= +github.com/charmbracelet/bubbletea v0.26.2/go.mod h1:6I0nZ3YHUrQj7YHIHlM8RySX4ZIthTliMY+W8X8b+Gs= github.com/charmbracelet/glamour v0.7.0 h1:2BtKGZ4iVJCDfMF229EzbeR1QRKLWztO9dMtjmqZSng= github.com/charmbracelet/glamour v0.7.0/go.mod h1:jUMh5MeihljJPQbJ/wf4ldw2+yBP59+ctV36jASy7ps= github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= @@ -175,32 +176,28 @@ github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMt github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= -github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo= -github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/ckaznocha/intrange v0.1.1 h1:gHe4LfqCspWkh8KpJFs20fJz3XRHFBFUV9yI7Itu83Q= -github.com/ckaznocha/intrange v0.1.1/go.mod h1:RWffCw/vKBwHeOEwWdCikAtY0q4gGt8VhJZEEA5n+RE= +github.com/ckaznocha/intrange v0.1.2 h1:3Y4JAxcMntgb/wABQ6e8Q8leMd26JbX2790lIss9MTI= +github.com/ckaznocha/intrange v0.1.2/go.mod h1:RWffCw/vKBwHeOEwWdCikAtY0q4gGt8VhJZEEA5n+RE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e85keuznYcH5rqI438v41pKcBl4ZxQ= github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= -github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containers/common v0.58.2 h1:5nu9lQz4QNSgovNk7NRk33SkqkVNKYoXh7L6gXmACow= -github.com/containers/common v0.58.2/go.mod h1:l3vMqanJGj7tZ3W/i76gEJ128VXgFUO1tLaohJXPvdk= +github.com/containers/common v0.58.3 h1:Iy/CdYjluEK926QT+ejonz7YvoRHazeW7BAiLIkmUQ4= +github.com/containers/common v0.58.3/go.mod h1:p4V1SNk+WOISgp01m+axuqCUxaDP3WSZPPzvnJnS/cQ= github.com/containers/storage v1.53.0 h1:VSES3C/u1pxjTJIXvLrSmyP7OBtDky04oGu07UvdTEA= github.com/containers/storage v1.53.0/go.mod h1:pujcoOSc+upx15Jirdkebhtd8uJiLwbSd/mYT6zDJK8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -214,8 +211,8 @@ github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDU github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= -github.com/daixiang0/gci v0.12.3 h1:yOZI7VAxAGPQmkb1eqt5g/11SUlwoat1fSblGLmdiQc= -github.com/daixiang0/gci v0.12.3/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI= +github.com/daixiang0/gci v0.13.4 h1:61UGkmpoAcxHM2hhNkZEf5SzwQtWJXTSws7jaPyqwlw= +github.com/daixiang0/gci v0.13.4/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs= github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -233,13 +230,13 @@ github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnephin/pflag v1.0.7 h1:oxONGlWxhmUct0YzKTgrpQv9AUA1wtPBn7zuSjJqptk= github.com/dnephin/pflag v1.0.7/go.mod h1:uxE91IoWURlOiTUIA8Mq5ZZkAv3dPUfZNaT80Zm7OQE= -github.com/docker/cli v26.1.0+incompatible h1:+nwRy8Ocd8cYNQ60mozDDICICD8aoFGtlPXifX/UQ3Y= -github.com/docker/cli v26.1.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v26.1.2+incompatible h1:/MWZpUMMlr1hCGyquL8QNbL1hbivQ1kLuT3Z9s1Tlpg= +github.com/docker/cli v26.1.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v26.1.0+incompatible h1:W1G9MPNbskA6VZWL7b3ZljTh0pXI68FpINx0GKaOdaM= -github.com/docker/docker v26.1.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v26.1.2+incompatible h1:UVX5ZOrrfTGZZYEP+ZDq3Xn9PdHNXaSYMFPDumMqG2k= +github.com/docker/docker v26.1.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo= github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= @@ -263,6 +260,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= @@ -273,8 +272,8 @@ github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4 github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y= -github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= +github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= +github.com/firefart/nonamedreturns v1.0.5/go.mod h1:gHJjDqhGM4WyPt639SOZs+G89Ko7QKH5R5BhnO6xJhw= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -285,22 +284,22 @@ github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQ github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/getkin/kin-openapi v0.118.0 h1:z43njxPmJ7TaPpMSCQb7PN0dEYno4tyBPQcrFdHoLuM= github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc= github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= -github.com/ghostiam/protogetter v0.3.5 h1:+f7UiF8XNd4w3a//4DnusQ2SZjPkUjxkMEfjbxOK4Ug= -github.com/ghostiam/protogetter v0.3.5/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= +github.com/ghostiam/protogetter v0.3.6 h1:R7qEWaSgFCsy20yYHNIJsU9ZOb8TziSRRxuAOTVKeOk= +github.com/ghostiam/protogetter v0.3.6/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= -github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= -github.com/go-critic/go-critic v0.11.2 h1:81xH/2muBphEgPtcwH1p6QD+KzXl2tMSi3hXjBSxDnM= -github.com/go-critic/go-critic v0.11.2/go.mod h1:OePaicfjsf+KPy33yq4gzv6CO7TEQ9Rom6ns1KsJnl8= +github.com/go-critic/go-critic v0.11.3 h1:SJbYD/egY1noYjTMNTlhGaYlfQ77rQmrNH7h+gtn0N0= +github.com/go-critic/go-critic v0.11.3/go.mod h1:Je0h5Obm1rR5hAGA9mP2PDiOOk53W+n7pyvXErFKIgI= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -338,8 +337,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= -github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= @@ -424,14 +423,16 @@ github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9 github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e h1:ULcKCDV1LOZPFxGZaA6TlQbiM3J2GCPnkx/bGF6sX/g= github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e/go.mod h1:Pm5KhLPA8gSnQwrQ6ukebRcapGb/BG9iUkdaiCcGHJM= -github.com/golangci/golangci-lint v1.57.2 h1:NNhxfZyL5He1WWDrIvl1a4n5bvWZBcgAqBwlJAAgLTw= -github.com/golangci/golangci-lint v1.57.2/go.mod h1:ApiG3S3Ca23QyfGp5BmsorTiVxJpr5jGiNS0BkdSidg= -github.com/golangci/misspell v0.4.1 h1:+y73iSicVy2PqyX7kmUefHusENlrP9YwuHZHPLGQj/g= -github.com/golangci/misspell v0.4.1/go.mod h1:9mAN1quEo3DlpbaIKKyEvRxK1pwqR9s/Sea1bJCtlNI= +github.com/golangci/golangci-lint v1.58.1 h1:IYKjkt7nofq/mYXiDUyJiBZQi5kxD0jPCjBy6VXxjz8= +github.com/golangci/golangci-lint v1.58.1/go.mod h1:IX9uSbhwDDOVTcceKZWmshlally+fOQYv1pZhIJCMNw= +github.com/golangci/misspell v0.5.1 h1:/SjR1clj5uDjNLwYzCahHwIOPmQgoH04AyQIiWGbhCM= +github.com/golangci/misspell v0.5.1/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= +github.com/golangci/modinfo v0.3.4 h1:oU5huX3fbxqQXdfspamej74DFX0kyGLkw1ppvXoJ8GA= +github.com/golangci/modinfo v0.3.4/go.mod h1:wytF1M5xl9u0ij8YSvhkEVPP3M5Mc7XLl1pxH3B2aUM= github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= -github.com/golangci/revgrep v0.5.2 h1:EndcWoRhcnfj2NHQ+28hyuXpLMF+dQmCN+YaeeIl4FU= -github.com/golangci/revgrep v0.5.2/go.mod h1:bjAMA+Sh/QUfTDcHzxfyHxr4xKvllVr/0sCv2e7jJHA= +github.com/golangci/revgrep v0.5.3 h1:3tL7c1XBMtWHHqVpS5ChmiAAoe4PF/d5+ULzV9sLAzs= +github.com/golangci/revgrep v0.5.3/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNFP0hTEi1YKjB/ub8zkpaOqFFMApi2EAs= github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -586,8 +587,8 @@ github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d/go.mod h1:h+uFLl github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48= github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= -github.com/jjti/go-spancheck v0.5.3 h1:vfq4s2IB8T3HvbpiwDTYgVPj1Ze/ZSXrTtaZRTc7CuM= -github.com/jjti/go-spancheck v0.5.3/go.mod h1:eQdOX1k3T+nAKvZDyLC3Eby0La4dZ+I19iOl5NzSPFE= +github.com/jjti/go-spancheck v0.6.1 h1:ZK/wE5Kyi1VX3PJpUO2oEgeoI4FWOUm7Shb2Gbv5obI= +github.com/jjti/go-spancheck v0.6.1/go.mod h1:vF1QkOO159prdo6mHRxak2CpzDpHAfKiPUDP/NeRnX8= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -607,8 +608,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= -github.com/karamaru-alpha/copyloopvar v1.0.10 h1:8HYDy6KQYqTmD7JuhZMWS1nwPru9889XI24ROd/+WXI= -github.com/karamaru-alpha/copyloopvar v1.0.10/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= +github.com/karamaru-alpha/copyloopvar v1.1.0 h1:x7gNyKcC2vRBO1H2Mks5u1VxQtYvFiym7fCjIP8RPos= +github.com/karamaru-alpha/copyloopvar v1.1.0/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -618,8 +619,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/kkHAIKE/contextcheck v1.1.5 h1:CdnJh63tcDe53vG+RebdpdXJTc9atMgGqdx8LXxiilg= github.com/kkHAIKE/contextcheck v1.1.5/go.mod h1:O930cpht4xb1YQpK+1+AgoM3mFsvxr7uyFptcnWTYUA= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= -github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -642,14 +643,16 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ= github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA= +github.com/lasiar/canonicalheader v1.0.6 h1:LJiiZ/MzkqibXOL2v+J8+WZM21pM0ivrBY/jbm9f5fo= +github.com/lasiar/canonicalheader v1.0.6/go.mod h1:GfXTLQb3O1qF5qcSTyXTnfNUggUNyzbkOSpzZ0dpUJo= github.com/ldez/gomoddirectives v0.2.4 h1:j3YjBIjEBbqZ0NKtBNzr8rtMHTOrLPeiwTkfUJZ3alg= github.com/ldez/gomoddirectives v0.2.4/go.mod h1:oWu9i62VcQDYp9EQ0ONTfqLNh+mDLWWDO+SO0qSQw5g= github.com/ldez/tagliatelle v0.5.0 h1:epgfuYt9v0CG3fms0pEgIMNPuFf/LpPIfjk4kyqSioo= github.com/ldez/tagliatelle v0.5.0/go.mod h1:rj1HmWiL1MiKQuOONhd09iySTEkUuE/8+5jtPYz9xa4= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/leonklingele/grouper v1.1.1 h1:suWXRU57D4/Enn6pXR0QVqqWWrnJ9Osrz+5rjt8ivzU= -github.com/leonklingele/grouper v1.1.1/go.mod h1:uk3I3uDfi9B6PeUjsCKi6ndcf63Uy7snXgR4yDYQVDY= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= +github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -735,8 +738,8 @@ github.com/moricho/tparallel v0.3.1 h1:fQKD4U1wRMAYNngDonW5XupoB/ZGJHdpzrWqgyg9k github.com/moricho/tparallel v0.3.1/go.mod h1:leENX2cUv7Sv2qDgdi0D0fCftN8fRC67Bcn8pqzeYNI= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= -github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= @@ -784,8 +787,8 @@ github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= -github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= -github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw= github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= @@ -800,8 +803,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polyfloyd/go-errorlint v1.4.8 h1:jiEjKDH33ouFktyez7sckv6pHWif9B7SuS8cutDXFHw= -github.com/polyfloyd/go-errorlint v1.4.8/go.mod h1:NNCxFcFjZcw3xNjVdCchERkEM6Oz7wta2XJVxRftwO4= +github.com/polyfloyd/go-errorlint v1.5.1 h1:5gHxDjLyyWij7fhfrjYNNlHsUNQeyx0LFQKUelO3RBo= +github.com/polyfloyd/go-errorlint v1.5.1/go.mod h1:sH1QC1pxxi0fFecsVIzBmxtrgd9IF/SkJpA6wqyKAJs= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -835,6 +838,8 @@ github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/quasilyte/go-ruleguard v0.4.2 h1:htXcXDK6/rO12kiTHKfHuqR4kr3Y4M0J0rOL6CH/BYs= github.com/quasilyte/go-ruleguard v0.4.2/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= +github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= +github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= @@ -854,8 +859,8 @@ github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryancurrah/gomodguard v1.3.1 h1:fH+fUg+ngsQO0ruZXXHnA/2aNllWA1whly4a6UvyzGE= -github.com/ryancurrah/gomodguard v1.3.1/go.mod h1:DGFHzEhi6iJ0oIDfMuo3TgrS+L9gZvrEfmjjuelnRU0= +github.com/ryancurrah/gomodguard v1.3.2 h1:CuG27ulzEB1Gu5Dk5gP8PFxSOZ3ptSdP5iI/3IXxM18= +github.com/ryancurrah/gomodguard v1.3.2/go.mod h1:LqdemiFomEjcxOqirbQCb3JFvSxH2JUYMerTFd3sF2o= github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= @@ -943,7 +948,6 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= @@ -975,12 +979,12 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= -github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81vI= github.com/ultraware/funlen v0.1.0/go.mod h1:XJqmOQja6DpxarLj6Jj1U7JuoS8PvL4nEqDaQhy22p4= -github.com/ultraware/whitespace v0.1.0 h1:O1HKYoh0kIeqE8sFqZf1o0qbORXUCOQFrlaQyZsczZw= -github.com/ultraware/whitespace v0.1.0/go.mod h1:/se4r3beMFNmewJ4Xmz0nMQ941GJt+qmSHGP9emHYe0= +github.com/ultraware/whitespace v0.1.1 h1:bTPOGejYFulW3PkcrqkeQwOd6NKOOXvmGD9bo/Gk8VQ= +github.com/ultraware/whitespace v0.1.1/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= github.com/uudashr/gocognit v1.1.2 h1:l6BAEKJqQH2UpKAPKdMfZf5kE4W/2xk8pfU1OVLvniI= github.com/uudashr/gocognit v1.1.2/go.mod h1:aAVdLURqcanke8h3vg35BC++eseDm66Z7KmchI5et4k= github.com/withfig/autocomplete-tools/packages/cobra v1.2.0 h1:MzD3XeOOSO3mAjOPpF07jFteSKZxsRHvlIcAR9RQzKM= @@ -997,8 +1001,8 @@ github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HH github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= -github.com/yeya24/promlinter v0.2.0 h1:xFKDQ82orCU5jQujdaD8stOHiv8UN68BSdn2a8u8Y3o= -github.com/yeya24/promlinter v0.2.0/go.mod h1:u54lkmBOZrpEbQQ6gox2zWKKLKu2SGe+2KOiextY+IA= +github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= +github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4= github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1016,14 +1020,14 @@ github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nD github.com/zalando/go-keyring v0.2.4 h1:wi2xxTqdiwMKbM6TWwi+uJCG/Tum2UV0jqaQhCa9/68= github.com/zalando/go-keyring v0.2.4/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -gitlab.com/bosi/decorder v0.4.1 h1:VdsdfxhstabyhZovHafFw+9eJ6eU0d2CkFNJcZz/NU4= -gitlab.com/bosi/decorder v0.4.1/go.mod h1:jecSqWUew6Yle1pCr2eLWTensJMmsxHsBwt+PVbkAqA= +gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= +gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= go-simpler.org/assert v0.7.0 h1:OzWWZqfNxt8cLS+MlUp6Tgk1HjPkmgdKBq9qvy8lZsA= go-simpler.org/assert v0.7.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= -go-simpler.org/musttag v0.9.0 h1:Dzt6/tyP9ONr5g9h9P3cnYWCxeBFRkd0uJL/w+1Mxos= -go-simpler.org/musttag v0.9.0/go.mod h1:gA9nThnalvNSKpEoyp3Ko4/vCX2xTpqKoUtNqXOnVR4= -go-simpler.org/sloglint v0.5.0 h1:2YCcd+YMuYpuqthCgubcF5lBSjb6berc5VMOYUHKrpY= -go-simpler.org/sloglint v0.5.0/go.mod h1:EUknX5s8iXqf18KQxKnaBHUPVriiPnOrPjjJcsaTcSQ= +go-simpler.org/musttag v0.12.1 h1:yaMcjl/uyVnd1z6GqIhBiFH/PoqNN9f2IgtU7bp7W/0= +go-simpler.org/musttag v0.12.1/go.mod h1:46HKu04A3Am9Lne5kKP0ssgwY3AeIlqsDzz3UxKROpY= +go-simpler.org/sloglint v0.6.0 h1:0YcqSVG7LI9EVBfRPhgPec79BH6X6mwjFuUR5Mr7j1M= +go-simpler.org/sloglint v0.6.0/go.mod h1:+kJJtebtPePWyG5boFwY46COydAggADDOHM22zOvzBk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -1073,8 +1077,8 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= -golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -1097,8 +1101,8 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1194,16 +1198,16 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= -golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= +golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= +golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1218,8 +1222,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1269,6 +1273,7 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1287,8 +1292,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1300,8 +1305,8 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1316,8 +1321,9 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1392,8 +1398,8 @@ golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= -golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= +golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= +golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1484,8 +1490,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/cenkalti/backoff.v2 v2.2.1 h1:eJ9UAg01/HIHG987TwxvnzK2MgxXq97YY6rYDpY9aII= @@ -1537,8 +1543,8 @@ honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs= honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo= mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA= -mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 h1:zCr3iRRgdk5eIikZNDphGcM6KGVTx3Yu+/Uu9Es254w= -mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14/go.mod h1:ZzZjEpJDOmx8TdVU6umamY3Xy0UAQUI2DHbf05USVbI= +mvdan.cc/unparam v0.0.0-20240427195214-063aff900ca1 h1:Nykk7fggxChwLK4rUPYESzeIwqsuxXXlFEAh5YhaMRo= +mvdan.cc/unparam v0.0.0-20240427195214-063aff900ca1/go.mod h1:ZzZjEpJDOmx8TdVU6umamY3Xy0UAQUI2DHbf05USVbI= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/bootstrap/bootstrap.go b/internal/bootstrap/bootstrap.go index 82d232fc3..e03916c05 100644 --- a/internal/bootstrap/bootstrap.go +++ b/internal/bootstrap/bootstrap.go @@ -30,6 +30,7 @@ import ( "github.com/supabase/cli/internal/utils/flags" "github.com/supabase/cli/internal/utils/tenant" "github.com/supabase/cli/pkg/api" + "github.com/supabase/cli/pkg/fetcher" "golang.org/x/oauth2" "golang.org/x/term" ) @@ -75,7 +76,7 @@ func Run(ctx context.Context, starter StarterTemplate, fsys afero.Fs, options .. return err } // 2. Create project - params := api.CreateProjectBody{ + params := api.V1CreateProjectBody{ Name: filepath.Base(workdir), TemplateUrl: &starter.Url, } @@ -88,9 +89,6 @@ func Run(ctx context.Context, starter StarterTemplate, fsys afero.Fs, options .. if err := backoff.RetryNotify(func() error { fmt.Fprintln(os.Stderr, "Linking project...") keys, err = apiKeys.RunGetApiKeys(ctx, flags.ProjectRef) - if err == nil { - tenant.SetApiKeys(tenant.NewApiKey(keys)) - } return err }, policy, newErrorCallback()); err != nil { return err @@ -99,7 +97,7 @@ func Run(ctx context.Context, starter StarterTemplate, fsys afero.Fs, options .. if err := utils.LoadConfigFS(fsys); err != nil { return err } - link.LinkServices(ctx, flags.ProjectRef, fsys) + link.LinkServices(ctx, flags.ProjectRef, tenant.NewApiKey(keys).Anon, fsys) if err := utils.WriteFile(utils.ProjectRefPath, []byte(flags.ProjectRef), fsys); err != nil { return err } @@ -356,7 +354,7 @@ func downloadSample(ctx context.Context, client *github.Client, templateUrl stri opts := github.RepositoryContentGetOptions{Ref: ref} queue := make([]string, 0) queue = append(queue, root) - jq := utils.NewJobQueue(5) + download := NewDownloader(5, fsys) for len(queue) > 0 { contentPath := queue[0] queue = queue[1:] @@ -369,9 +367,7 @@ func downloadSample(ctx context.Context, client *github.Client, templateUrl stri case "file": path := strings.TrimPrefix(file.GetPath(), root) hostPath := filepath.Join(".", filepath.FromSlash(path)) - if err := jq.Put(func() error { - return utils.DownloadFile(ctx, hostPath, file.GetDownloadURL(), fsys) - }); err != nil { + if err := download.Start(ctx, hostPath, file.GetDownloadURL()); err != nil { return err } case "dir": @@ -381,5 +377,38 @@ func downloadSample(ctx context.Context, client *github.Client, templateUrl stri } } } - return jq.Collect() + return download.Wait() +} + +type Downloader struct { + api *fetcher.Fetcher + queue *utils.JobQueue + fsys afero.Fs +} + +func NewDownloader(concurrency uint, fsys afero.Fs) *Downloader { + return &Downloader{ + api: fetcher.NewFetcher(""), + queue: utils.NewJobQueue(concurrency), + fsys: fsys, + } +} + +func (d *Downloader) Start(ctx context.Context, localPath, remotePath string) error { + job := func() error { + resp, err := d.api.Send(ctx, http.MethodGet, remotePath, nil) + if err != nil { + return err + } + defer resp.Body.Close() + if err := afero.WriteReader(d.fsys, localPath, resp.Body); err != nil { + return errors.Errorf("failed to write file: %w", err) + } + return nil + } + return d.queue.Put(job) +} + +func (d *Downloader) Wait() error { + return d.queue.Collect() } diff --git a/internal/db/diff/diff.go b/internal/db/diff/diff.go index e60c88839..3f15c5fc5 100644 --- a/internal/db/diff/diff.go +++ b/internal/db/diff/diff.go @@ -5,7 +5,9 @@ import ( _ "embed" "fmt" "io" + "io/fs" "os" + "path/filepath" "regexp" "strconv" "strings" @@ -24,6 +26,7 @@ import ( "github.com/supabase/cli/internal/gen/keys" "github.com/supabase/cli/internal/migration/apply" "github.com/supabase/cli/internal/migration/list" + "github.com/supabase/cli/internal/migration/repair" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/internal/utils/parser" ) @@ -35,6 +38,19 @@ func Run(ctx context.Context, schema []string, file string, config pgconn.Config if err := utils.LoadConfigFS(fsys); err != nil { return err } + if utils.IsLocalDatabase(config) { + if container, err := createShadowIfNotExists(ctx, fsys); err != nil { + return err + } else if len(container) > 0 { + defer utils.DockerRemove(container) + if !start.WaitForHealthyService(ctx, container, start.HealthTimeout) { + return errors.New(start.ErrDatabase) + } + if err := migrateBaseDatabase(ctx, container, fsys, options...); err != nil { + return err + } + } + } // 1. Load all user defined schemas if len(schema) == 0 { schema, err = loadSchema(ctx, config, options...) @@ -60,6 +76,35 @@ func Run(ctx context.Context, schema []string, file string, config pgconn.Config return nil } +func createShadowIfNotExists(ctx context.Context, fsys afero.Fs) (string, error) { + if exists, err := afero.DirExists(fsys, utils.SchemasDir); err != nil { + return "", errors.Errorf("failed to check schemas: %w", err) + } else if !exists { + return "", nil + } + if err := utils.AssertSupabaseDbIsRunning(); !errors.Is(err, utils.ErrNotRunning) { + return "", err + } + fmt.Fprintf(os.Stderr, "Creating local database from %s...\n", utils.Bold(utils.SchemasDir)) + return CreateShadowDatabase(ctx, utils.Config.Db.Port) +} + +func loadDeclaredSchemas(fsys afero.Fs) ([]string, error) { + var declared []string + if err := afero.Walk(fsys, utils.SchemasDir, func(path string, info fs.FileInfo, err error) error { + if err != nil { + return err + } + if info.Mode().IsRegular() && filepath.Ext(info.Name()) == ".sql" { + declared = append(declared, path) + } + return nil + }); err != nil { + return nil, errors.Errorf("failed to walk dir: %w", err) + } + return declared, nil +} + // https://github.com/djrobstep/migra/blob/master/migra/statements.py#L6 var dropStatementPattern = regexp.MustCompile(`(?i)drop\s+`) @@ -87,9 +132,9 @@ func loadSchema(ctx context.Context, config pgconn.Config, options ...func(*pgx. return reset.LoadUserSchemas(ctx, conn) } -func CreateShadowDatabase(ctx context.Context) (string, error) { +func CreateShadowDatabase(ctx context.Context, port uint16) (string, error) { config := start.NewContainerConfig() - hostPort := strconv.FormatUint(uint64(utils.Config.Db.ShadowPort), 10) + hostPort := strconv.FormatUint(uint64(port), 10) hostConfig := container.HostConfig{ PortBindings: nat.PortMap{"5432/tcp": []nat.PortBinding{{HostPort: hostPort}}}, AutoRemove: true, @@ -105,7 +150,7 @@ func CreateShadowDatabase(ctx context.Context) (string, error) { func ConnectShadowDatabase(ctx context.Context, timeout time.Duration, options ...func(*pgx.ConnConfig)) (conn *pgx.Conn, err error) { // Retry until connected, cancelled, or timeout policy := backoff.WithMaxRetries(backoff.NewConstantBackOff(time.Second), uint64(timeout.Seconds())) - config := pgconn.Config{Port: uint16(utils.Config.Db.ShadowPort)} + config := pgconn.Config{Port: utils.Config.Db.ShadowPort} connect := func() (*pgx.Conn, error) { return utils.ConnectLocalPostgres(ctx, config, options...) } @@ -128,9 +173,35 @@ func MigrateShadowDatabase(ctx context.Context, container string, fsys afero.Fs, return apply.MigrateUp(ctx, conn, migrations, fsys) } +func migrateBaseDatabase(ctx context.Context, container string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error { + migrations, err := loadDeclaredSchemas(fsys) + if err != nil { + return err + } + conn, err := utils.ConnectLocalPostgres(ctx, pgconn.Config{}, options...) + if err != nil { + return err + } + defer conn.Close(context.Background()) + if err := start.SetupDatabase(ctx, conn, container[:12], os.Stderr, fsys); err != nil { + return err + } + for _, path := range migrations { + fmt.Fprintln(os.Stderr, "Applying schema "+utils.Bold(path)+"...") + migration, err := repair.NewMigrationFromFile(path, fsys) + if err != nil { + return err + } + if err := migration.ExecBatch(ctx, conn); err != nil { + return err + } + } + return nil +} + func DiffDatabase(ctx context.Context, schema []string, config pgconn.Config, w io.Writer, fsys afero.Fs, differ func(context.Context, string, string, []string) (string, error), options ...func(*pgx.ConnConfig)) (string, error) { fmt.Fprintln(w, "Creating shadow database...") - shadow, err := CreateShadowDatabase(ctx) + shadow, err := CreateShadowDatabase(ctx, utils.Config.Db.ShadowPort) if err != nil { return "", err } @@ -144,7 +215,7 @@ func DiffDatabase(ctx context.Context, schema []string, config pgconn.Config, w fmt.Fprintln(w, "Diffing schemas:", strings.Join(schema, ",")) source := utils.ToPostgresURL(pgconn.Config{ Host: utils.Config.Hostname, - Port: uint16(utils.Config.Db.ShadowPort), + Port: utils.Config.Db.ShadowPort, User: "postgres", Password: utils.Config.Db.Password, Database: "postgres", diff --git a/internal/db/diff/pgadmin.go b/internal/db/diff/pgadmin.go index 6c94442f4..248a38a8a 100644 --- a/internal/db/diff/pgadmin.go +++ b/internal/db/diff/pgadmin.go @@ -22,8 +22,8 @@ func SaveDiff(out, file string, fsys afero.Fs) error { fmt.Fprintln(os.Stderr, "No schema changes found") } else if len(file) > 0 { path := new.GetMigrationPath(utils.GetCurrentTimestamp(), file) - if err := afero.WriteFile(fsys, path, []byte(out), 0644); err != nil { - return errors.Errorf("failed to save diff: %w", err) + if err := utils.WriteFile(path, []byte(out), fsys); err != nil { + return err } fmt.Fprintln(os.Stderr, warnDiff) } else { @@ -58,7 +58,7 @@ func run(p utils.Program, ctx context.Context, schema []string, config pgconn.Co p.Send(utils.StatusMsg("Creating shadow database...")) // 1. Create shadow db and run migrations - shadow, err := CreateShadowDatabase(ctx) + shadow, err := CreateShadowDatabase(ctx, utils.Config.Db.ShadowPort) if err != nil { return err } diff --git a/internal/db/pull/pull.go b/internal/db/pull/pull.go index c7b3b755b..83c86aed3 100644 --- a/internal/db/pull/pull.go +++ b/internal/db/pull/pull.go @@ -35,9 +35,6 @@ var ( func Run(ctx context.Context, schema []string, config pgconn.Config, name string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error { // 1. Sanity checks. - if err := utils.AssertDockerIsRunning(ctx); err != nil { - return err - } if err := utils.LoadConfigFS(fsys); err != nil { return err } diff --git a/internal/db/pull/pull_test.go b/internal/db/pull/pull_test.go index 95e2c6030..ffce631ce 100644 --- a/internal/db/pull/pull_test.go +++ b/internal/db/pull/pull_test.go @@ -3,7 +3,6 @@ package pull import ( "context" "errors" - "net/http" "os" "path/filepath" "testing" @@ -41,41 +40,9 @@ var escapedSchemas = []string{ } func TestPullCommand(t *testing.T) { - t.Run("throws error on missing docker", func(t *testing.T) { - // 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()). - Head("/_ping"). - ReplyError(errors.New("network error")) - gock.New(utils.Docker.DaemonHost()). - Get("/_ping"). - ReplyError(errors.New("network error")) - // Run test - err := Run(context.Background(), nil, pgconn.Config{}, "", fsys) - // Check error - assert.ErrorContains(t, err, "network error") - assert.Empty(t, apitest.ListUnmatchedRequests()) - }) - t.Run("throws error on missing config", func(t *testing.T) { // 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()). - Head("/_ping"). - Reply(http.StatusOK). - SetHeader("API-Version", utils.Docker.ClientVersion()). - SetHeader("OSType", "linux") - gock.New(utils.Docker.DaemonHost()). - Get("/_ping"). - Reply(http.StatusOK). - SetHeader("API-Version", utils.Docker.ClientVersion()). - SetHeader("OSType", "linux") // Run test err := Run(context.Background(), nil, pgconn.Config{}, "", fsys) // Check error @@ -87,19 +54,6 @@ func TestPullCommand(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() require.NoError(t, utils.WriteConfig(fsys, false)) - // Setup mock docker - require.NoError(t, apitest.MockDocker(utils.Docker)) - defer gock.OffAll() - gock.New(utils.Docker.DaemonHost()). - Head("/_ping"). - Reply(http.StatusOK). - SetHeader("API-Version", utils.Docker.ClientVersion()). - SetHeader("OSType", "linux") - gock.New(utils.Docker.DaemonHost()). - Get("/_ping"). - Reply(http.StatusOK). - SetHeader("API-Version", utils.Docker.ClientVersion()). - SetHeader("OSType", "linux") // Run test err := Run(context.Background(), nil, pgconn.Config{}, "", fsys) // Check error @@ -111,19 +65,6 @@ func TestPullCommand(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() require.NoError(t, utils.WriteConfig(fsys, false)) - // Setup mock docker - require.NoError(t, apitest.MockDocker(utils.Docker)) - defer gock.OffAll() - gock.New(utils.Docker.DaemonHost()). - Head("/_ping"). - Reply(http.StatusOK). - SetHeader("API-Version", utils.Docker.ClientVersion()). - SetHeader("OSType", "linux") - gock.New(utils.Docker.DaemonHost()). - Get("/_ping"). - Reply(http.StatusOK). - SetHeader("API-Version", utils.Docker.ClientVersion()). - SetHeader("OSType", "linux") // Setup mock postgres conn := pgtest.NewConn() defer conn.Close(t) diff --git a/internal/db/remote/changes/changes.go b/internal/db/remote/changes/changes.go index 2bdd3abe6..d35defd1b 100644 --- a/internal/db/remote/changes/changes.go +++ b/internal/db/remote/changes/changes.go @@ -15,13 +15,8 @@ var output string func Run(ctx context.Context, schema []string, config pgconn.Config, fsys afero.Fs) error { // Sanity checks. - { - if err := utils.AssertDockerIsRunning(ctx); err != nil { - return err - } - if err := utils.LoadConfigFS(fsys); err != nil { - return err - } + if err := utils.LoadConfigFS(fsys); err != nil { + return err } if err := utils.RunProgram(ctx, func(p utils.Program, ctx context.Context) error { diff --git a/internal/db/remote/commit/commit.go b/internal/db/remote/commit/commit.go index 3669c8697..718b23068 100644 --- a/internal/db/remote/commit/commit.go +++ b/internal/db/remote/commit/commit.go @@ -20,13 +20,8 @@ import ( func Run(ctx context.Context, schema []string, config pgconn.Config, fsys afero.Fs) error { // Sanity checks. - { - if err := utils.AssertDockerIsRunning(ctx); err != nil { - return err - } - if err := utils.LoadConfigFS(fsys); err != nil { - return err - } + if err := utils.LoadConfigFS(fsys); err != nil { + return err } if err := utils.RunProgram(ctx, func(p utils.Program, ctx context.Context) error { @@ -86,7 +81,7 @@ func fetchRemote(p utils.Program, ctx context.Context, schema []string, timestam if len(output) == 0 { return errors.New("No schema changes found") } - return afero.WriteFile(fsys, path, []byte(output), 0644) + return utils.WriteFile(path, []byte(output), fsys) } func assertRemoteInSync(ctx context.Context, conn *pgx.Conn, fsys afero.Fs) error { diff --git a/internal/db/reset/reset_test.go b/internal/db/reset/reset_test.go index 9728bac42..650c9557d 100644 --- a/internal/db/reset/reset_test.go +++ b/internal/db/reset/reset_test.go @@ -29,7 +29,7 @@ func TestResetCommand(t *testing.T) { var dbConfig = pgconn.Config{ Host: utils.Config.Hostname, - Port: uint16(utils.Config.Db.Port), + Port: utils.Config.Db.Port, User: "admin", Password: "password", Database: "postgres", diff --git a/internal/db/start/start.go b/internal/db/start/start.go index 1b1992516..778e3e5eb 100644 --- a/internal/db/start/start.go +++ b/internal/db/start/start.go @@ -273,5 +273,5 @@ func SetupDatabase(ctx context.Context, conn *pgx.Conn, host string, w io.Writer if err := initSchema(ctx, conn, host, w); err != nil { return err } - return push.CreateCustomRoles(ctx, conn, w, fsys) + return push.CreateCustomRoles(ctx, conn, os.Stderr, fsys) } diff --git a/internal/db/start/start_test.go b/internal/db/start/start_test.go index 518a830c3..eae42c335 100644 --- a/internal/db/start/start_test.go +++ b/internal/db/start/start_test.go @@ -196,11 +196,6 @@ func TestStartCommand(t *testing.T) { // Setup mock docker require.NoError(t, apitest.MockDocker(utils.Docker)) defer gock.OffAll() - gock.New(utils.Docker.DaemonHost()). - Head("/_ping"). - Reply(http.StatusOK). - SetHeader("API-Version", utils.Docker.ClientVersion()). - SetHeader("OSType", "linux") gock.New(utils.Docker.DaemonHost()). Get("/v" + utils.Docker.ClientVersion() + "/containers/"). Reply(http.StatusOK). @@ -219,11 +214,6 @@ func TestStartCommand(t *testing.T) { // Setup mock docker require.NoError(t, apitest.MockDocker(utils.Docker)) defer gock.OffAll() - gock.New(utils.Docker.DaemonHost()). - Head("/_ping"). - Reply(http.StatusOK). - SetHeader("API-Version", utils.Docker.ClientVersion()). - SetHeader("OSType", "linux") gock.New(utils.Docker.DaemonHost()). Get("/v" + utils.Docker.ClientVersion() + "/containers/"). Reply(http.StatusNotFound) diff --git a/internal/debug/postgres.go b/internal/debug/postgres.go index 5bda31daa..9bc275b02 100644 --- a/internal/debug/postgres.go +++ b/internal/debug/postgres.go @@ -3,6 +3,7 @@ package debug import ( "context" "encoding/json" + "errors" "io" "log" "net" @@ -59,7 +60,7 @@ func (p *Proxy) DialFunc(ctx context.Context, network, addr string) (net.Conn, e for { // Since pgx closes connection first, every EOF is seen as unexpected - if err := <-p.errChan; err != nil && err != io.ErrUnexpectedEOF { + if err := <-p.errChan; err != nil && !errors.Is(err, io.ErrUnexpectedEOF) { panic(err) } } diff --git a/internal/functions/new/new.go b/internal/functions/new/new.go index f9447cf91..7abc36584 100644 --- a/internal/functions/new/new.go +++ b/internal/functions/new/new.go @@ -51,7 +51,7 @@ func Run(ctx context.Context, slug string, fsys afero.Fs) error { utils.CmdSuggestion = utils.SuggestDebugFlag } config := indexConfig{ - Port: uint16(utils.Config.Api.Port), + Port: utils.Config.Api.Port, Slug: slug, Token: utils.Config.Auth.AnonKey, } diff --git a/internal/functions/serve/serve.go b/internal/functions/serve/serve.go index b1015732c..ad8de85c0 100644 --- a/internal/functions/serve/serve.go +++ b/internal/functions/serve/serve.go @@ -14,10 +14,10 @@ import ( "github.com/docker/docker/api/types/network" "github.com/docker/go-connections/nat" "github.com/go-errors/errors" - "github.com/joho/godotenv" "github.com/spf13/afero" "github.com/spf13/viper" "github.com/supabase/cli/internal/db/start" + "github.com/supabase/cli/internal/secrets/set" "github.com/supabase/cli/internal/utils" ) @@ -183,18 +183,14 @@ func parseEnvFile(envFilePath string, fsys afero.Fs) ([]string, error) { if len(envFilePath) == 0 { return env, nil } - f, err := fsys.Open(envFilePath) + envMap, err := set.ParseEnvFile(envFilePath, fsys) if err != nil { - return env, errors.Errorf("failed to open env file: %w", err) - } - defer f.Close() - envMap, err := godotenv.Parse(f) - if err != nil { - return env, errors.Errorf("failed to parse env file: %w", err) + return env, err } for name, value := range envMap { if strings.HasPrefix(name, "SUPABASE_") { - return env, errors.Errorf("Invalid env name: %s. Env names cannot start with SUPABASE_.", name) + fmt.Fprintln(os.Stderr, "Env name cannot start with SUPABASE_, skipping: "+name) + continue } env = append(env, name+"="+value) } diff --git a/internal/gen/types/typescript/typescript_test.go b/internal/gen/types/typescript/typescript_test.go index 9e4ed81e4..31fec482b 100644 --- a/internal/gen/types/typescript/typescript_test.go +++ b/internal/gen/types/typescript/typescript_test.go @@ -25,7 +25,7 @@ func TestGenLocalCommand(t *testing.T) { dbConfig := pgconn.Config{ Host: utils.Config.Hostname, - Port: uint16(utils.Config.Db.Port), + Port: utils.Config.Db.Port, User: "admin", Password: "password", } diff --git a/internal/hostnames/common_test.go b/internal/hostnames/common_test.go index 6fd26735b..ede963792 100644 --- a/internal/hostnames/common_test.go +++ b/internal/hostnames/common_test.go @@ -14,7 +14,7 @@ func TestVerifyCNAME(t *testing.T) { gock.New("https://1.1.1.1"). Get("/dns-query"). MatchParam("name", "hello.custom-domain.com"). - MatchParam("type", "CNAME"). + MatchParam("type", "5"). MatchHeader("accept", "application/dns-json"). Reply(http.StatusOK). JSON(&map[string]interface{}{"Answer": []map[string]interface{}{ @@ -31,7 +31,7 @@ func TestVerifyCNAMEFailures(t *testing.T) { gock.New("https://1.1.1.1"). Get("/dns-query"). MatchParam("name", "hello.custom-domain.com"). - MatchParam("type", "CNAME"). + MatchParam("type", "5"). MatchHeader("accept", "application/dns-json"). Reply(http.StatusOK). JSON(&map[string]interface{}{"Answer": []map[string]interface{}{ diff --git a/internal/inspect/cache/cache.go b/internal/inspect/cache/cache.go index eb16a7bb4..e689be658 100644 --- a/internal/inspect/cache/cache.go +++ b/internal/inspect/cache/cache.go @@ -24,6 +24,7 @@ func Run(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...fu if err != nil { return err } + defer conn.Close(context.Background()) rows, err := conn.Query(ctx, inspect.CACHE_QUERY) if err != nil { return errors.Errorf("failed to query rows: %w", err) diff --git a/internal/inspect/cache/cache_test.go b/internal/inspect/cache/cache_test.go new file mode 100644 index 000000000..974ba1742 --- /dev/null +++ b/internal/inspect/cache/cache_test.go @@ -0,0 +1,53 @@ +package cache + +import ( + "context" + "testing" + + "github.com/jackc/pgconn" + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/supabase/cli/internal/inspect" + "github.com/supabase/cli/internal/testing/pgtest" +) + +var dbConfig = pgconn.Config{ + Host: "127.0.0.1", + Port: 5432, + User: "admin", + Password: "password", + Database: "postgres", +} + +func TestCacheCommand(t *testing.T) { + t.Run("inspects cache hit rate", func(t *testing.T) { + // Setup in-memory fs + fsys := afero.NewMemMapFs() + // Setup mock postgres + conn := pgtest.NewConn() + defer conn.Close(t) + conn.Query(inspect.CACHE_QUERY). + Reply("SELECT 1", Result{ + Name: "index hit rate", + Ratio: 0.9, + }) + // Run test + err := Run(context.Background(), dbConfig, fsys, conn.Intercept) + // Check error + assert.NoError(t, err) + }) + + t.Run("throws error on empty result", func(t *testing.T) { + // Setup in-memory fs + fsys := afero.NewMemMapFs() + // Setup mock postgres + conn := pgtest.NewConn() + defer conn.Close(t) + conn.Query(inspect.CACHE_QUERY). + Reply("SELECT 1", []interface{}{}) + // Run test + err := Run(context.Background(), dbConfig, fsys, conn.Intercept) + // Check error + assert.ErrorContains(t, err, "cannot find field Name in returned row") + }) +} diff --git a/internal/link/link.go b/internal/link/link.go index 502b78f4a..acdf552c6 100644 --- a/internal/link/link.go +++ b/internal/link/link.go @@ -37,10 +37,11 @@ func (c ConfigCopy) IsEmpty() bool { func Run(ctx context.Context, projectRef string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error { // 1. Check service config - if _, err := tenant.GetApiKeys(ctx, projectRef); err != nil { + keys, err := tenant.GetApiKeys(ctx, projectRef) + if err != nil { return err } - LinkServices(ctx, projectRef, fsys) + LinkServices(ctx, projectRef, keys.Anon, fsys) // 2. Check database connection config := flags.GetDbConfigOptionalPassword(projectRef) @@ -72,7 +73,7 @@ func PostRun(projectRef string, stdout io.Writer, fsys afero.Fs) error { return nil } -func LinkServices(ctx context.Context, projectRef string, fsys afero.Fs) { +func LinkServices(ctx context.Context, projectRef, anonKey string, fsys afero.Fs) { // Ignore non-fatal errors linking services var wg sync.WaitGroup wg.Add(6) @@ -90,25 +91,26 @@ func LinkServices(ctx context.Context, projectRef string, fsys afero.Fs) { }() go func() { defer wg.Done() - if err := linkPostgrestVersion(ctx, projectRef, fsys); err != nil && viper.GetBool("DEBUG") { + if err := linkPooler(ctx, projectRef, fsys); err != nil && viper.GetBool("DEBUG") { fmt.Fprintln(os.Stderr, err) } }() + api := tenant.NewTenantAPI(ctx, projectRef, anonKey) go func() { defer wg.Done() - if err := linkGotrueVersion(ctx, projectRef, fsys); err != nil && viper.GetBool("DEBUG") { + if err := linkPostgrestVersion(ctx, api, fsys); err != nil && viper.GetBool("DEBUG") { fmt.Fprintln(os.Stderr, err) } }() go func() { defer wg.Done() - if err := linkStorageVersion(ctx, projectRef, fsys); err != nil && viper.GetBool("DEBUG") { + if err := linkGotrueVersion(ctx, api, fsys); err != nil && viper.GetBool("DEBUG") { fmt.Fprintln(os.Stderr, err) } }() go func() { defer wg.Done() - if err := linkPooler(ctx, projectRef, fsys); err != nil && viper.GetBool("DEBUG") { + if err := linkStorageVersion(ctx, api, fsys); err != nil && viper.GetBool("DEBUG") { fmt.Fprintln(os.Stderr, err) } }() @@ -127,8 +129,8 @@ func linkPostgrest(ctx context.Context, projectRef string) error { return nil } -func linkPostgrestVersion(ctx context.Context, projectRef string, fsys afero.Fs) error { - version, err := tenant.GetPostgrestVersion(ctx, projectRef) +func linkPostgrestVersion(ctx context.Context, api tenant.TenantAPI, fsys afero.Fs) error { + version, err := api.GetPostgrestVersion(ctx) if err != nil { return err } @@ -160,16 +162,16 @@ func readCsv(line string) []string { return result } -func linkGotrueVersion(ctx context.Context, projectRef string, fsys afero.Fs) error { - version, err := tenant.GetGotrueVersion(ctx, projectRef) +func linkGotrueVersion(ctx context.Context, api tenant.TenantAPI, fsys afero.Fs) error { + version, err := api.GetGotrueVersion(ctx) if err != nil { return err } return utils.WriteFile(utils.GotrueVersionPath, []byte(version), fsys) } -func linkStorageVersion(ctx context.Context, projectRef string, fsys afero.Fs) error { - version, err := tenant.GetStorageVersion(ctx, projectRef) +func linkStorageVersion(ctx context.Context, api tenant.TenantAPI, fsys afero.Fs) error { + version, err := api.GetStorageVersion(ctx) if err != nil { return err } diff --git a/internal/link/link_test.go b/internal/link/link_test.go index 8613b615c..e73a46a48 100644 --- a/internal/link/link_test.go +++ b/internal/link/link_test.go @@ -93,7 +93,7 @@ func TestLinkCommand(t *testing.T) { gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/postgrest"). Reply(200). - JSON(api.PostgrestConfigResponse{}) + JSON(api.V1PostgrestConfigResponse{}) gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/config/database/pgbouncer"). Reply(200). @@ -113,14 +113,14 @@ func TestLinkCommand(t *testing.T) { Get("/storage/v1/version"). Reply(200). BodyString("0.40.4") - postgres := api.DatabaseResponse{ + postgres := api.V1DatabaseResponse{ Host: utils.GetSupabaseDbHost(project), Version: "15.1.0.117", } gock.New(utils.DefaultApiHost). Get("/v1/projects"). Reply(200). - JSON([]api.ProjectResponse{ + JSON([]api.V1ProjectResponse{ { Id: project, Database: &postgres, @@ -246,7 +246,7 @@ func TestLinkPostgrest(t *testing.T) { gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/postgrest"). Reply(200). - JSON(api.PostgrestConfigResponse{}) + JSON(api.V1PostgrestConfigResponse{}) // Run test err := linkPostgrest(context.Background(), project) // Check error @@ -262,7 +262,7 @@ func TestLinkPostgrest(t *testing.T) { gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/postgrest"). Reply(200). - JSON(api.PostgrestConfigResponse{ + JSON(api.V1PostgrestConfigResponse{ DbSchema: "public, graphql_public", DbExtraSearchPath: "public, extensions", MaxRows: 1000, diff --git a/internal/migration/list/list.go b/internal/migration/list/list.go index dd82386fd..ea22ff56c 100644 --- a/internal/migration/list/list.go +++ b/internal/migration/list/list.go @@ -140,8 +140,8 @@ func LoadLocalVersions(fsys afero.Fs) ([]string, error) { var versions []string for _, filename := range names { // LoadLocalMigrations guarantees we always have a match - verion := utils.MigrateFilePattern.FindStringSubmatch(filename)[1] - versions = append(versions, verion) + version := utils.MigrateFilePattern.FindStringSubmatch(filename)[1] + versions = append(versions, version) } return versions, nil } diff --git a/internal/migration/list/list_test.go b/internal/migration/list/list_test.go index f4a1c6258..cb3234a23 100644 --- a/internal/migration/list/list_test.go +++ b/internal/migration/list/list_test.go @@ -103,7 +103,7 @@ func TestRemoteMigrations(t *testing.T) { conn := pgtest.NewConn() defer conn.Close(t) conn.Query(LIST_MIGRATION_VERSION). - Reply("SELECT 1", nil) + Reply("SELECT 1", []interface{}{}) // Run test _, err := loadRemoteVersions(context.Background(), dbConfig, conn.Intercept) // Check error diff --git a/internal/migration/squash/squash.go b/internal/migration/squash/squash.go index 557afafff..05576e12e 100644 --- a/internal/migration/squash/squash.go +++ b/internal/migration/squash/squash.go @@ -80,7 +80,7 @@ func squashToVersion(ctx context.Context, version string, fsys afero.Fs, options func squashMigrations(ctx context.Context, migrations []string, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error { // 1. Start shadow database - shadow, err := diff.CreateShadowDatabase(ctx) + shadow, err := diff.CreateShadowDatabase(ctx, utils.Config.Db.ShadowPort) if err != nil { return err } @@ -100,7 +100,7 @@ func squashMigrations(ctx context.Context, migrations []string, fsys afero.Fs, o schemas := []string{"auth", "storage"} config := pgconn.Config{ Host: utils.Config.Hostname, - Port: uint16(utils.Config.Db.ShadowPort), + Port: utils.Config.Db.ShadowPort, User: "postgres", Password: utils.Config.Db.Password, Database: "postgres", diff --git a/internal/migration/up/up.go b/internal/migration/up/up.go index 93d6aa313..f7a54a43e 100644 --- a/internal/migration/up/up.go +++ b/internal/migration/up/up.go @@ -45,24 +45,27 @@ func GetPendingMigrations(ctx context.Context, includeAll bool, conn *pgx.Conn, // Find unapplied local migrations older than the latest migration on // remote, and remote migrations that are missing from local. var unapplied, missing []string - for i, remote := range remoteMigrations { - for _, filename := range localMigrations[i+len(unapplied)-len(missing):] { - // Check if migration has been applied before, LoadLocalMigrations guarantees a match - local := utils.MigrateFilePattern.FindStringSubmatch(filename)[1] - if remote == local { - break - } - if remote < local { - missing = append(missing, remote) - break - } + i, j := 0, 0 + for i < len(remoteMigrations) && j < len(localMigrations) { + remote := remoteMigrations[i] + filename := localMigrations[j] + // Check if migration has been applied before, LoadLocalMigrations guarantees a match + local := utils.MigrateFilePattern.FindStringSubmatch(filename)[1] + if remote == local { + j++ + i++ + } else if remote < local { + missing = append(missing, remote) + i++ + } else { // Include out-of-order local migrations unapplied = append(unapplied, filename) + j++ } } - // Check if all remote versions exist in local - if len(localMigrations) == 0 { - missing = remoteMigrations + // Ensure all remote versions exist on local + if j == len(localMigrations) { + missing = append(missing, remoteMigrations[i:]...) } if len(missing) > 0 { utils.CmdSuggestion = suggestRevertHistory(missing) diff --git a/internal/migration/up/up_test.go b/internal/migration/up/up_test.go index 515bd8b3e..c82da0b6a 100644 --- a/internal/migration/up/up_test.go +++ b/internal/migration/up/up_test.go @@ -142,11 +142,12 @@ func TestIgnoreVersionMismatch(t *testing.T) { assert.ElementsMatch(t, []string{files[1], files[3]}, pending) }) - t.Run("throws error on missing local migration", func(t *testing.T) { + t.Run("throws error on missing local and remote migration", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() files := []string{ "20221201000000_test.sql", + "20221201000001_test.sql", "20221201000002_test.sql", "20221201000003_test.sql", } @@ -158,7 +159,41 @@ func TestIgnoreVersionMismatch(t *testing.T) { conn := pgtest.NewConn() defer conn.Close(t) conn.Query(list.LIST_MIGRATION_VERSION). - Reply("SELECT 1", []interface{}{"20221201000000"}, []interface{}{"20221201000001"}, []interface{}{"20221201000002"}, []interface{}{"20221201000003"}) + Reply("SELECT 2", []interface{}{"20221201000002"}, []interface{}{"20221201000004"}) + // Connect to mock + ctx := context.Background() + mock, err := utils.ConnectLocalPostgres(ctx, pgconn.Config{Port: 5432}, conn.Intercept) + require.NoError(t, err) + defer mock.Close(ctx) + // Run test + _, err = GetPendingMigrations(ctx, true, mock, fsys) + // Check error + assert.ErrorIs(t, err, errMissingLocal) + assert.Contains(t, utils.CmdSuggestion, "supabase migration repair --status reverted 20221201000004") + }) + + t.Run("throws error on missing local migration", func(t *testing.T) { + // Setup in-memory fs + fsys := afero.NewMemMapFs() + files := []string{ + "20221201000000_test.sql", + "20221201000002_test.sql", + } + for _, name := range files { + path := filepath.Join(utils.MigrationsDir, name) + require.NoError(t, afero.WriteFile(fsys, path, []byte(""), 0644)) + } + // Setup mock postgres + conn := pgtest.NewConn() + defer conn.Close(t) + conn.Query(list.LIST_MIGRATION_VERSION). + Reply("SELECT 5", + []interface{}{"20221201000000"}, + []interface{}{"20221201000001"}, + []interface{}{"20221201000002"}, + []interface{}{"20221201000003"}, + []interface{}{"20221201000004"}, + ) // Connect to mock ctx := context.Background() mock, err := utils.ConnectLocalPostgres(ctx, pgconn.Config{Port: 5432}, conn.Intercept) @@ -168,6 +203,6 @@ func TestIgnoreVersionMismatch(t *testing.T) { _, err = GetPendingMigrations(ctx, true, mock, fsys) // Check error assert.ErrorIs(t, err, errMissingLocal) - assert.Contains(t, utils.CmdSuggestion, "supabase migration repair --status reverted 20221201000001") + assert.Contains(t, utils.CmdSuggestion, "supabase migration repair --status reverted 20221201000001 20221201000003 20221201000004") }) } diff --git a/internal/projects/create/create.go b/internal/projects/create/create.go index bc651b414..c9a36b2f6 100644 --- a/internal/projects/create/create.go +++ b/internal/projects/create/create.go @@ -15,7 +15,7 @@ import ( "github.com/supabase/cli/pkg/api" ) -func Run(ctx context.Context, params api.CreateProjectBody, fsys afero.Fs) error { +func Run(ctx context.Context, params api.V1CreateProjectBody, fsys afero.Fs) error { if err := promptMissingParams(ctx, ¶ms); err != nil { return err } @@ -45,7 +45,7 @@ func printKeyValue(key, value string) string { return key + ":" + spaces + value } -func promptMissingParams(ctx context.Context, body *api.CreateProjectBody) error { +func promptMissingParams(ctx context.Context, body *api.V1CreateProjectBody) error { var err error if len(body.Name) == 0 { if body.Name, err = promptProjectName(); err != nil { @@ -100,7 +100,7 @@ func promptOrgId(ctx context.Context) (string, error) { return choice.Details, nil } -func promptProjectRegion(ctx context.Context) (api.CreateProjectBodyRegion, error) { +func promptProjectRegion(ctx context.Context) (api.V1CreateProjectBodyRegion, error) { title := "Which region do you want to host the project in?" items := make([]utils.PromptItem, len(utils.RegionMap)) i := 0 @@ -112,5 +112,5 @@ func promptProjectRegion(ctx context.Context) (api.CreateProjectBodyRegion, erro if err != nil { return "", err } - return api.CreateProjectBodyRegion(choice.Summary), nil + return api.V1CreateProjectBodyRegion(choice.Summary), nil } diff --git a/internal/projects/create/create_test.go b/internal/projects/create/create_test.go index 34f90fcb6..51ef30a76 100644 --- a/internal/projects/create/create_test.go +++ b/internal/projects/create/create_test.go @@ -14,11 +14,11 @@ import ( ) func TestProjectCreateCommand(t *testing.T) { - var params = api.CreateProjectBody{ + var params = api.V1CreateProjectBody{ Name: "Test Project", OrganizationId: "combined-fuchsia-lion", DbPass: "redacted", - Region: api.CreateProjectBodyRegionUsWest1, + Region: api.V1CreateProjectBodyRegionUsWest1, } t.Run("creates a new project", func(t *testing.T) { @@ -34,7 +34,7 @@ func TestProjectCreateCommand(t *testing.T) { MatchType("json"). JSON(params). Reply(201). - JSON(api.ProjectResponse{ + JSON(api.V1ProjectResponse{ Id: apitest.RandomProjectRef(), OrganizationId: params.OrganizationId, Name: params.Name, diff --git a/internal/projects/delete/delete_test.go b/internal/projects/delete/delete_test.go index abc681369..07a541081 100644 --- a/internal/projects/delete/delete_test.go +++ b/internal/projects/delete/delete_test.go @@ -33,7 +33,7 @@ func TestDeleteCommand(t *testing.T) { gock.New(utils.DefaultApiHost). Delete("/v1/projects/" + ref). Reply(http.StatusOK). - JSON(api.ProjectRefResponse{ + JSON(api.V1ProjectRefResponse{ Ref: ref, Name: "test-project", }) diff --git a/internal/projects/list/list_test.go b/internal/projects/list/list_test.go index 1fab7fe42..7f8043dd0 100644 --- a/internal/projects/list/list_test.go +++ b/internal/projects/list/list_test.go @@ -25,7 +25,7 @@ func TestProjectListCommand(t *testing.T) { gock.New(utils.DefaultApiHost). Get("/v1/projects"). Reply(200). - JSON([]api.ProjectResponse{ + JSON([]api.V1ProjectResponse{ { Id: apitest.RandomProjectRef(), OrganizationId: "combined-fuchsia-lion", diff --git a/internal/secrets/set/set.go b/internal/secrets/set/set.go index 517de12d5..b2ca0e245 100644 --- a/internal/secrets/set/set.go +++ b/internal/secrets/set/set.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/http" + "os" "strings" "github.com/go-errors/errors" @@ -19,11 +20,15 @@ func Run(ctx context.Context, projectRef, envFilePath string, args []string, fsy { var secrets api.CreateSecretsJSONBody if envFilePath != "" { - envMap, err := godotenv.Read(envFilePath) + envMap, err := ParseEnvFile(envFilePath, fsys) if err != nil { - return errors.Errorf("failed to read env file: %w", err) + return err } for name, value := range envMap { + if strings.HasPrefix(name, "SUPABASE_") { + fmt.Fprintln(os.Stderr, "Env name cannot start with SUPABASE_, skipping: "+name) + continue + } secret := api.CreateSecretBody{ Name: name, Value: value, @@ -61,3 +66,16 @@ func Run(ctx context.Context, projectRef, envFilePath string, args []string, fsy fmt.Println("Finished " + utils.Aqua("supabase secrets set") + ".") return nil } + +func ParseEnvFile(envFilePath string, fsys afero.Fs) (map[string]string, error) { + f, err := fsys.Open(envFilePath) + if err != nil { + return nil, errors.Errorf("failed to open env file: %w", err) + } + defer f.Close() + envMap, err := godotenv.Parse(f) + if err != nil { + return nil, errors.Errorf("failed to parse env file: %w", err) + } + return envMap, nil +} diff --git a/internal/secrets/set/set_test.go b/internal/secrets/set/set_test.go index 2f513b45f..480cf6e6e 100644 --- a/internal/secrets/set/set_test.go +++ b/internal/secrets/set/set_test.go @@ -3,7 +3,6 @@ package set import ( "context" "errors" - "os" "testing" "github.com/spf13/afero" @@ -44,17 +43,12 @@ func TestSecretSetCommand(t *testing.T) { t.Run("Sets secret value via env file", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() + require.NoError(t, afero.WriteFile(fsys, "/tmp/.env", []byte(dummyEnv), 0644)) // Setup valid project ref project := apitest.RandomProjectRef() // Setup valid access token token := apitest.RandomAccessToken(t) t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) - // Setup dotenv file - tmpfile, err := os.CreateTemp("", "secret") - require.NoError(t, err) - defer os.Remove(tmpfile.Name()) - _, err = tmpfile.Write([]byte(dummyEnv)) - require.NoError(t, err) // Flush pending mocks after test execution defer gock.OffAll() gock.New(utils.DefaultApiHost). @@ -63,7 +57,7 @@ func TestSecretSetCommand(t *testing.T) { JSON(api.CreateSecretsJSONBody{dummy}). Reply(200) // Run test - err = Run(context.Background(), project, tmpfile.Name(), []string{}, fsys) + err := Run(context.Background(), project, "/tmp/.env", []string{}, fsys) // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) diff --git a/internal/services/services.go b/internal/services/services.go index a6d451ede..626139f2e 100644 --- a/internal/services/services.go +++ b/internal/services/services.go @@ -58,31 +58,37 @@ func GetServiceImages() []string { } func GetRemoteImages(ctx context.Context, projectRef string) map[string]string { - const cap = 4 - linked := make(map[string]string, cap) + linked := make(map[string]string, 4) var wg sync.WaitGroup - wg.Add(cap) + wg.Add(1) go func() { defer wg.Done() if version, err := tenant.GetDatabaseVersion(ctx, projectRef); err == nil { linked[utils.Config.Db.Image] = version } }() + keys, err := tenant.GetApiKeys(ctx, projectRef) + if err != nil { + wg.Wait() + return linked + } + api := tenant.NewTenantAPI(ctx, projectRef, keys.Anon) + wg.Add(3) go func() { defer wg.Done() - if version, err := tenant.GetGotrueVersion(ctx, projectRef); err == nil { + if version, err := api.GetGotrueVersion(ctx); err == nil { linked[utils.Config.Auth.Image] = version } }() go func() { defer wg.Done() - if version, err := tenant.GetPostgrestVersion(ctx, projectRef); err == nil { + if version, err := api.GetPostgrestVersion(ctx); err == nil { linked[utils.Config.Api.Image] = version } }() go func() { defer wg.Done() - if version, err := tenant.GetStorageVersion(ctx, projectRef); err == nil { + if version, err := api.GetStorageVersion(ctx); err == nil { linked[utils.Config.Storage.Image] = version } }() diff --git a/internal/start/start.go b/internal/start/start.go index 09a14c350..331261196 100644 --- a/internal/start/start.go +++ b/internal/start/start.go @@ -103,7 +103,7 @@ type kongConfig struct { EdgeRuntimeId string LogflareId string ApiHost string - ApiPort uint + ApiPort uint16 } var ( @@ -497,6 +497,7 @@ EOF env, "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED=true", "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_URI="+utils.Config.Auth.Hook.MFAVerificationAttempt.URI, + "GOTRUE_HOOK_MFA_VERIFICATION_ATTEMPT_SECRETS="+utils.Config.Auth.Hook.MFAVerificationAttempt.Secrets, ) } @@ -505,6 +506,7 @@ EOF env, "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED=true", "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI="+utils.Config.Auth.Hook.PasswordVerificationAttempt.URI, + "GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_SECRETS="+utils.Config.Auth.Hook.PasswordVerificationAttempt.Secrets, ) } @@ -513,6 +515,25 @@ EOF env, "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_ENABLED=true", "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_URI="+utils.Config.Auth.Hook.CustomAccessToken.URI, + "GOTRUE_HOOK_CUSTOM_ACCESS_TOKEN_SECRETS="+utils.Config.Auth.Hook.CustomAccessToken.Secrets, + ) + } + + if utils.Config.Auth.Hook.SendSMS.Enabled { + env = append( + env, + "GOTRUE_HOOK_SEND_SMS_ENABLED=true", + "GOTRUE_HOOK_SEND_SMS_URI="+utils.Config.Auth.Hook.SendSMS.URI, + "GOTRUE_HOOK_SEND_SMS_SECRETS="+utils.Config.Auth.Hook.SendSMS.Secrets, + ) + } + + if utils.Config.Auth.Hook.SendEmail.Enabled { + env = append( + env, + "GOTRUE_HOOK_SEND_EMAIL_ENABLED=true", + "GOTRUE_HOOK_SEND_EMAIL_URI="+utils.Config.Auth.Hook.SendEmail.URI, + "GOTRUE_HOOK_SEND_EMAIL_SECRETS="+utils.Config.Auth.Hook.SendEmail.Secrets, ) } diff --git a/internal/start/start_test.go b/internal/start/start_test.go index 696cff663..843ed8d46 100644 --- a/internal/start/start_test.go +++ b/internal/start/start_test.go @@ -61,16 +61,6 @@ func TestStartCommand(t *testing.T) { // Setup mock docker require.NoError(t, apitest.MockDocker(utils.Docker)) defer gock.OffAll() - gock.New("http:///var/run/docker.sock"). - Head("/_ping"). - Reply(http.StatusOK). - SetHeader("API-Version", utils.Docker.ClientVersion()). - SetHeader("OSType", "linux") - gock.New(utils.Docker.DaemonHost()). - Get("/_ping"). - Reply(http.StatusOK). - SetHeader("API-Version", utils.Docker.ClientVersion()). - SetHeader("OSType", "linux") gock.New(utils.Docker.DaemonHost()). Get("/v" + utils.Docker.ClientVersion() + "/containers"). Reply(http.StatusOK). diff --git a/internal/storage/client/api.go b/internal/storage/client/api.go new file mode 100644 index 000000000..bd4c860c7 --- /dev/null +++ b/internal/storage/client/api.go @@ -0,0 +1,30 @@ +package client + +import ( + "context" + "fmt" + + "github.com/supabase/cli/internal/utils" + "github.com/supabase/cli/internal/utils/tenant" + "github.com/supabase/cli/pkg/fetcher" + "github.com/supabase/cli/pkg/storage" +) + +func NewStorageAPI(ctx context.Context, projectRef string) (storage.StorageAPI, error) { + server := fmt.Sprintf("http://%s:%d", utils.Config.Hostname, utils.Config.Api.Port) + token := utils.Config.Auth.ServiceRoleKey + if len(projectRef) > 0 { + server = "https://" + utils.GetSupabaseHost(projectRef) + apiKey, err := tenant.GetApiKeys(ctx, projectRef) + if err != nil { + return storage.StorageAPI{}, err + } + token = apiKey.ServiceRole + } + api := storage.StorageAPI{Fetcher: fetcher.NewFetcher( + server, + fetcher.WithBearerToken(token), + fetcher.WithUserAgent("SupabaseCLI/"+utils.Version), + )} + return api, nil +} diff --git a/internal/storage/scheme.go b/internal/storage/client/scheme.go similarity index 98% rename from internal/storage/scheme.go rename to internal/storage/client/scheme.go index 96f4e6cd7..768c29036 100644 --- a/internal/storage/scheme.go +++ b/internal/storage/client/scheme.go @@ -1,4 +1,4 @@ -package storage +package client import ( "net/url" diff --git a/internal/storage/scheme_test.go b/internal/storage/client/scheme_test.go similarity index 99% rename from internal/storage/scheme_test.go rename to internal/storage/client/scheme_test.go index 9427ffcb6..5da6191a0 100644 --- a/internal/storage/scheme_test.go +++ b/internal/storage/client/scheme_test.go @@ -1,4 +1,4 @@ -package storage +package client import ( "testing" diff --git a/internal/storage/cp/cp.go b/internal/storage/cp/cp.go index 09d6d5175..64890d699 100644 --- a/internal/storage/cp/cp.go +++ b/internal/storage/cp/cp.go @@ -12,16 +12,16 @@ import ( "github.com/go-errors/errors" "github.com/spf13/afero" - "github.com/supabase/cli/internal/storage" "github.com/supabase/cli/internal/storage/client" "github.com/supabase/cli/internal/storage/ls" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/internal/utils/flags" + "github.com/supabase/cli/pkg/storage" ) var errUnsupportedOperation = errors.New("Unsupported operation") -func Run(ctx context.Context, src, dst string, recursive bool, maxJobs uint, fsys afero.Fs, opts ...func(*client.FileOptions)) error { +func Run(ctx context.Context, src, dst string, recursive bool, maxJobs uint, fsys afero.Fs, opts ...func(*storage.FileOptions)) error { srcParsed, err := url.Parse(src) if err != nil { return errors.Errorf("failed to parse src url: %w", err) @@ -30,36 +30,36 @@ func Run(ctx context.Context, src, dst string, recursive bool, maxJobs uint, fsy if err != nil { return errors.Errorf("failed to parse dst url: %w", err) } - projectRef, err := flags.LoadProjectRef(fsys) + api, err := client.NewStorageAPI(ctx, flags.ProjectRef) if err != nil { return err } - if strings.ToLower(srcParsed.Scheme) == storage.STORAGE_SCHEME && dstParsed.Scheme == "" { + if strings.ToLower(srcParsed.Scheme) == client.STORAGE_SCHEME && dstParsed.Scheme == "" { localPath := dst if !filepath.IsAbs(dst) { localPath = filepath.Join(utils.CurrentDirAbs, dst) } if recursive { - return DownloadStorageObjectAll(ctx, projectRef, srcParsed.Path, localPath, maxJobs, fsys) + return DownloadStorageObjectAll(ctx, api, srcParsed.Path, localPath, maxJobs, fsys) } - return client.DownloadStorageObject(ctx, projectRef, srcParsed.Path, localPath, fsys) - } else if srcParsed.Scheme == "" && strings.ToLower(dstParsed.Scheme) == storage.STORAGE_SCHEME { + return api.DownloadObject(ctx, srcParsed.Path, localPath, fsys) + } else if srcParsed.Scheme == "" && strings.ToLower(dstParsed.Scheme) == client.STORAGE_SCHEME { localPath := src if !filepath.IsAbs(localPath) { localPath = filepath.Join(utils.CurrentDirAbs, localPath) } if recursive { - return UploadStorageObjectAll(ctx, projectRef, dstParsed.Path, localPath, maxJobs, fsys, opts...) + return UploadStorageObjectAll(ctx, api, dstParsed.Path, localPath, maxJobs, fsys, opts...) } - return client.UploadStorageObject(ctx, projectRef, dstParsed.Path, src, fsys, opts...) - } else if strings.ToLower(srcParsed.Scheme) == storage.STORAGE_SCHEME && strings.ToLower(dstParsed.Scheme) == storage.STORAGE_SCHEME { + return api.UploadObject(ctx, dstParsed.Path, src, fsys, opts...) + } else if strings.ToLower(srcParsed.Scheme) == client.STORAGE_SCHEME && strings.ToLower(dstParsed.Scheme) == client.STORAGE_SCHEME { return errors.New("Copying between buckets is not supported") } utils.CmdSuggestion = fmt.Sprintf("Run %s to copy between local directories.", utils.Aqua("cp -r ")) return errors.New(errUnsupportedOperation) } -func DownloadStorageObjectAll(ctx context.Context, projectRef, remotePath, localPath string, maxJobs uint, fsys afero.Fs) error { +func DownloadStorageObjectAll(ctx context.Context, api storage.StorageAPI, remotePath, localPath string, maxJobs uint, fsys afero.Fs) error { // Prepare local directory for download if fi, err := fsys.Stat(localPath); err == nil && fi.IsDir() { localPath = filepath.Join(localPath, path.Base(remotePath)) @@ -67,7 +67,7 @@ func DownloadStorageObjectAll(ctx context.Context, projectRef, remotePath, local // No need to be atomic because it's incremented only on main thread count := 0 jq := utils.NewJobQueue(maxJobs) - err := ls.IterateStoragePathsAll(ctx, projectRef, remotePath, func(objectPath string) error { + err := ls.IterateStoragePathsAll(ctx, api, remotePath, func(objectPath string) error { relPath := strings.TrimPrefix(objectPath, remotePath) dstPath := filepath.Join(localPath, filepath.FromSlash(relPath)) fmt.Fprintln(os.Stderr, "Downloading:", objectPath, "=>", dstPath) @@ -79,7 +79,7 @@ func DownloadStorageObjectAll(ctx context.Context, projectRef, remotePath, local if err := utils.MkdirIfNotExistFS(fsys, filepath.Dir(dstPath)); err != nil { return err } - return client.DownloadStorageObject(ctx, projectRef, objectPath, dstPath, fsys) + return api.DownloadObject(ctx, objectPath, dstPath, fsys) } return jq.Put(job) }) @@ -89,12 +89,12 @@ func DownloadStorageObjectAll(ctx context.Context, projectRef, remotePath, local return errors.Join(err, jq.Collect()) } -func UploadStorageObjectAll(ctx context.Context, projectRef, remotePath, localPath string, maxJobs uint, fsys afero.Fs, opts ...func(*client.FileOptions)) error { +func UploadStorageObjectAll(ctx context.Context, api storage.StorageAPI, remotePath, localPath string, maxJobs uint, fsys afero.Fs, opts ...func(*storage.FileOptions)) error { noSlash := strings.TrimSuffix(remotePath, "/") // Check if directory exists on remote dirExists := false fileExists := false - if err := ls.IterateStoragePaths(ctx, projectRef, noSlash, func(objectName string) error { + if err := ls.IterateStoragePaths(ctx, api, noSlash, func(objectName string) error { if objectName == path.Base(noSlash) { fileExists = true } @@ -121,7 +121,7 @@ func UploadStorageObjectAll(ctx context.Context, projectRef, remotePath, localPa dstPath := remotePath // Copying single file if relPath == "." { - _, prefix := storage.SplitBucketPrefix(dstPath) + _, prefix := client.SplitBucketPrefix(dstPath) if IsDir(prefix) || (dirExists && !fileExists) { dstPath = path.Join(dstPath, info.Name()) } @@ -133,14 +133,14 @@ func UploadStorageObjectAll(ctx context.Context, projectRef, remotePath, localPa } fmt.Fprintln(os.Stderr, "Uploading:", filePath, "=>", dstPath) job := func() error { - err := client.UploadStorageObject(ctx, projectRef, dstPath, filePath, fsys, opts...) + err := api.UploadObject(ctx, dstPath, filePath, fsys, opts...) if err != nil && strings.Contains(err.Error(), `"error":"Bucket not found"`) { // Retry after creating bucket - if bucket, prefix := storage.SplitBucketPrefix(dstPath); len(prefix) > 0 { - if _, err := client.CreateStorageBucket(ctx, projectRef, bucket); err != nil { + if bucket, prefix := client.SplitBucketPrefix(dstPath); len(prefix) > 0 { + if _, err := api.CreateBucket(ctx, bucket); err != nil { return err } - err = client.UploadStorageObject(ctx, projectRef, dstPath, filePath, fsys, opts...) + err = api.UploadObject(ctx, dstPath, filePath, fsys, opts...) } } return err diff --git a/internal/storage/cp/cp_test.go b/internal/storage/cp/cp_test.go index de536b1b1..371394eca 100644 --- a/internal/storage/cp/cp_test.go +++ b/internal/storage/cp/cp_test.go @@ -9,20 +9,22 @@ import ( "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/supabase/cli/internal/storage/client" "github.com/supabase/cli/internal/testing/apitest" "github.com/supabase/cli/internal/utils" + "github.com/supabase/cli/internal/utils/flags" "github.com/supabase/cli/pkg/api" + "github.com/supabase/cli/pkg/fetcher" + "github.com/supabase/cli/pkg/storage" "gopkg.in/h2non/gock.v1" ) -var mockFile = client.ObjectResponse{ +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"), - Metadata: &client.ObjectMetadata{ + Metadata: &storage.ObjectMetadata{ ETag: `"887ea9be3c68e6f2fca7fd2d7c77d8fe"`, Size: 82702, Mimetype: "application/pdf", @@ -33,29 +35,34 @@ var mockFile = client.ObjectResponse{ }, } +var mockApi = storage.StorageAPI{Fetcher: fetcher.NewFetcher( + "http://127.0.0.1", +)} + func TestStorageCP(t *testing.T) { + flags.ProjectRef = apitest.RandomProjectRef() + // Setup valid access token + token := apitest.RandomAccessToken(t) + t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) + t.Run("copy local to remote", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() - projectRef := apitest.RandomProjectRef() - require.NoError(t, afero.WriteFile(fsys, utils.ProjectRefPath, []byte(projectRef), 0644)) - // Setup valid access token - token := apitest.RandomAccessToken(t) - t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) + require.NoError(t, afero.WriteFile(fsys, "/tmp/file", []byte{}, 0644)) // Setup mock api defer gock.OffAll() gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). + Get("/v1/projects/" + flags.ProjectRef + "/api-keys"). Reply(http.StatusOK). JSON([]api.ApiKeyResponse{{ Name: "service_role", ApiKey: "service-key", }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). Post("/storage/v1/object/private/file"). Reply(http.StatusOK) // Run test - err := Run(context.Background(), utils.ProjectRefPath, "ss:///private/file", false, 1, fsys) + err := Run(context.Background(), "/tmp/file", "ss:///private/file", false, 1, fsys) // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -64,21 +71,19 @@ func TestStorageCP(t *testing.T) { t.Run("throws error on missing file", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() - projectRef := apitest.RandomProjectRef() - require.NoError(t, afero.WriteFile(fsys, utils.ProjectRefPath, []byte(projectRef), 0644)) // Setup mock api defer gock.OffAll() gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). + Get("/v1/projects/" + flags.ProjectRef + "/api-keys"). Reply(http.StatusOK). JSON([]api.ApiKeyResponse{{ Name: "service_role", ApiKey: "service-key", }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). Get("/storage/v1/bucket"). Reply(http.StatusOK). - JSON([]client.BucketResponse{}) + JSON([]storage.BucketResponse{}) // Run test err := Run(context.Background(), "abstract.pdf", "ss:///private", true, 1, fsys) // Check error @@ -89,21 +94,16 @@ func TestStorageCP(t *testing.T) { t.Run("copy remote to local", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() - projectRef := apitest.RandomProjectRef() - require.NoError(t, afero.WriteFile(fsys, utils.ProjectRefPath, []byte(projectRef), 0644)) - // Setup valid access token - token := apitest.RandomAccessToken(t) - t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) // Setup mock api defer gock.OffAll() gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). + Get("/v1/projects/" + flags.ProjectRef + "/api-keys"). Reply(http.StatusOK). JSON([]api.ApiKeyResponse{{ Name: "service_role", ApiKey: "service-key", }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). Get("/storage/v1/object/private/file"). Reply(http.StatusOK) // Run test @@ -119,21 +119,19 @@ func TestStorageCP(t *testing.T) { t.Run("throws error on missing bucket", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() - projectRef := apitest.RandomProjectRef() - require.NoError(t, afero.WriteFile(fsys, utils.ProjectRefPath, []byte(projectRef), 0644)) // Setup mock api defer gock.OffAll() gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). + Get("/v1/projects/" + flags.ProjectRef + "/api-keys"). Reply(http.StatusOK). JSON([]api.ApiKeyResponse{{ Name: "service_role", ApiKey: "service-key", }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). Get("/storage/v1/bucket"). Reply(http.StatusOK). - JSON([]client.BucketResponse{}) + JSON([]storage.BucketResponse{}) // Run test err := Run(context.Background(), "ss:///private", ".", true, 1, fsys) // Check error @@ -159,20 +157,18 @@ func TestStorageCP(t *testing.T) { assert.ErrorContains(t, err, "missing protocol scheme") }) - t.Run("throws error on missing project", func(t *testing.T) { - // Setup in-memory fs - fsys := afero.NewMemMapFs() - // Run test - err := Run(context.Background(), ".", ".", false, 1, fsys) - // Check error - assert.ErrorIs(t, err, utils.ErrNotLinked) - }) - t.Run("throws error on unsupported operation", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() - projectRef := apitest.RandomProjectRef() - require.NoError(t, afero.WriteFile(fsys, utils.ProjectRefPath, []byte(projectRef), 0644)) + // Setup mock api + defer gock.OffAll() + gock.New(utils.DefaultApiHost). + Get("/v1/projects/" + flags.ProjectRef + "/api-keys"). + Reply(http.StatusOK). + JSON([]api.ApiKeyResponse{{ + Name: "service_role", + ApiKey: "service-key", + }}) // Run test err := Run(context.Background(), ".", ".", false, 1, fsys) // Check error @@ -181,42 +177,29 @@ func TestStorageCP(t *testing.T) { } func TestUploadAll(t *testing.T) { - // Setup valid project ref - projectRef := apitest.RandomProjectRef() - // Setup valid access token - token := apitest.RandomAccessToken(t) - t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) - t.Run("uploads directory to new bucket", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() require.NoError(t, afero.WriteFile(fsys, "/tmp/readme.md", []byte{}, 0644)) // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Get("/storage/v1/bucket"). Reply(http.StatusOK). - JSON([]client.BucketResponse{}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + JSON([]storage.BucketResponse{}) + gock.New("http://127.0.0.1"). Post("/storage/v1/object/tmp/readme.md"). Reply(http.StatusNotFound). JSON(map[string]string{"error": "Bucket not found"}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/bucket"). Reply(http.StatusOK). - JSON(client.CreateBucketResponse{Name: "tmp"}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + JSON(storage.CreateBucketResponse{Name: "tmp"}) + gock.New("http://127.0.0.1"). Post("/storage/v1/object/tmp/readme.md"). Reply(http.StatusOK) // Run test - err := UploadStorageObjectAll(context.Background(), projectRef, "", "/tmp", 1, fsys) + err := UploadStorageObjectAll(context.Background(), mockApi, "", "/tmp", 1, fsys) // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -228,26 +211,19 @@ func TestUploadAll(t *testing.T) { require.NoError(t, afero.WriteFile(fsys, "/tmp/readme.md", []byte{}, 0644)) // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Get("/storage/v1/bucket"). Reply(http.StatusOK). - JSON([]client.BucketResponse{}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + JSON([]storage.BucketResponse{}) + gock.New("http://127.0.0.1"). Post("/storage/v1/object/tmp/readme.md"). Reply(http.StatusNotFound). JSON(map[string]string{"error": "Bucket not found"}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/bucket"). Reply(http.StatusServiceUnavailable) // Run test - err := UploadStorageObjectAll(context.Background(), projectRef, "", "/tmp", 1, fsys) + err := UploadStorageObjectAll(context.Background(), mockApi, "", "/tmp", 1, fsys) // Check error assert.ErrorContains(t, err, "Error status 503:") assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -260,27 +236,20 @@ func TestUploadAll(t *testing.T) { require.NoError(t, afero.WriteFile(fsys, "/tmp/docs/api.md", []byte{}, 0644)) // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). Reply(http.StatusOK). - JSON([]client.ObjectResponse{{ + JSON([]storage.ObjectResponse{{ Name: "dir", }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/private/dir/tmp/readme.md"). Reply(http.StatusOK) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/private/dir/tmp/docs/api.md"). Reply(http.StatusOK) // Run test - err := UploadStorageObjectAll(context.Background(), projectRef, "/private/dir/", "/tmp", 1, fsys) + err := UploadStorageObjectAll(context.Background(), mockApi, "/private/dir/", "/tmp", 1, fsys) // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -292,27 +261,20 @@ func TestUploadAll(t *testing.T) { require.NoError(t, afero.WriteFile(fsys, "/tmp/readme.md", []byte{}, 0644)) // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Get("/storage/v1/bucket"). Reply(http.StatusOK). - JSON([]client.BucketResponse{{ + JSON([]storage.BucketResponse{{ Id: "private", Name: "private", CreatedAt: "2023-10-13T17:48:58.491Z", UpdatedAt: "2023-10-13T17:48:58.491Z", }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/private/readme.md"). Reply(http.StatusOK) // Run test - err := UploadStorageObjectAll(context.Background(), projectRef, "private", "/tmp/readme.md", 1, fsys) + err := UploadStorageObjectAll(context.Background(), mockApi, "private", "/tmp/readme.md", 1, fsys) // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -324,24 +286,17 @@ func TestUploadAll(t *testing.T) { require.NoError(t, afero.WriteFile(fsys, "/tmp/readme.md", []byte{}, 0644)) // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) fileObject := mockFile fileObject.Name = "file" - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). Reply(http.StatusOK). - JSON([]client.ObjectResponse{fileObject}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + JSON([]storage.ObjectResponse{fileObject}) + gock.New("http://127.0.0.1"). Post("/storage/v1/object/private/file"). Reply(http.StatusOK) // Run test - err := UploadStorageObjectAll(context.Background(), projectRef, "private/file", "/tmp/readme.md", 1, fsys) + err := UploadStorageObjectAll(context.Background(), mockApi, "private/file", "/tmp/readme.md", 1, fsys) // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -352,18 +307,11 @@ func TestUploadAll(t *testing.T) { fsys := afero.NewMemMapFs() // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Get("/storage/v1/bucket"). Reply(http.StatusServiceUnavailable) // Run test - err := UploadStorageObjectAll(context.Background(), projectRef, "", ".", 1, fsys) + err := UploadStorageObjectAll(context.Background(), mockApi, "", ".", 1, fsys) // Check error assert.ErrorContains(t, err, "Error status 503:") assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -371,28 +319,15 @@ func TestUploadAll(t *testing.T) { } func TestDownloadAll(t *testing.T) { - // Setup valid project ref - projectRef := apitest.RandomProjectRef() - // Setup valid access token - token := apitest.RandomAccessToken(t) - t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) - t.Run("downloads buckets to existing directory", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Get("/storage/v1/bucket"). Reply(http.StatusOK). - JSON([]client.BucketResponse{{ + JSON([]storage.BucketResponse{{ Id: "test", Name: "test", Public: true, @@ -404,16 +339,16 @@ func TestDownloadAll(t *testing.T) { CreatedAt: "2023-10-13T17:48:58.491Z", UpdatedAt: "2023-10-13T17:48:58.491Z", }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). Reply(http.StatusOK). - JSON([]client.ObjectResponse{}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + JSON([]storage.ObjectResponse{}) + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/test"). Reply(http.StatusOK). - JSON([]client.ObjectResponse{}) + JSON([]storage.ObjectResponse{}) // Run test - err := DownloadStorageObjectAll(context.Background(), projectRef, "", "/", 1, fsys) + err := DownloadStorageObjectAll(context.Background(), mockApi, "", "/", 1, fsys) // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -430,32 +365,25 @@ func TestDownloadAll(t *testing.T) { fsys := afero.NewMemMapFs() // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Get("/storage/v1/object/private"). Reply(http.StatusNotFound). JSON(map[string]string{"error": "Not Found"}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Get("/storage/v1/bucket"). Reply(http.StatusOK). - JSON([]client.BucketResponse{{ + JSON([]storage.BucketResponse{{ Id: "private", Name: "private", CreatedAt: "2023-10-13T17:48:58.491Z", UpdatedAt: "2023-10-13T17:48:58.491Z", }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). Reply(http.StatusOK). - JSON([]client.ObjectResponse{}) + JSON([]storage.ObjectResponse{}) // Run test - err := DownloadStorageObjectAll(context.Background(), projectRef, "/private", "/tmp", 1, fsys) + err := DownloadStorageObjectAll(context.Background(), mockApi, "/private", "/tmp", 1, fsys) // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -472,19 +400,12 @@ func TestDownloadAll(t *testing.T) { fsys := afero.NewMemMapFs() // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). Reply(http.StatusOK). - JSON([]client.ObjectResponse{}) + JSON([]storage.ObjectResponse{}) // Run test - err := DownloadStorageObjectAll(context.Background(), projectRef, "private/dir/", "/", 1, fsys) + err := DownloadStorageObjectAll(context.Background(), mockApi, "private/dir/", "/", 1, fsys) // Check error assert.ErrorContains(t, err, "Object not found: private/dir/") assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -498,47 +419,40 @@ func TestDownloadAll(t *testing.T) { fsys := afero.NewMemMapFs() // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) // Lists /private/tmp directory - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). - JSON(client.ListObjectsQuery{ + JSON(storage.ListObjectsQuery{ Prefix: "tmp/", Search: "", - Limit: client.PAGE_LIMIT, + Limit: storage.PAGE_LIMIT, Offset: 0, }). Reply(http.StatusOK). - JSON([]client.ObjectResponse{{ + JSON([]storage.ObjectResponse{{ Name: "docs", }, mockFile}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Get("/storage/v1/object/private/tmp/abstract.pdf"). Reply(http.StatusOK) // Lists /private/tmp/docs directory readme := mockFile readme.Name = "readme.md" - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). - JSON(client.ListObjectsQuery{ + JSON(storage.ListObjectsQuery{ Prefix: "tmp/docs/", Search: "", - Limit: client.PAGE_LIMIT, + Limit: storage.PAGE_LIMIT, Offset: 0, }). Reply(http.StatusOK). - JSON([]client.ObjectResponse{readme}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + JSON([]storage.ObjectResponse{readme}) + gock.New("http://127.0.0.1"). Get("/storage/v1/object/private/tmp/docs/readme.md"). Reply(http.StatusOK) // Run test - err := DownloadStorageObjectAll(context.Background(), projectRef, "private/tmp/", "/", 1, fsys) + err := DownloadStorageObjectAll(context.Background(), mockApi, "private/tmp/", "/", 1, fsys) // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -555,22 +469,15 @@ func TestDownloadAll(t *testing.T) { fsys := afero.NewMemMapFs() // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). Reply(http.StatusOK). - JSON([]client.ObjectResponse{mockFile}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + JSON([]storage.ObjectResponse{mockFile}) + gock.New("http://127.0.0.1"). Get("/storage/v1/object/private/abstract.pdf"). Reply(http.StatusOK) // Run test - err := DownloadStorageObjectAll(context.Background(), projectRef, "/private/abstract.pdf", "/tmp/file", 1, fsys) + err := DownloadStorageObjectAll(context.Background(), mockApi, "/private/abstract.pdf", "/tmp/file", 1, fsys) // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) diff --git a/internal/storage/ls/ls.go b/internal/storage/ls/ls.go index 2cb9f974f..396dc9b7c 100644 --- a/internal/storage/ls/ls.go +++ b/internal/storage/ls/ls.go @@ -8,17 +8,13 @@ import ( "strings" "github.com/spf13/afero" - "github.com/supabase/cli/internal/storage" "github.com/supabase/cli/internal/storage/client" "github.com/supabase/cli/internal/utils/flags" + "github.com/supabase/cli/pkg/storage" ) func Run(ctx context.Context, objectPath string, recursive bool, fsys afero.Fs) error { - remotePath, err := storage.ParseStorageURL(objectPath) - if err != nil { - return err - } - projectRef, err := flags.LoadProjectRef(fsys) + remotePath, err := client.ParseStorageURL(objectPath) if err != nil { return err } @@ -26,25 +22,29 @@ func Run(ctx context.Context, objectPath string, recursive bool, fsys afero.Fs) fmt.Println(objectPath) return nil } + api, err := client.NewStorageAPI(ctx, flags.ProjectRef) + if err != nil { + return err + } if recursive { - return IterateStoragePathsAll(ctx, projectRef, remotePath, callback) + return IterateStoragePathsAll(ctx, api, remotePath, callback) } - return IterateStoragePaths(ctx, projectRef, remotePath, callback) + return IterateStoragePaths(ctx, api, remotePath, callback) } -func ListStoragePaths(ctx context.Context, projectRef, remotePath string) ([]string, error) { +func ListStoragePaths(ctx context.Context, api storage.StorageAPI, remotePath string) ([]string, error) { var result []string - err := IterateStoragePaths(ctx, projectRef, remotePath, func(objectName string) error { + err := IterateStoragePaths(ctx, api, remotePath, func(objectName string) error { result = append(result, objectName) return nil }) return result, err } -func IterateStoragePaths(ctx context.Context, projectRef, remotePath string, callback func(objectName string) error) error { - bucket, prefix := storage.SplitBucketPrefix(remotePath) +func IterateStoragePaths(ctx context.Context, api storage.StorageAPI, remotePath string, callback func(objectName string) error) error { + bucket, prefix := client.SplitBucketPrefix(remotePath) if len(bucket) == 0 || (len(prefix) == 0 && !strings.HasSuffix(remotePath, "/")) { - buckets, err := client.ListStorageBuckets(ctx, projectRef) + buckets, err := api.ListBuckets(ctx) if err != nil { return err } @@ -58,7 +58,7 @@ func IterateStoragePaths(ctx context.Context, projectRef, remotePath string, cal } else { pages := 1 for i := 0; i < pages; i++ { - objects, err := client.ListStorageObjects(ctx, projectRef, bucket, prefix, i) + objects, err := api.ListObjects(ctx, bucket, prefix, i) if err != nil { return err } @@ -71,7 +71,7 @@ func IterateStoragePaths(ctx context.Context, projectRef, remotePath string, cal return err } } - if len(objects) == client.PAGE_LIMIT { + if len(objects) == storage.PAGE_LIMIT { // TODO: show interactive prompt? fmt.Fprintln(os.Stderr, "Loading page:", pages) pages++ @@ -82,16 +82,16 @@ func IterateStoragePaths(ctx context.Context, projectRef, remotePath string, cal } // Expects remotePath to be terminated by "/" -func ListStoragePathsAll(ctx context.Context, projectRef, remotePath string) ([]string, error) { +func ListStoragePathsAll(ctx context.Context, api storage.StorageAPI, remotePath string) ([]string, error) { var result []string - err := IterateStoragePathsAll(ctx, projectRef, remotePath, func(objectPath string) error { + err := IterateStoragePathsAll(ctx, api, remotePath, func(objectPath string) error { result = append(result, objectPath) return nil }) return result, err } -func IterateStoragePathsAll(ctx context.Context, projectRef, remotePath string, callback func(objectPath string) error) error { +func IterateStoragePathsAll(ctx context.Context, api storage.StorageAPI, remotePath string, callback func(objectPath string) error) error { basePath := remotePath if !strings.HasSuffix(remotePath, "/") { basePath, _ = path.Split(remotePath) @@ -99,7 +99,7 @@ func IterateStoragePathsAll(ctx context.Context, projectRef, remotePath string, // BFS so we can list paths in increasing depth dirQueue := make([]string, 0) // We don't know if user passed in a directory or file, so query storage first. - if err := IterateStoragePaths(ctx, projectRef, remotePath, func(objectName string) error { + if err := IterateStoragePaths(ctx, api, remotePath, func(objectName string) error { objectPath := basePath + objectName if strings.HasSuffix(objectName, "/") { dirQueue = append(dirQueue, objectPath) @@ -113,7 +113,7 @@ func IterateStoragePathsAll(ctx context.Context, projectRef, remotePath string, dirPath := dirQueue[len(dirQueue)-1] dirQueue = dirQueue[:len(dirQueue)-1] empty := true - if err := IterateStoragePaths(ctx, projectRef, dirPath, func(objectName string) error { + if err := IterateStoragePaths(ctx, api, dirPath, func(objectName string) error { empty = false objectPath := dirPath + objectName if strings.HasSuffix(objectName, "/") { @@ -125,7 +125,7 @@ func IterateStoragePathsAll(ctx context.Context, projectRef, remotePath string, return err } // Also report empty buckets - bucket, prefix := storage.SplitBucketPrefix(dirPath) + bucket, prefix := client.SplitBucketPrefix(dirPath) if empty && len(prefix) == 0 { if err := callback(bucket + "/"); err != nil { return err diff --git a/internal/storage/ls/ls_test.go b/internal/storage/ls/ls_test.go index cae6542d3..69d8bad9d 100644 --- a/internal/storage/ls/ls_test.go +++ b/internal/storage/ls/ls_test.go @@ -8,22 +8,23 @@ import ( "github.com/spf13/afero" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/supabase/cli/internal/storage" "github.com/supabase/cli/internal/storage/client" "github.com/supabase/cli/internal/testing/apitest" "github.com/supabase/cli/internal/utils" + "github.com/supabase/cli/internal/utils/flags" "github.com/supabase/cli/pkg/api" + "github.com/supabase/cli/pkg/fetcher" + "github.com/supabase/cli/pkg/storage" "gopkg.in/h2non/gock.v1" ) -var mockFile = client.ObjectResponse{ +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"), - Metadata: &client.ObjectMetadata{ + Metadata: &storage.ObjectMetadata{ ETag: `"887ea9be3c68e6f2fca7fd2d7c77d8fe"`, Size: 82702, Mimetype: "application/pdf", @@ -34,28 +35,32 @@ var mockFile = client.ObjectResponse{ }, } +var mockApi = storage.StorageAPI{Fetcher: fetcher.NewFetcher( + "http://127.0.0.1", +)} + func TestStorageLS(t *testing.T) { + flags.ProjectRef = apitest.RandomProjectRef() + // Setup valid access token + token := apitest.RandomAccessToken(t) + t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) + t.Run("lists buckets", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() - projectRef := apitest.RandomProjectRef() - require.NoError(t, afero.WriteFile(fsys, utils.ProjectRefPath, []byte(projectRef), 0644)) - // Setup valid access token - token := apitest.RandomAccessToken(t) - t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) // Setup mock api defer gock.OffAll() gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). + Get("/v1/projects/" + flags.ProjectRef + "/api-keys"). Reply(http.StatusOK). JSON([]api.ApiKeyResponse{{ Name: "service_role", ApiKey: "service-key", }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). Get("/storage/v1/bucket"). Reply(http.StatusOK). - JSON([]client.BucketResponse{}) + JSON([]storage.BucketResponse{}) // Run test err := Run(context.Background(), "ss:///", false, fsys) // Check error @@ -68,48 +73,34 @@ func TestStorageLS(t *testing.T) { // Run test err := Run(context.Background(), "", false, fsys) // Check error - assert.ErrorIs(t, err, storage.ErrInvalidURL) - }) - - t.Run("throws error on invalid project", func(t *testing.T) { - // Setup in-memory fs - fsys := afero.NewMemMapFs() - // Run test - err := Run(context.Background(), "ss:///", false, fsys) - // Check error - assert.ErrorIs(t, err, utils.ErrNotLinked) + assert.ErrorIs(t, err, client.ErrInvalidURL) }) t.Run("lists objects recursive", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() - projectRef := apitest.RandomProjectRef() - require.NoError(t, afero.WriteFile(fsys, utils.ProjectRefPath, []byte(projectRef), 0644)) - // Setup valid access token - token := apitest.RandomAccessToken(t) - t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) // Setup mock api defer gock.OffAll() gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). + Get("/v1/projects/" + flags.ProjectRef + "/api-keys"). Reply(http.StatusOK). JSON([]api.ApiKeyResponse{{ Name: "service_role", ApiKey: "service-key", }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). Get("/storage/v1/bucket"). Reply(http.StatusOK). - JSON([]client.BucketResponse{{ + JSON([]storage.BucketResponse{{ Id: "private", Name: "private", CreatedAt: "2023-10-13T17:48:58.491Z", UpdatedAt: "2023-10-13T17:48:58.491Z", }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). Post("/storage/v1/object/list/private"). Reply(http.StatusOK). - JSON([]client.ObjectResponse{}) + JSON([]storage.ObjectResponse{}) // Run test err := Run(context.Background(), "ss:///", true, fsys) // Check error @@ -118,26 +109,13 @@ func TestStorageLS(t *testing.T) { } func TestListStoragePaths(t *testing.T) { - // Setup valid project ref - projectRef := apitest.RandomProjectRef() - // Setup valid access token - token := apitest.RandomAccessToken(t) - t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) - t.Run("lists bucket paths by prefix", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Get("/storage/v1/bucket"). Reply(http.StatusOK). - JSON([]client.BucketResponse{{ + JSON([]storage.BucketResponse{{ Id: "test", Name: "test", Public: true, @@ -150,7 +128,7 @@ func TestListStoragePaths(t *testing.T) { UpdatedAt: "2023-10-13T17:48:58.491Z", }}) // Run test - paths, err := ListStoragePaths(context.Background(), projectRef, "te") + paths, err := ListStoragePaths(context.Background(), mockApi, "te") // Check error assert.NoError(t, err) assert.ElementsMatch(t, []string{"test/"}, paths) @@ -160,18 +138,11 @@ func TestListStoragePaths(t *testing.T) { t.Run("throws error on bucket service unavailable", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Get("/storage/v1/bucket"). Reply(http.StatusServiceUnavailable) // Run test - paths, err := ListStoragePaths(context.Background(), projectRef, "/") + paths, err := ListStoragePaths(context.Background(), mockApi, "/") // Check error assert.ErrorContains(t, err, "Error status 503:") assert.Empty(t, paths) @@ -181,21 +152,14 @@ func TestListStoragePaths(t *testing.T) { t.Run("lists object paths by prefix", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/bucket"). Reply(http.StatusOK). - JSON([]client.ObjectResponse{{ + JSON([]storage.ObjectResponse{{ Name: "folder", }, mockFile}) // Run test - paths, err := ListStoragePaths(context.Background(), projectRef, "bucket/") + paths, err := ListStoragePaths(context.Background(), mockApi, "bucket/") // Check error assert.NoError(t, err) assert.ElementsMatch(t, []string{"folder/", "abstract.pdf"}, paths) @@ -205,18 +169,11 @@ func TestListStoragePaths(t *testing.T) { t.Run("throws error on object service unavailable", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/bucket"). Reply(http.StatusServiceUnavailable) // Run test - paths, err := ListStoragePaths(context.Background(), projectRef, "bucket/") + paths, err := ListStoragePaths(context.Background(), mockApi, "bucket/") // Check error assert.ErrorContains(t, err, "Error status 503:") assert.Empty(t, paths) @@ -226,41 +183,34 @@ func TestListStoragePaths(t *testing.T) { t.Run("lists object paths with pagination", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) - expected := make([]string, client.PAGE_LIMIT) - resp := make([]client.ObjectResponse, client.PAGE_LIMIT) + expected := make([]string, storage.PAGE_LIMIT) + resp := make([]storage.ObjectResponse, storage.PAGE_LIMIT) for i := 0; i < len(resp); i++ { - resp[i] = client.ObjectResponse{Name: fmt.Sprintf("dir_%d", i)} + resp[i] = storage.ObjectResponse{Name: fmt.Sprintf("dir_%d", i)} expected[i] = resp[i].Name + "/" } - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/bucket"). - JSON(client.ListObjectsQuery{ + JSON(storage.ListObjectsQuery{ Prefix: "", Search: "dir", - Limit: client.PAGE_LIMIT, + Limit: storage.PAGE_LIMIT, Offset: 0, }). Reply(http.StatusOK). JSON(resp) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/bucket"). - JSON(client.ListObjectsQuery{ + JSON(storage.ListObjectsQuery{ Prefix: "", Search: "dir", - Limit: client.PAGE_LIMIT, - Offset: client.PAGE_LIMIT, + Limit: storage.PAGE_LIMIT, + Offset: storage.PAGE_LIMIT, }). Reply(http.StatusOK). - JSON([]client.ObjectResponse{}) + JSON([]storage.ObjectResponse{}) // Run test - paths, err := ListStoragePaths(context.Background(), projectRef, "/bucket/dir") + paths, err := ListStoragePaths(context.Background(), mockApi, "/bucket/dir") // Check error assert.NoError(t, err) assert.ElementsMatch(t, expected, paths) @@ -269,27 +219,14 @@ func TestListStoragePaths(t *testing.T) { } func TestListStoragePathsAll(t *testing.T) { - // Setup valid project ref - projectRef := apitest.RandomProjectRef() - // Setup valid access token - token := apitest.RandomAccessToken(t) - t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) - t.Run("lists nested object paths", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) // List buckets - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Get("/storage/v1/bucket"). Reply(http.StatusOK). - JSON([]client.BucketResponse{{ + JSON([]storage.BucketResponse{{ Id: "test", Name: "test", Public: true, @@ -302,41 +239,41 @@ func TestListStoragePathsAll(t *testing.T) { UpdatedAt: "2023-10-13T17:48:58.491Z", }}) // List folders - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/test"). - JSON(client.ListObjectsQuery{ + JSON(storage.ListObjectsQuery{ Prefix: "", Search: "", - Limit: client.PAGE_LIMIT, + Limit: storage.PAGE_LIMIT, Offset: 0, }). Reply(http.StatusOK). - JSON([]client.ObjectResponse{}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + JSON([]storage.ObjectResponse{}) + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). - JSON(client.ListObjectsQuery{ + JSON(storage.ListObjectsQuery{ Prefix: "", Search: "", - Limit: client.PAGE_LIMIT, + Limit: storage.PAGE_LIMIT, Offset: 0, }). Reply(http.StatusOK). - JSON([]client.ObjectResponse{{ + JSON([]storage.ObjectResponse{{ Name: "folder", }}) // List files - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). - JSON(client.ListObjectsQuery{ + JSON(storage.ListObjectsQuery{ Prefix: "folder/", Search: "", - Limit: client.PAGE_LIMIT, + Limit: storage.PAGE_LIMIT, Offset: 0, }). Reply(http.StatusOK). - JSON([]client.ObjectResponse{mockFile}) + JSON([]storage.ObjectResponse{mockFile}) // Run test - paths, err := ListStoragePathsAll(context.Background(), projectRef, "") + paths, err := ListStoragePathsAll(context.Background(), mockApi, "") // Check error assert.NoError(t, err) assert.ElementsMatch(t, []string{"private/folder/abstract.pdf", "test/"}, paths) @@ -346,47 +283,40 @@ func TestListStoragePathsAll(t *testing.T) { t.Run("returns partial result on error", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) // List folders - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). - JSON(client.ListObjectsQuery{ + JSON(storage.ListObjectsQuery{ Prefix: "", Search: "", - Limit: client.PAGE_LIMIT, + Limit: storage.PAGE_LIMIT, Offset: 0, }). Reply(http.StatusOK). - JSON([]client.ObjectResponse{{ + JSON([]storage.ObjectResponse{{ Name: "error", }, mockFile}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). - JSON(client.ListObjectsQuery{ + JSON(storage.ListObjectsQuery{ Prefix: "empty/", Search: "", - Limit: client.PAGE_LIMIT, + Limit: storage.PAGE_LIMIT, Offset: 0, }). Reply(http.StatusOK). - JSON([]client.ObjectResponse{}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + JSON([]storage.ObjectResponse{}) + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). - JSON(client.ListObjectsQuery{ + JSON(storage.ListObjectsQuery{ Prefix: "error/", Search: "", - Limit: client.PAGE_LIMIT, + Limit: storage.PAGE_LIMIT, Offset: 0, }). Reply(http.StatusServiceUnavailable) // Run test - paths, err := ListStoragePathsAll(context.Background(), projectRef, "private/") + paths, err := ListStoragePathsAll(context.Background(), mockApi, "private/") // Check error assert.ErrorContains(t, err, "Error status 503:") assert.ElementsMatch(t, []string{"private/abstract.pdf"}, paths) diff --git a/internal/storage/mv/mv.go b/internal/storage/mv/mv.go index 44d61b2f2..a6dc8773c 100644 --- a/internal/storage/mv/mv.go +++ b/internal/storage/mv/mv.go @@ -9,10 +9,10 @@ import ( "github.com/go-errors/errors" "github.com/spf13/afero" - "github.com/supabase/cli/internal/storage" "github.com/supabase/cli/internal/storage/client" "github.com/supabase/cli/internal/storage/ls" "github.com/supabase/cli/internal/utils/flags" + "github.com/supabase/cli/pkg/storage" ) var ( @@ -21,39 +21,39 @@ var ( ) func Run(ctx context.Context, src, dst string, recursive bool, fsys afero.Fs) error { - srcParsed, err := storage.ParseStorageURL(src) + srcParsed, err := client.ParseStorageURL(src) if err != nil { return err } - dstParsed, err := storage.ParseStorageURL(dst) + dstParsed, err := client.ParseStorageURL(dst) if err != nil { return err } - projectRef, err := flags.LoadProjectRef(fsys) - if err != nil { - return err - } - srcBucket, srcPrefix := storage.SplitBucketPrefix(srcParsed) - dstBucket, dstPrefix := storage.SplitBucketPrefix(dstParsed) + srcBucket, srcPrefix := client.SplitBucketPrefix(srcParsed) + dstBucket, dstPrefix := client.SplitBucketPrefix(dstParsed) if len(srcPrefix) == 0 && len(dstPrefix) == 0 { return errors.New(errMissingPath) } if srcBucket != dstBucket { return errors.New(errUnsupportedMove) } + api, err := client.NewStorageAPI(ctx, flags.ProjectRef) + if err != nil { + return err + } fmt.Fprintln(os.Stderr, "Moving object:", srcParsed, "=>", dstParsed) - data, err := client.MoveStorageObject(ctx, projectRef, srcBucket, srcPrefix, dstPrefix) + data, err := api.MoveObject(ctx, srcBucket, srcPrefix, dstPrefix) if err == nil { fmt.Fprintln(os.Stderr, data.Message) } else if strings.Contains(err.Error(), `"error":"not_found"`) && recursive { - return MoveStorageObjectAll(ctx, projectRef, srcParsed+"/", dstParsed) + return MoveStorageObjectAll(ctx, api, srcParsed+"/", dstParsed) } return err } // Expects srcPath to be terminated by "/" -func MoveStorageObjectAll(ctx context.Context, projectRef, srcPath, dstPath string) error { - _, dstPrefix := storage.SplitBucketPrefix(dstPath) +func MoveStorageObjectAll(ctx context.Context, api storage.StorageAPI, srcPath, dstPath string) error { + _, dstPrefix := client.SplitBucketPrefix(dstPath) // Cannot iterate because pagination result may be updated during move count := 0 queue := make([]string, 0) @@ -61,7 +61,7 @@ func MoveStorageObjectAll(ctx context.Context, projectRef, srcPath, dstPath stri for len(queue) > 0 { dirPath := queue[len(queue)-1] queue = queue[:len(queue)-1] - paths, err := ls.ListStoragePaths(ctx, projectRef, dirPath) + paths, err := ls.ListStoragePaths(ctx, api, dirPath) if err != nil { return err } @@ -73,10 +73,10 @@ func MoveStorageObjectAll(ctx context.Context, projectRef, srcPath, dstPath stri } count++ relPath := strings.TrimPrefix(objectPath, srcPath) - srcBucket, srcPrefix := storage.SplitBucketPrefix(objectPath) + srcBucket, srcPrefix := client.SplitBucketPrefix(objectPath) absPath := path.Join(dstPrefix, relPath) fmt.Fprintln(os.Stderr, "Moving object:", objectPath, "=>", path.Join(dstPath, relPath)) - if _, err := client.MoveStorageObject(ctx, projectRef, srcBucket, srcPrefix, absPath); err != nil { + if _, err := api.MoveObject(ctx, srcBucket, srcPrefix, absPath); err != nil { return err } } diff --git a/internal/storage/mv/mv_test.go b/internal/storage/mv/mv_test.go index 609bca21e..299851c54 100644 --- a/internal/storage/mv/mv_test.go +++ b/internal/storage/mv/mv_test.go @@ -7,21 +7,22 @@ import ( "github.com/spf13/afero" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/supabase/cli/internal/storage/client" "github.com/supabase/cli/internal/testing/apitest" "github.com/supabase/cli/internal/utils" + "github.com/supabase/cli/internal/utils/flags" "github.com/supabase/cli/pkg/api" + "github.com/supabase/cli/pkg/fetcher" + "github.com/supabase/cli/pkg/storage" "gopkg.in/h2non/gock.v1" ) -var mockFile = client.ObjectResponse{ +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"), - Metadata: &client.ObjectMetadata{ + Metadata: &storage.ObjectMetadata{ ETag: `"887ea9be3c68e6f2fca7fd2d7c77d8fe"`, Size: 82702, Mimetype: "application/pdf", @@ -32,33 +33,37 @@ var mockFile = client.ObjectResponse{ }, } +var mockApi = storage.StorageAPI{Fetcher: fetcher.NewFetcher( + "http://127.0.0.1", +)} + func TestStorageMV(t *testing.T) { + flags.ProjectRef = apitest.RandomProjectRef() + // Setup valid access token + token := apitest.RandomAccessToken(t) + t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) + t.Run("moves single object", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() - projectRef := apitest.RandomProjectRef() - require.NoError(t, afero.WriteFile(fsys, utils.ProjectRefPath, []byte(projectRef), 0644)) - // Setup valid access token - token := apitest.RandomAccessToken(t) - t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) // Setup mock api defer gock.OffAll() gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). + Get("/v1/projects/" + flags.ProjectRef + "/api-keys"). Reply(http.StatusOK). JSON([]api.ApiKeyResponse{{ Name: "service_role", ApiKey: "service-key", }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). Post("/storage/v1/object/move"). - JSON(client.MoveObjectRequest{ + JSON(storage.MoveObjectRequest{ BucketId: "private", SourceKey: "readme.md", DestinationKey: "docs/file", }). Reply(http.StatusOK). - JSON(client.MoveObjectResponse{Message: "Successfully moved"}) + JSON(storage.MoveObjectResponse{Message: "Successfully moved"}) // Run test err := Run(context.Background(), "ss:///private/readme.md", "ss:///private/docs/file", false, fsys) // Check error @@ -69,23 +74,18 @@ func TestStorageMV(t *testing.T) { t.Run("moves directory when recursive", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() - projectRef := apitest.RandomProjectRef() - require.NoError(t, afero.WriteFile(fsys, utils.ProjectRefPath, []byte(projectRef), 0644)) - // Setup valid access token - token := apitest.RandomAccessToken(t) - t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) // Setup mock api defer gock.OffAll() gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). + Get("/v1/projects/" + flags.ProjectRef + "/api-keys"). Reply(http.StatusOK). JSON([]api.ApiKeyResponse{{ Name: "service_role", ApiKey: "service-key", }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). Post("/storage/v1/object/move"). - JSON(client.MoveObjectRequest{ + JSON(storage.MoveObjectRequest{ BucketId: "private", SourceKey: "", DestinationKey: "docs", @@ -93,19 +93,19 @@ func TestStorageMV(t *testing.T) { Reply(http.StatusNotFound). JSON(map[string]string{"error": "not_found"}) // List bucket /private/ - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). Post("/storage/v1/object/list/private"). Reply(http.StatusOK). - JSON([]client.ObjectResponse{mockFile}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + JSON([]storage.ObjectResponse{mockFile}) + gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). Post("/storage/v1/object/move"). - JSON(client.MoveObjectRequest{ + JSON(storage.MoveObjectRequest{ BucketId: "private", SourceKey: "abstract.pdf", DestinationKey: "docs/abstract.pdf", }). Reply(http.StatusOK). - JSON(client.MoveObjectResponse{Message: "Successfully moved"}) + JSON(storage.MoveObjectResponse{Message: "Successfully moved"}) // Run test err := Run(context.Background(), "ss:///private", "ss:///private/docs", true, fsys) // Check error @@ -131,20 +131,9 @@ func TestStorageMV(t *testing.T) { assert.ErrorContains(t, err, "missing protocol scheme") }) - t.Run("throws error on missing project", func(t *testing.T) { - // Setup in-memory fs - fsys := afero.NewMemMapFs() - // Run test - err := Run(context.Background(), "ss:///", "ss:///", false, fsys) - // Check error - assert.ErrorIs(t, err, utils.ErrNotLinked) - }) - t.Run("throws error on missing object path", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() - projectRef := apitest.RandomProjectRef() - require.NoError(t, afero.WriteFile(fsys, utils.ProjectRefPath, []byte(projectRef), 0644)) // Run test err := Run(context.Background(), "ss:///", "ss:///", false, fsys) // Check error @@ -154,8 +143,6 @@ func TestStorageMV(t *testing.T) { t.Run("throws error on bucket mismatch", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() - projectRef := apitest.RandomProjectRef() - require.NoError(t, afero.WriteFile(fsys, utils.ProjectRefPath, []byte(projectRef), 0644)) // Run test err := Run(context.Background(), "ss:///bucket/docs", "ss:///private", false, fsys) // Check error @@ -164,68 +151,55 @@ func TestStorageMV(t *testing.T) { } func TestMoveAll(t *testing.T) { - // Setup valid project ref - projectRef := apitest.RandomProjectRef() - // Setup valid access token - token := apitest.RandomAccessToken(t) - t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) - t.Run("rename directory within bucket", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) // Lists /private/tmp directory - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). - JSON(client.ListObjectsQuery{ + JSON(storage.ListObjectsQuery{ Prefix: "tmp/", Search: "", - Limit: client.PAGE_LIMIT, + Limit: storage.PAGE_LIMIT, Offset: 0, }). Reply(http.StatusOK). - JSON([]client.ObjectResponse{{ + JSON([]storage.ObjectResponse{{ Name: "docs", }, mockFile}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/move"). - JSON(client.MoveObjectRequest{ + JSON(storage.MoveObjectRequest{ BucketId: "private", SourceKey: "tmp/abstract.pdf", DestinationKey: "dir/abstract.pdf", }). Reply(http.StatusOK). - JSON(client.MoveObjectResponse{Message: "Successfully moved"}) + JSON(storage.MoveObjectResponse{Message: "Successfully moved"}) // Lists /private/tmp/docs directory readme := mockFile readme.Name = "readme.md" - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). - JSON(client.ListObjectsQuery{ + JSON(storage.ListObjectsQuery{ Prefix: "tmp/docs/", Search: "", - Limit: client.PAGE_LIMIT, + Limit: storage.PAGE_LIMIT, Offset: 0, }). Reply(http.StatusOK). - JSON([]client.ObjectResponse{readme}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + JSON([]storage.ObjectResponse{readme}) + gock.New("http://127.0.0.1"). Post("/storage/v1/object/move"). - JSON(client.MoveObjectRequest{ + JSON(storage.MoveObjectRequest{ BucketId: "private", SourceKey: "tmp/docs/readme.md", DestinationKey: "dir/docs/readme.md", }). Reply(http.StatusOK). - JSON(client.MoveObjectResponse{Message: "Successfully moved"}) + JSON(storage.MoveObjectResponse{Message: "Successfully moved"}) // Run test - err := MoveStorageObjectAll(context.Background(), projectRef, "private/tmp/", "private/dir") + err := MoveStorageObjectAll(context.Background(), mockApi, "private/tmp/", "private/dir") // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -234,35 +208,28 @@ func TestMoveAll(t *testing.T) { t.Run("moves object into directory", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) // Lists /private/ bucket - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). - JSON(client.ListObjectsQuery{ + JSON(storage.ListObjectsQuery{ Prefix: "", Search: "", - Limit: client.PAGE_LIMIT, + Limit: storage.PAGE_LIMIT, Offset: 0, }). Reply(http.StatusOK). - JSON([]client.ObjectResponse{mockFile}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + JSON([]storage.ObjectResponse{mockFile}) + gock.New("http://127.0.0.1"). Post("/storage/v1/object/move"). - JSON(client.MoveObjectRequest{ + JSON(storage.MoveObjectRequest{ BucketId: "private", SourceKey: "abstract.pdf", DestinationKey: "dir/abstract.pdf", }). Reply(http.StatusOK). - JSON(client.MoveObjectResponse{Message: "Successfully moved"}) + JSON(storage.MoveObjectResponse{Message: "Successfully moved"}) // Run test - err := MoveStorageObjectAll(context.Background(), projectRef, "private/", "private/dir") + err := MoveStorageObjectAll(context.Background(), mockApi, "private/", "private/dir") // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -271,37 +238,30 @@ func TestMoveAll(t *testing.T) { t.Run("moves object out of directory", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) // Lists /private/tmp/ directory readme := mockFile readme.Name = "readme.md" - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). - JSON(client.ListObjectsQuery{ + JSON(storage.ListObjectsQuery{ Prefix: "tmp/", Search: "", - Limit: client.PAGE_LIMIT, + Limit: storage.PAGE_LIMIT, Offset: 0, }). Reply(http.StatusOK). - JSON([]client.ObjectResponse{readme}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + JSON([]storage.ObjectResponse{readme}) + gock.New("http://127.0.0.1"). Post("/storage/v1/object/move"). - JSON(client.MoveObjectRequest{ + JSON(storage.MoveObjectRequest{ BucketId: "private", SourceKey: "tmp/readme.md", DestinationKey: "readme.md", }). Reply(http.StatusOK). - JSON(client.MoveObjectResponse{Message: "Successfully moved"}) + JSON(storage.MoveObjectResponse{Message: "Successfully moved"}) // Run test - err := MoveStorageObjectAll(context.Background(), projectRef, "private/tmp/", "private") + err := MoveStorageObjectAll(context.Background(), mockApi, "private/tmp/", "private") // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -310,18 +270,11 @@ func TestMoveAll(t *testing.T) { t.Run("throws error on service unavailable", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). Reply(http.StatusServiceUnavailable) // Run test - err := MoveStorageObjectAll(context.Background(), projectRef, "private/tmp/", "private") + err := MoveStorageObjectAll(context.Background(), mockApi, "private/tmp/", "private") // Check error assert.ErrorContains(t, err, "Error status 503:") assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -330,22 +283,15 @@ func TestMoveAll(t *testing.T) { t.Run("throws error on move failure", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). Reply(http.StatusOK). - JSON([]client.ObjectResponse{mockFile}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + JSON([]storage.ObjectResponse{mockFile}) + gock.New("http://127.0.0.1"). Post("/storage/v1/object/move"). Reply(http.StatusServiceUnavailable) // Run test - err := MoveStorageObjectAll(context.Background(), projectRef, "private/tmp/", "private") + err := MoveStorageObjectAll(context.Background(), mockApi, "private/tmp/", "private") // Check error assert.ErrorContains(t, err, "Error status 503:") assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -354,19 +300,12 @@ func TestMoveAll(t *testing.T) { t.Run("throws error on missing object", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). Reply(http.StatusOK). - JSON([]client.ObjectResponse{}) + JSON([]storage.ObjectResponse{}) // Run test - err := MoveStorageObjectAll(context.Background(), projectRef, "private/tmp/", "private") + err := MoveStorageObjectAll(context.Background(), mockApi, "private/tmp/", "private") // Check error assert.ErrorContains(t, err, "Object not found: private/tmp/") assert.Empty(t, apitest.ListUnmatchedRequests()) diff --git a/internal/storage/rm/rm.go b/internal/storage/rm/rm.go index 6b7883585..50ff8d148 100644 --- a/internal/storage/rm/rm.go +++ b/internal/storage/rm/rm.go @@ -8,12 +8,12 @@ import ( "github.com/go-errors/errors" "github.com/spf13/afero" - "github.com/supabase/cli/internal/storage" "github.com/supabase/cli/internal/storage/client" "github.com/supabase/cli/internal/storage/cp" "github.com/supabase/cli/internal/storage/ls" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/internal/utils/flags" + "github.com/supabase/cli/pkg/storage" ) var ( @@ -31,11 +31,11 @@ func Run(ctx context.Context, paths []string, recursive bool, fsys afero.Fs) err // Group paths by buckets groups := map[string][]string{} for _, objectPath := range paths { - remotePath, err := storage.ParseStorageURL(objectPath) + remotePath, err := client.ParseStorageURL(objectPath) if err != nil { return err } - bucket, prefix := storage.SplitBucketPrefix(remotePath) + bucket, prefix := client.SplitBucketPrefix(remotePath) // Ignore attempts to delete all buckets if len(bucket) == 0 { return errors.New(errMissingBucket) @@ -45,7 +45,7 @@ func Run(ctx context.Context, paths []string, recursive bool, fsys afero.Fs) err } groups[bucket] = append(groups[bucket], prefix) } - projectRef, err := flags.LoadProjectRef(fsys) + api, err := client.NewStorageAPI(ctx, flags.ProjectRef) if err != nil { return err } @@ -56,7 +56,7 @@ func Run(ctx context.Context, paths []string, recursive bool, fsys afero.Fs) err } // Always try deleting first in case the paths resolve to extensionless files fmt.Fprintln(os.Stderr, "Deleting objects:", prefixes) - removed, err := client.DeleteStorageObjects(ctx, projectRef, bucket, prefixes) + removed, err := api.DeleteObjects(ctx, bucket, prefixes) if err != nil { return err } @@ -75,7 +75,7 @@ func Run(ctx context.Context, paths []string, recursive bool, fsys afero.Fs) err if len(prefix) > 0 { prefix += "/" } - if err := RemoveStoragePathAll(ctx, projectRef, bucket, prefix); err != nil { + if err := RemoveStoragePathAll(ctx, api, bucket, prefix); err != nil { return err } } @@ -84,14 +84,14 @@ func Run(ctx context.Context, paths []string, recursive bool, fsys afero.Fs) err } // Expects prefix to be terminated by "/" or "" -func RemoveStoragePathAll(ctx context.Context, projectRef, bucket, prefix string) error { +func RemoveStoragePathAll(ctx context.Context, api storage.StorageAPI, bucket, prefix string) error { // We must remove one directory at a time to avoid breaking pagination result queue := make([]string, 0) queue = append(queue, prefix) for len(queue) > 0 { dirPrefix := queue[len(queue)-1] queue = queue[:len(queue)-1] - paths, err := ls.ListStoragePaths(ctx, projectRef, fmt.Sprintf("/%s/%s", bucket, dirPrefix)) + paths, err := ls.ListStoragePaths(ctx, api, fmt.Sprintf("/%s/%s", bucket, dirPrefix)) if err != nil { return err } @@ -109,14 +109,14 @@ func RemoveStoragePathAll(ctx context.Context, projectRef, bucket, prefix string } if len(files) > 0 { fmt.Fprintln(os.Stderr, "Deleting objects:", files) - if _, err := client.DeleteStorageObjects(ctx, projectRef, bucket, files); err != nil { + if _, err := api.DeleteObjects(ctx, bucket, files); err != nil { return err } } } if len(prefix) == 0 { fmt.Fprintln(os.Stderr, "Deleting bucket:", bucket) - if data, err := client.DeleteStorageBucket(ctx, projectRef, bucket); err == nil { + if data, err := api.DeleteBucket(ctx, bucket); err == nil { fmt.Fprintln(os.Stderr, data.Message) } else if strings.Contains(err.Error(), `"error":"Bucket not found"`) { fmt.Fprintln(os.Stderr, "Bucket not found:", bucket) diff --git a/internal/storage/rm/rm_test.go b/internal/storage/rm/rm_test.go index b56b77ca1..0ce155a45 100644 --- a/internal/storage/rm/rm_test.go +++ b/internal/storage/rm/rm_test.go @@ -7,22 +7,23 @@ import ( "github.com/spf13/afero" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/supabase/cli/internal/storage/client" "github.com/supabase/cli/internal/testing/apitest" "github.com/supabase/cli/internal/testing/fstest" "github.com/supabase/cli/internal/utils" + "github.com/supabase/cli/internal/utils/flags" "github.com/supabase/cli/pkg/api" + "github.com/supabase/cli/pkg/fetcher" + "github.com/supabase/cli/pkg/storage" "gopkg.in/h2non/gock.v1" ) -var mockFile = client.ObjectResponse{ +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"), - Metadata: &client.ObjectMetadata{ + Metadata: &storage.ObjectMetadata{ ETag: `"887ea9be3c68e6f2fca7fd2d7c77d8fe"`, Size: 82702, Mimetype: "application/pdf", @@ -33,7 +34,16 @@ var mockFile = client.ObjectResponse{ }, } +var mockApi = storage.StorageAPI{Fetcher: fetcher.NewFetcher( + "http://127.0.0.1", +)} + func TestStorageRM(t *testing.T) { + flags.ProjectRef = apitest.RandomProjectRef() + // Setup valid access token + token := apitest.RandomAccessToken(t) + t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) + t.Run("throws error on invalid url", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() @@ -61,41 +71,27 @@ func TestStorageRM(t *testing.T) { assert.ErrorIs(t, err, errMissingFlag) }) - t.Run("throws error on missing project", func(t *testing.T) { - // Setup in-memory fs - fsys := afero.NewMemMapFs() - // Run test - err := Run(context.Background(), []string{}, false, fsys) - // Check error - assert.ErrorIs(t, err, utils.ErrNotLinked) - }) - t.Run("removes multiple objects", func(t *testing.T) { defer fstest.MockStdin(t, "y")() // Setup in-memory fs fsys := afero.NewMemMapFs() - projectRef := apitest.RandomProjectRef() - require.NoError(t, afero.WriteFile(fsys, utils.ProjectRefPath, []byte(projectRef), 0644)) - // Setup valid access token - token := apitest.RandomAccessToken(t) - t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) // Setup mock api defer gock.OffAll() gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). + Get("/v1/projects/" + flags.ProjectRef + "/api-keys"). Reply(http.StatusOK). JSON([]api.ApiKeyResponse{{ Name: "service_role", ApiKey: "service-key", }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). Delete("/storage/v1/object/private"). - JSON(client.DeleteObjectsRequest{Prefixes: []string{ + JSON(storage.DeleteObjectsRequest{Prefixes: []string{ "abstract.pdf", "docs/readme.md", }}). Reply(http.StatusOK). - JSON([]client.DeleteObjectsResponse{{ + JSON([]storage.DeleteObjectsResponse{{ BucketId: "private", Version: "cf5c5c53-ee73-4806-84e3-7d92c954b436", Name: "abstract.pdf", @@ -118,59 +114,54 @@ func TestStorageRM(t *testing.T) { defer fstest.MockStdin(t, "y")() // Setup in-memory fs fsys := afero.NewMemMapFs() - projectRef := apitest.RandomProjectRef() - require.NoError(t, afero.WriteFile(fsys, utils.ProjectRefPath, []byte(projectRef), 0644)) - // Setup valid access token - token := apitest.RandomAccessToken(t) - t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) // Setup mock api defer gock.OffAll() gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). + Get("/v1/projects/" + flags.ProjectRef + "/api-keys"). Reply(http.StatusOK). JSON([]api.ApiKeyResponse{{ Name: "service_role", ApiKey: "service-key", }}) // Delete /test/ bucket - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). Post("/storage/v1/object/list/test"). Reply(http.StatusOK). - JSON([]client.ObjectResponse{}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + JSON([]storage.ObjectResponse{}) + gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). Delete("/storage/v1/object/test"). - JSON(client.DeleteObjectsRequest{Prefixes: []string{ + JSON(storage.DeleteObjectsRequest{Prefixes: []string{ "", }}). Reply(http.StatusOK). - JSON([]client.DeleteObjectsResponse{}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + JSON([]storage.DeleteObjectsResponse{}) + gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). Post("/storage/v1/object/list/test"). Reply(http.StatusOK). - JSON([]client.ObjectResponse{}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + JSON([]storage.ObjectResponse{}) + gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). Delete("/storage/v1/bucket/test"). Reply(http.StatusNotFound). JSON(map[string]string{"error": "Bucket not found"}) // Delete /private/docs/ directory - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). Delete("/storage/v1/object/private"). - JSON(client.DeleteObjectsRequest{Prefixes: []string{ + JSON(storage.DeleteObjectsRequest{Prefixes: []string{ "docs", }}). Reply(http.StatusOK). - JSON([]client.DeleteObjectsResponse{}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + JSON([]storage.DeleteObjectsResponse{}) + gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). Post("/storage/v1/object/list/private"). Reply(http.StatusOK). - JSON([]client.ObjectResponse{mockFile}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + JSON([]storage.ObjectResponse{mockFile}) + gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). Delete("/storage/v1/object/private"). - JSON(client.DeleteObjectsRequest{Prefixes: []string{ + JSON(storage.DeleteObjectsRequest{Prefixes: []string{ "docs/abstract.pdf", }}). Reply(http.StatusOK). - JSON([]client.DeleteObjectsResponse{{ + JSON([]storage.DeleteObjectsResponse{{ BucketId: "private", Version: "cf5c5c53-ee73-4806-84e3-7d92c954b436", Name: "abstract.pdf", @@ -193,21 +184,16 @@ func TestStorageRM(t *testing.T) { defer fstest.MockStdin(t, "y")() // Setup in-memory fs fsys := afero.NewMemMapFs() - projectRef := apitest.RandomProjectRef() - require.NoError(t, afero.WriteFile(fsys, utils.ProjectRefPath, []byte(projectRef), 0644)) - // Setup valid access token - token := apitest.RandomAccessToken(t) - t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) // Setup mock api defer gock.OffAll() gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). + Get("/v1/projects/" + flags.ProjectRef + "/api-keys"). Reply(http.StatusOK). JSON([]api.ApiKeyResponse{{ Name: "service_role", ApiKey: "service-key", }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). Delete("/storage/v1/object/private"). Reply(http.StatusServiceUnavailable) // Run test @@ -219,52 +205,43 @@ func TestStorageRM(t *testing.T) { } func TestRemoveAll(t *testing.T) { - projectRef := apitest.RandomProjectRef() - t.Run("removes objects by prefix", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) // List /private/tmp/ - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). - JSON(client.ListObjectsQuery{ + JSON(storage.ListObjectsQuery{ Prefix: "tmp/", Search: "", - Limit: client.PAGE_LIMIT, + Limit: storage.PAGE_LIMIT, Offset: 0, }). Reply(http.StatusOK). - JSON([]client.ObjectResponse{{ + JSON([]storage.ObjectResponse{{ Name: "docs", }}) // List /private/docs/ readme := mockFile readme.Name = "readme.md" - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). - JSON(client.ListObjectsQuery{ + JSON(storage.ListObjectsQuery{ Prefix: "tmp/docs/", Search: "", - Limit: client.PAGE_LIMIT, + Limit: storage.PAGE_LIMIT, Offset: 0, }). Reply(http.StatusOK). - JSON([]client.ObjectResponse{mockFile, readme}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + JSON([]storage.ObjectResponse{mockFile, readme}) + gock.New("http://127.0.0.1"). Delete("/storage/v1/object/private"). - JSON(client.DeleteObjectsRequest{Prefixes: []string{ + JSON(storage.DeleteObjectsRequest{Prefixes: []string{ "tmp/docs/abstract.pdf", "tmp/docs/readme.md", }}). Reply(http.StatusOK). - JSON([]client.DeleteObjectsResponse{{ + JSON([]storage.DeleteObjectsResponse{{ BucketId: "private", Version: "cf5c5c53-ee73-4806-84e3-7d92c954b436", Name: "abstract.pdf", @@ -282,7 +259,7 @@ func TestRemoveAll(t *testing.T) { LastAccessedAt: "2023-10-13T18:08:22.068Z", }}) // Run test - err := RemoveStoragePathAll(context.Background(), projectRef, "private", "tmp/") + err := RemoveStoragePathAll(context.Background(), mockApi, "private", "tmp/") // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -291,23 +268,16 @@ func TestRemoveAll(t *testing.T) { t.Run("removes empty bucket", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). Reply(http.StatusOK). - JSON([]client.ObjectResponse{}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + JSON([]storage.ObjectResponse{}) + gock.New("http://127.0.0.1"). Delete("/storage/v1/bucket/private"). Reply(http.StatusOK). - JSON(client.DeleteBucketResponse{Message: "Successfully deleted"}) + JSON(storage.DeleteBucketResponse{Message: "Successfully deleted"}) // Run test - err := RemoveStoragePathAll(context.Background(), projectRef, "private", "") + err := RemoveStoragePathAll(context.Background(), mockApi, "private", "") // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -316,19 +286,12 @@ func TestRemoveAll(t *testing.T) { t.Run("throws error on empty directory", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). Reply(http.StatusOK). - JSON([]client.ObjectResponse{}) + JSON([]storage.ObjectResponse{}) // Run test - err := RemoveStoragePathAll(context.Background(), projectRef, "private", "dir") + err := RemoveStoragePathAll(context.Background(), mockApi, "private", "dir") // Check error assert.ErrorContains(t, err, "Object not found: private/dir") assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -337,18 +300,11 @@ func TestRemoveAll(t *testing.T) { t.Run("throws error on service unavailable", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). Reply(http.StatusServiceUnavailable) // Run test - err := RemoveStoragePathAll(context.Background(), projectRef, "private", "") + err := RemoveStoragePathAll(context.Background(), mockApi, "private", "") // Check error assert.ErrorContains(t, err, "Error status 503:") assert.Empty(t, apitest.ListUnmatchedRequests()) @@ -357,22 +313,15 @@ func TestRemoveAll(t *testing.T) { t.Run("throws error on delete failure", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{ - Name: "service_role", - ApiKey: "service-key", - }}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + gock.New("http://127.0.0.1"). Post("/storage/v1/object/list/private"). Reply(http.StatusOK). - JSON([]client.ObjectResponse{mockFile}) - gock.New("https://" + utils.GetSupabaseHost(projectRef)). + JSON([]storage.ObjectResponse{mockFile}) + gock.New("http://127.0.0.1"). Delete("/storage/v1/object/private"). Reply(http.StatusServiceUnavailable) // Run test - err := RemoveStoragePathAll(context.Background(), projectRef, "private", "") + err := RemoveStoragePathAll(context.Background(), mockApi, "private", "") // Check error assert.ErrorContains(t, err, "Error status 503:") assert.Empty(t, apitest.ListUnmatchedRequests()) diff --git a/internal/testing/pgtest/mock.go b/internal/testing/pgtest/mock.go index 32402db31..0cfc7ad49 100644 --- a/internal/testing/pgtest/mock.go +++ b/internal/testing/pgtest/mock.go @@ -104,10 +104,10 @@ func (r *MockConn) encodeValueArg(v interface{}) (value []byte, oid uint32) { } var err error switch dt.OID { - case pgtype.TextArrayOID: - value, err = (dt.Value).(pgtype.BinaryEncoder).EncodeBinary(ci, []byte{}) - default: + case pgtype.TextOID: value, err = (dt.Value).(pgtype.TextEncoder).EncodeText(ci, []byte{}) + default: + value, err = (dt.Value).(pgtype.BinaryEncoder).EncodeBinary(ci, []byte{}) } if err != nil { r.errChan <- fmt.Errorf("failed to encode arg: %w", err) @@ -132,26 +132,35 @@ func (r *MockConn) lastQuery() *extendedQueryStep { // Adds a server reply using binary or text protocol format. // // TODO: support prepared statements when using binary protocol -func (r *MockConn) Reply(tag string, rows ...[]interface{}) *MockConn { +func (r *MockConn) Reply(tag string, rows ...interface{}) *MockConn { q := r.lastQuery() // Add field description if len(rows) > 0 { var desc pgproto3.RowDescription - for i, v := range rows[0] { - name := fmt.Sprintf("c_%02d", i) - if dt, ok := ci.DataTypeForValue(v); ok { - size := getDataTypeSize(v) - format := ci.ParamFormatCodeForOID(dt.OID) - desc.Fields = append(desc.Fields, pgproto3.FieldDescription{ - Name: []byte(name), - TableOID: 17131, - TableAttributeNumber: 1, - DataTypeOID: dt.OID, - DataTypeSize: size, - TypeModifier: -1, - Format: format, - }) + if arr, ok := rows[0].([]interface{}); ok { + for i, v := range arr { + name := fmt.Sprintf("c_%02d", i) + if fd := toFieldDescription(v); fd != nil { + fd.Name = []byte(name) + desc.Fields = append(desc.Fields, *fd) + } else { + r.errChan <- fmt.Errorf("failed to describe field: %s", name) + } + } + } else if t := reflect.TypeOf(rows[0]); t.Kind() == reflect.Struct { + s := reflect.ValueOf(rows[0]) + for i := 0; i < s.NumField(); i++ { + name := t.Field(i).Name + v := s.Field(i).Interface() + if fd := toFieldDescription(v); fd != nil { + fd.Name = []byte(name) + desc.Fields = append(desc.Fields, *fd) + } else { + r.errChan <- fmt.Errorf("failed to describe field: %s", name) + } } + } else { + r.errChan <- fmt.Errorf("reply type must be one of [array, struct], found: %s", t.Kind()) } q.reply.Steps = append(q.reply.Steps, pgmock.SendMessage(&desc)) } else { @@ -161,10 +170,22 @@ func (r *MockConn) Reply(tag string, rows ...[]interface{}) *MockConn { // Add row data for _, data := range rows { var dr pgproto3.DataRow - for _, v := range data { - if value, oid := r.encodeValueArg(v); oid > 0 { - dr.Values = append(dr.Values, value) + if arr, ok := data.([]interface{}); ok { + for _, v := range arr { + if value, oid := r.encodeValueArg(v); oid > 0 { + dr.Values = append(dr.Values, value) + } + } + } else if t := reflect.TypeOf(data); t.Kind() == reflect.Struct { + s := reflect.ValueOf(rows[0]) + for i := 0; i < s.NumField(); i++ { + v := s.Field(i).Interface() + if value, oid := r.encodeValueArg(v); oid > 0 { + dr.Values = append(dr.Values, value) + } } + } else { + r.errChan <- fmt.Errorf("invalid reply value: %v", data) } q.reply.Steps = append(q.reply.Steps, pgmock.SendMessage(&dr)) } @@ -179,6 +200,22 @@ func (r *MockConn) Reply(tag string, rows ...[]interface{}) *MockConn { return r } +func toFieldDescription(v interface{}) *pgproto3.FieldDescription { + if dt, ok := ci.DataTypeForValue(v); ok { + size := getDataTypeSize(v) + format := ci.ParamFormatCodeForOID(dt.OID) + return &pgproto3.FieldDescription{ + TableOID: 17131, + TableAttributeNumber: 1, + DataTypeOID: dt.OID, + DataTypeSize: size, + TypeModifier: -1, + Format: format, + } + } + return nil +} + // Simulates an error reply from the server. // // TODO: simulate a notice reply @@ -197,8 +234,8 @@ func (r *MockConn) ReplyError(code, message string) *MockConn { } func (r *MockConn) Close(t *testing.T) { - if err := <-r.errChan; err != nil { - t.Fatalf("failed to close: %v", err) + for err := range r.errChan { + t.Errorf("pgmock error: %v", err) } if err := r.server.Close(); err != nil { t.Fatalf("failed to close: %v", err) @@ -210,7 +247,7 @@ func NewWithStatus(status map[string]string) *MockConn { mock := MockConn{ server: bufconn.Listen(bufSize), status: status, - errChan: make(chan error, 1), + errChan: make(chan error, 10), } // Start server in background const timeout = time.Millisecond * 450 diff --git a/internal/utils/api.go b/internal/utils/api.go index 78d99ff88..90e6a2610 100644 --- a/internal/utils/api.go +++ b/internal/utils/api.go @@ -14,6 +14,7 @@ import ( "github.com/go-errors/errors" "github.com/spf13/viper" + "github.com/supabase/cli/internal/utils/cloudflare" supabase "github.com/supabase/cli/pkg/api" ) @@ -32,40 +33,21 @@ var ( } ) -const ( - // Ref: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4 - dnsIPv4Type uint16 = 1 - cnameType uint16 = 5 - dnsIPv6Type uint16 = 28 -) - -type dnsAnswer struct { - Type uint16 `json:"type"` - Data string `json:"data"` -} - -type dnsResponse struct { - Answer []dnsAnswer `json:",omitempty"` -} - // Performs DNS lookup via HTTPS, in case firewall blocks native netgo resolver. func FallbackLookupIP(ctx context.Context, host string) ([]string, error) { if net.ParseIP(host) != nil { return []string{host}, nil } // Ref: https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/make-api-requests/dns-json - url := "https://1.1.1.1/dns-query?name=" + host - data, err := JsonResponse[dnsResponse](ctx, http.MethodGet, url, nil, func(ctx context.Context, req *http.Request) error { - req.Header.Add("accept", "application/dns-json") - return nil - }) + cf := cloudflare.NewCloudflareAPI() + data, err := cf.DNSQuery(ctx, cloudflare.DNSParams{Name: host}) if err != nil { return nil, err } // Look for first valid IP var resolved []string for _, answer := range data.Answer { - if answer.Type == dnsIPv4Type || answer.Type == dnsIPv6Type { + if answer.Type == cloudflare.TypeA || answer.Type == cloudflare.TypeAAAA { resolved = append(resolved, answer.Data) } } @@ -77,17 +59,14 @@ 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 - url := fmt.Sprintf("https://1.1.1.1/dns-query?name=%s&type=CNAME", host) - data, err := JsonResponse[dnsResponse](ctx, http.MethodGet, url, nil, func(ctx context.Context, req *http.Request) error { - req.Header.Add("accept", "application/dns-json") - return nil - }) + cf := cloudflare.NewCloudflareAPI() + data, err := cf.DNSQuery(ctx, cloudflare.DNSParams{Name: host, Type: Ptr(cloudflare.TypeCNAME)}) if err != nil { return "", err } // Look for first valid IP for _, answer := range data.Answer { - if answer.Type == cnameType { + if answer.Type == cloudflare.TypeCNAME { return answer.Data, nil } } diff --git a/internal/utils/api_test.go b/internal/utils/api_test.go index 742e33336..4e045a8cb 100644 --- a/internal/utils/api_test.go +++ b/internal/utils/api_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/supabase/cli/internal/testing/apitest" + "github.com/supabase/cli/internal/utils/cloudflare" "gopkg.in/h2non/gock.v1" ) @@ -24,8 +25,8 @@ func TestLookupIP(t *testing.T) { MatchParam("name", host). MatchHeader("accept", "application/dns-json"). Reply(http.StatusOK). - JSON(&dnsResponse{Answer: []dnsAnswer{ - {Type: dnsIPv4Type, Data: "127.0.0.1"}, + JSON(&cloudflare.DNSResponse{Answer: []cloudflare.DNSAnswer{ + {Type: cloudflare.TypeA, Data: "127.0.0.1"}, }}) // Run test ip, err := FallbackLookupIP(context.Background(), host) @@ -43,9 +44,9 @@ func TestLookupIP(t *testing.T) { MatchParam("name", "api.supabase.com"). MatchHeader("accept", "application/dns-json"). Reply(http.StatusOK). - JSON(&dnsResponse{Answer: []dnsAnswer{ - {Type: 5, Data: "supabase-api.fly.dev."}, - {Type: dnsIPv6Type, Data: "2606:2800:220:1:248:1893:25c8:1946"}, + JSON(&cloudflare.DNSResponse{Answer: []cloudflare.DNSAnswer{ + {Type: cloudflare.TypeCNAME, Data: "supabase-api.fly.dev."}, + {Type: cloudflare.TypeAAAA, Data: "2606:2800:220:1:248:1893:25c8:1946"}, }}) // Run test ip, err := FallbackLookupIP(context.Background(), "api.supabase.com") @@ -121,11 +122,11 @@ func TestLookupIP(t *testing.T) { MatchParam("name", host). MatchHeader("accept", "application/dns-json"). Reply(http.StatusOK). - JSON(&dnsResponse{}) + JSON(&cloudflare.DNSResponse{}) // Run test ip, err := FallbackLookupIP(context.Background(), host) // Validate output - assert.ErrorContains(t, err, "failed to locate valid IP for api.supabase.io; resolves to []utils.dnsAnswer(nil)") + assert.ErrorContains(t, err, "failed to locate valid IP for api.supabase.io; resolves to []cloudflare.DNSAnswer(nil)") assert.Empty(t, ip) assert.Empty(t, apitest.ListUnmatchedRequests()) }) @@ -137,11 +138,11 @@ func TestResolveCNAME(t *testing.T) { gock.New("https://1.1.1.1"). Get("/dns-query"). MatchParam("name", host). - MatchParam("type", "CNAME"). + MatchParam("type", "5"). MatchHeader("accept", "application/dns-json"). Reply(http.StatusOK). - JSON(&dnsResponse{Answer: []dnsAnswer{ - {Type: cnameType, Data: "foobarbaz.supabase.co"}, + JSON(&cloudflare.DNSResponse{Answer: []cloudflare.DNSAnswer{ + {Type: cloudflare.TypeCNAME, Data: "foobarbaz.supabase.co"}, }}) // Run test cname, err := ResolveCNAME(context.Background(), host) @@ -156,10 +157,10 @@ func TestResolveCNAME(t *testing.T) { gock.New("https://1.1.1.1"). Get("/dns-query"). MatchParam("name", host). - MatchParam("type", "CNAME"). + MatchParam("type", "5"). MatchHeader("accept", "application/dns-json"). Reply(http.StatusOK). - JSON(&dnsResponse{Answer: []dnsAnswer{}}) + JSON(&cloudflare.DNSResponse{Answer: []cloudflare.DNSAnswer{}}) // Run test cname, err := ResolveCNAME(context.Background(), host) // Validate output @@ -173,11 +174,11 @@ func TestResolveCNAME(t *testing.T) { gock.New("https://1.1.1.1"). Get("/dns-query"). MatchParam("name", host). - MatchParam("type", "CNAME"). + MatchParam("type", "5"). MatchHeader("accept", "application/dns-json"). Reply(http.StatusOK). - JSON(&dnsResponse{Answer: []dnsAnswer{ - {Type: dnsIPv4Type, Data: "127.0.0.1"}, + JSON(&cloudflare.DNSResponse{Answer: []cloudflare.DNSAnswer{ + {Type: cloudflare.TypeA, Data: "127.0.0.1"}, }}) // Run test cname, err := ResolveCNAME(context.Background(), host) @@ -220,8 +221,8 @@ func TestFallbackDNS(t *testing.T) { MatchParam("name", host). MatchHeader("accept", "application/dns-json"). Reply(http.StatusOK). - JSON(&dnsResponse{Answer: []dnsAnswer{ - {Type: dnsIPv4Type, Data: "127.0.0.1"}, + JSON(&cloudflare.DNSResponse{Answer: []cloudflare.DNSAnswer{ + {Type: cloudflare.TypeA, Data: "127.0.0.1"}, }}) // Run test conn, err := wrapped(context.Background(), "udp", host+":80") @@ -248,8 +249,8 @@ func TestFallbackDNS(t *testing.T) { MatchParam("name", host). MatchHeader("accept", "application/dns-json"). Reply(http.StatusOK). - JSON(&dnsResponse{Answer: []dnsAnswer{ - {Type: dnsIPv4Type, Data: "127.0.0.1"}, + JSON(&cloudflare.DNSResponse{Answer: []cloudflare.DNSAnswer{ + {Type: cloudflare.TypeA, Data: "127.0.0.1"}, }}) // Run test conn, err := wrapped(context.Background(), "udp", host+":80") diff --git a/internal/utils/cloudflare/api.go b/internal/utils/cloudflare/api.go new file mode 100644 index 000000000..7ba5c2ac3 --- /dev/null +++ b/internal/utils/cloudflare/api.go @@ -0,0 +1,28 @@ +package cloudflare + +import ( + "net/http" + "time" + + "github.com/supabase/cli/pkg/fetcher" +) + +type CloudflareAPI struct { + *fetcher.Fetcher +} + +func NewCloudflareAPI() CloudflareAPI { + server := "https://1.1.1.1" + client := &http.Client{ + Timeout: 10 * time.Second, + } + header := func(req *http.Request) { + req.Header.Add("accept", "application/dns-json") + } + api := CloudflareAPI{Fetcher: fetcher.NewFetcher( + server, + fetcher.WithHTTPClient(client), + fetcher.WithRequestEditor(header), + )} + return api +} diff --git a/internal/utils/cloudflare/dns.go b/internal/utils/cloudflare/dns.go new file mode 100644 index 000000000..0f36ccce7 --- /dev/null +++ b/internal/utils/cloudflare/dns.go @@ -0,0 +1,155 @@ +package cloudflare + +import ( + "context" + "net/http" + + "github.com/go-errors/errors" + "github.com/google/go-querystring/query" + "github.com/supabase/cli/pkg/fetcher" +) + +type DNSType uint16 + +// Spec: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4 +// Impl: https://github.com/miekg/dns/blob/master/types.go#L23 +const ( + TypeNone DNSType = 0 + TypeA DNSType = 1 // IPv4 + TypeNS DNSType = 2 + TypeMD DNSType = 3 + TypeMF DNSType = 4 + TypeCNAME DNSType = 5 + TypeSOA DNSType = 6 + TypeMB DNSType = 7 + TypeMG DNSType = 8 + TypeMR DNSType = 9 + TypeNULL DNSType = 10 + TypePTR DNSType = 12 + TypeHINFO DNSType = 13 + TypeMINFO DNSType = 14 + TypeMX DNSType = 15 + TypeTXT DNSType = 16 + TypeRP DNSType = 17 + TypeAFSDB DNSType = 18 + TypeX25 DNSType = 19 + TypeISDN DNSType = 20 + TypeRT DNSType = 21 + TypeNSAPPTR DNSType = 23 + TypeSIG DNSType = 24 + TypeKEY DNSType = 25 + TypePX DNSType = 26 + TypeGPOS DNSType = 27 + TypeAAAA DNSType = 28 // IPv6 + TypeLOC DNSType = 29 + TypeNXT DNSType = 30 + TypeEID DNSType = 31 + TypeNIMLOC DNSType = 32 + TypeSRV DNSType = 33 + TypeATMA DNSType = 34 + TypeNAPTR DNSType = 35 + TypeKX DNSType = 36 + TypeCERT DNSType = 37 + TypeDNAME DNSType = 39 + TypeOPT DNSType = 41 // EDNS + TypeAPL DNSType = 42 + TypeDS DNSType = 43 + TypeSSHFP DNSType = 44 + TypeIPSECKEY DNSType = 45 + TypeRRSIG DNSType = 46 + TypeNSEC DNSType = 47 + TypeDNSKEY DNSType = 48 + TypeDHCID DNSType = 49 + TypeNSEC3 DNSType = 50 + TypeNSEC3PARAM DNSType = 51 + TypeTLSA DNSType = 52 + TypeSMIMEA DNSType = 53 + TypeHIP DNSType = 55 + TypeNINFO DNSType = 56 + TypeRKEY DNSType = 57 + TypeTALINK DNSType = 58 + TypeCDS DNSType = 59 + TypeCDNSKEY DNSType = 60 + TypeOPENPGPKEY DNSType = 61 + TypeCSYNC DNSType = 62 + TypeZONEMD DNSType = 63 + TypeSVCB DNSType = 64 + TypeHTTPS DNSType = 65 + TypeSPF DNSType = 99 + TypeUINFO DNSType = 100 + TypeUID DNSType = 101 + TypeGID DNSType = 102 + TypeUNSPEC DNSType = 103 + TypeNID DNSType = 104 + TypeL32 DNSType = 105 + TypeL64 DNSType = 106 + TypeLP DNSType = 107 + TypeEUI48 DNSType = 108 + TypeEUI64 DNSType = 109 + TypeURI DNSType = 256 + TypeCAA DNSType = 257 + TypeAVC DNSType = 258 + TypeAMTRELAY DNSType = 260 + + TypeTKEY DNSType = 249 + TypeTSIG DNSType = 250 + + // valid Question.Qtype only + TypeIXFR DNSType = 251 + TypeAXFR DNSType = 252 + TypeMAILB DNSType = 253 + TypeMAILA DNSType = 254 + TypeANY DNSType = 255 + + TypeTA DNSType = 32768 + TypeDLV DNSType = 32769 + TypeReserved DNSType = 65535 +) + +type DNSQuestion struct { + Name string `json:"name"` + Type DNSType `json:"type"` +} + +type DNSAnswer struct { + Name string `json:"name"` + Type DNSType `json:"type"` + Ttl uint32 `json:"TTL"` + Data string `json:"data"` +} + +// Ref: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 +type DNSResponse struct { + Status uint16 + TC bool + RD bool + RA bool + AD bool + CD bool + Question []DNSQuestion `json:",omitempty"` + Answer []DNSAnswer `json:",omitempty"` + Authority []DNSAnswer `json:",omitempty"` + Additional []DNSAnswer `json:",omitempty"` +} + +type DNSParams struct { + Name string `url:"name"` + Type *DNSType `url:"type,omitempty"` + Do *bool `url:"do,omitempty"` + Cd *bool `url:"cd,omitempty"` +} + +// Performs DNS lookup via HTTPS, in case firewall blocks native netgo resolver. +// Ref: https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/make-api-requests/dns-json +func (c *CloudflareAPI) DNSQuery(ctx context.Context, params DNSParams) (*DNSResponse, error) { + values, err := query.Values(params) + if err != nil { + return nil, errors.Errorf("failed to encode query params: %w", err) + } + resp, err := c.Send(ctx, http.MethodGet, "/dns-query?"+values.Encode(), nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + return fetcher.ParseJSON[DNSResponse](resp.Body) +} diff --git a/internal/utils/config.go b/internal/utils/config.go index 2174fdf61..c68a1fb33 100644 --- a/internal/utils/config.go +++ b/internal/utils/config.go @@ -4,6 +4,7 @@ import ( "bytes" _ "embed" "fmt" + "net/url" "os" "path/filepath" "regexp" @@ -267,7 +268,7 @@ type ( api struct { Enabled bool `toml:"enabled"` Image string `toml:"-"` - Port uint `toml:"port"` + Port uint16 `toml:"port"` Schemas []string `toml:"schemas"` ExtraSearchPath []string `toml:"extra_search_path"` MaxRows uint `toml:"max_rows"` @@ -275,8 +276,8 @@ type ( db struct { Image string `toml:"-"` - Port uint `toml:"port"` - ShadowPort uint `toml:"shadow_port"` + Port uint16 `toml:"port"` + ShadowPort uint16 `toml:"shadow_port"` MajorVersion uint `toml:"major_version"` Password string `toml:"-"` RootKey string `toml:"-" mapstructure:"root_key"` @@ -303,16 +304,16 @@ type ( studio struct { Enabled bool `toml:"enabled"` - Port uint `toml:"port"` + Port uint16 `toml:"port"` ApiUrl string `toml:"api_url"` OpenaiApiKey string `toml:"openai_api_key"` } inbucket struct { - Enabled bool `toml:"enabled"` - Port uint `toml:"port"` - SmtpPort uint `toml:"smtp_port"` - Pop3Port uint `toml:"pop3_port"` + Enabled bool `toml:"enabled"` + Port uint16 `toml:"port"` + SmtpPort uint16 `toml:"smtp_port"` + Pop3Port uint16 `toml:"pop3_port"` } storage struct { @@ -387,11 +388,14 @@ type ( MFAVerificationAttempt hookConfig `toml:"mfa_verification_attempt"` PasswordVerificationAttempt hookConfig `toml:"password_verification_attempt"` CustomAccessToken hookConfig `toml:"custom_access_token"` + SendSMS hookConfig `toml:"send_sms"` + SendEmail hookConfig `toml:"send_email"` } hookConfig struct { Enabled bool `toml:"enabled"` URI string `toml:"uri"` + Secrets string `toml:"secrets"` } twilioConfig struct { @@ -460,6 +464,24 @@ type ( // } ) +func (h *hookConfig) HandleHook(hookType string) error { + // If not enabled do nothing + if !h.Enabled { + return nil + } + if h.URI == "" { + return errors.Errorf("missing required field in config: auth.hook.%s.uri", hookType) + } + if err := validateHookURI(h.URI, hookType); err != nil { + return err + } + var err error + if h.Secrets, err = maybeLoadEnv(h.Secrets); err != nil { + return errors.Errorf("missing required field in config: auth.hook.%s.secrets", hookType) + } + return nil +} + func LoadConfigFS(fsys afero.Fs) error { // Load default values var buf bytes.Buffer @@ -687,25 +709,21 @@ func LoadConfigFS(fsys afero.Fs) error { return err } } - - if Config.Auth.Hook.MFAVerificationAttempt.Enabled { - if Config.Auth.Hook.MFAVerificationAttempt.URI == "" { - return errors.New("Missing required field in config: auth.hook.mfa_verification_atempt.uri") - } + if err := Config.Auth.Hook.MFAVerificationAttempt.HandleHook("mfa_verification_attempt"); err != nil { + return err } - - if Config.Auth.Hook.PasswordVerificationAttempt.Enabled { - if Config.Auth.Hook.PasswordVerificationAttempt.URI == "" { - return errors.New("Missing required field in config: auth.hook.password_verification_attempt.uri") - } + if err := Config.Auth.Hook.PasswordVerificationAttempt.HandleHook("password_verification_attempt"); err != nil { + return err } - - if Config.Auth.Hook.CustomAccessToken.Enabled { - if Config.Auth.Hook.CustomAccessToken.URI == "" { - return errors.New("Missing required field in config: auth.hook.custom_access_token.uri") - } + if err := Config.Auth.Hook.CustomAccessToken.HandleHook("custom_access_token"); err != nil { + return err + } + if err := Config.Auth.Hook.SendSMS.HandleHook("send_sms"); err != nil { + return err + } + if err := Config.Auth.Hook.SendEmail.HandleHook("send_email"); err != nil { + return err } - // Validate oauth config for ext, provider := range Config.Auth.External { if !provider.Enabled { @@ -862,3 +880,14 @@ func loadEnvIfExists(path string) error { } return nil } + +func validateHookURI(uri, hookName string) error { + parsed, err := url.Parse(uri) + if err != nil { + return errors.Errorf("failed to parse template url: %w", err) + } + if !(parsed.Scheme == "http" || parsed.Scheme == "https" || parsed.Scheme == "pg-functions") { + return errors.Errorf("Invalid HTTP hook config: auth.hook.%v should be a Postgres function URI, or a HTTP or HTTPS URL", hookName) + } + return nil +} diff --git a/internal/utils/config_test.go b/internal/utils/config_test.go index 5a0c5d5a8..9dae64279 100644 --- a/internal/utils/config_test.go +++ b/internal/utils/config_test.go @@ -41,6 +41,7 @@ func TestConfigParsing(t *testing.T) { t.Setenv("TWILIO_AUTH_TOKEN", "token") t.Setenv("AZURE_CLIENT_ID", "hello") t.Setenv("AZURE_SECRET", "this is cool") + t.Setenv("AUTH_SEND_SMS_SECRETS", "v1,whsec_aWxpa2VzdXBhYmFzZXZlcnltdWNoYW5kaWhvcGV5b3Vkb3Rvbw==") assert.NoError(t, LoadConfigFS(fsys)) // Check error assert.Equal(t, "hello", Config.Auth.External["azure"].ClientId) @@ -155,3 +156,58 @@ func TestSigningJWT(t *testing.T) { assert.Equal(t, defaultServiceRoleKey, signed) }) } + +func TestValidateHookURI(t *testing.T) { + tests := []struct { + name string + uri string + hookName string + shouldErr bool + errorMsg string + }{ + { + name: "valid http URL", + uri: "http://example.com", + hookName: "testHook", + shouldErr: false, + }, + { + name: "valid https URL", + uri: "https://example.com", + hookName: "testHook", + shouldErr: false, + }, + { + name: "valid pg-functions URI", + uri: "pg-functions://functionName", + hookName: "pgHook", + shouldErr: false, + }, + { + name: "invalid URI with unsupported scheme", + uri: "ftp://example.com", + hookName: "malformedHook", + shouldErr: true, + errorMsg: "Invalid HTTP hook config: auth.hook.malformedHook should be a Postgres function URI, or a HTTP or HTTPS URL", + }, + { + name: "invalid URI with parsing error", + uri: "http://a b.com", + hookName: "errorHook", + shouldErr: true, + errorMsg: "failed to parse template url: parse \"http://a b.com\": invalid character \" \" in host name", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateHookURI(tt.uri, tt.hookName) + if tt.shouldErr { + assert.Error(t, err, "Expected an error for %v", tt.name) + assert.EqualError(t, err, tt.errorMsg, "Expected error message does not match for %v", tt.name) + } else { + assert.NoError(t, err, "Expected no error for %v", tt.name) + } + }) + } +} diff --git a/internal/utils/connect.go b/internal/utils/connect.go index cf7c5d9fc..b3c44ad42 100644 --- a/internal/utils/connect.go +++ b/internal/utils/connect.go @@ -98,7 +98,7 @@ func ConnectLocalPostgres(ctx context.Context, config pgconn.Config, options ... config.Host = Config.Hostname } if config.Port == 0 { - config.Port = uint16(Config.Db.Port) + config.Port = Config.Db.Port } if len(config.User) == 0 { config.User = "postgres" @@ -155,5 +155,5 @@ func ConnectByConfig(ctx context.Context, config pgconn.Config, options ...func( } func IsLocalDatabase(config pgconn.Config) bool { - return config.Host == Config.Hostname && config.Port == uint16(Config.Db.Port) + return config.Host == Config.Hostname && config.Port == Config.Db.Port } diff --git a/internal/utils/connect_test.go b/internal/utils/connect_test.go index 92b77ff1c..3749c59b8 100644 --- a/internal/utils/connect_test.go +++ b/internal/utils/connect_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/supabase/cli/internal/testing/apitest" "github.com/supabase/cli/internal/testing/pgtest" + "github.com/supabase/cli/internal/utils/cloudflare" "gopkg.in/h2non/gock.v1" ) @@ -41,16 +42,16 @@ func TestConnectByConfig(t *testing.T) { MatchParam("name", dbConfig.Host). MatchHeader("accept", "application/dns-json"). Reply(http.StatusOK). - JSON(&dnsResponse{Answer: []dnsAnswer{ - {Type: dnsIPv4Type, Data: "127.0.0.1"}, + JSON(&cloudflare.DNSResponse{Answer: []cloudflare.DNSAnswer{ + {Type: cloudflare.TypeA, Data: "127.0.0.1"}, }}) gock.New("https://1.1.1.1"). Get("/dns-query"). MatchParam("name", dbConfig.Host). MatchHeader("accept", "application/dns-json"). Reply(http.StatusOK). - JSON(&dnsResponse{Answer: []dnsAnswer{ - {Type: dnsIPv4Type, Data: "127.0.0.1"}, + JSON(&cloudflare.DNSResponse{Answer: []cloudflare.DNSAnswer{ + {Type: cloudflare.TypeA, Data: "127.0.0.1"}, }}) // Setup mock postgres conn := pgtest.NewConn() diff --git a/internal/utils/docker.go b/internal/utils/docker.go index 45d288141..5cb397bd6 100644 --- a/internal/utils/docker.go +++ b/internal/utils/docker.go @@ -49,17 +49,6 @@ func NewDocker() *client.Client { return cli.Client().(*client.Client) } -func AssertDockerIsRunning(ctx context.Context) error { - if _, err := Docker.Ping(ctx); err != nil { - if client.IsErrConnectionFailed(err) { - CmdSuggestion = suggestDockerInstall - } - return errors.Errorf("failed to ping docker daemon: %w", err) - } - - return nil -} - const ( CliProjectLabel = "com.supabase.cli.project" composeProjectLabel = "com.docker.compose.project" diff --git a/internal/utils/flags/db_url.go b/internal/utils/flags/db_url.go index 602d2974c..629e0332a 100644 --- a/internal/utils/flags/db_url.go +++ b/internal/utils/flags/db_url.go @@ -63,7 +63,7 @@ func ParseDatabaseConfig(flagSet *pflag.FlagSet, fsys afero.Fs) error { } // Ignore other PG settings DbConfig.Host = utils.Config.Hostname - DbConfig.Port = uint16(utils.Config.Db.Port) + DbConfig.Port = utils.Config.Db.Port DbConfig.User = "postgres" DbConfig.Password = utils.Config.Db.Password DbConfig.Database = "postgres" diff --git a/internal/utils/flags/project_ref_test.go b/internal/utils/flags/project_ref_test.go index 0b6d5db8f..699419a88 100644 --- a/internal/utils/flags/project_ref_test.go +++ b/internal/utils/flags/project_ref_test.go @@ -74,7 +74,7 @@ func TestProjectPrompt(t *testing.T) { gock.New(utils.DefaultApiHost). Get("/v1/projects"). Reply(http.StatusOK). - JSON([]api.ProjectResponse{{ + JSON([]api.V1ProjectResponse{{ Id: "test-project", Name: "My Project", OrganizationId: "test-org", diff --git a/internal/utils/http.go b/internal/utils/http.go deleted file mode 100644 index c7d10dbf4..000000000 --- a/internal/utils/http.go +++ /dev/null @@ -1,117 +0,0 @@ -package utils - -import ( - "bytes" - "context" - "encoding/json" - "io" - "net/http" - "time" - - "github.com/go-errors/errors" - "github.com/spf13/afero" - openapi "github.com/supabase/cli/pkg/api" -) - -var httpClient = http.Client{Timeout: 10 * time.Second} - -func JsonResponse[T any](ctx context.Context, method, url string, reqBody any, reqEditors ...openapi.RequestEditorFn) (*T, error) { - var body bytes.Buffer - if reqBody != nil { - enc := json.NewEncoder(&body) - if err := enc.Encode(reqBody); err != nil { - return nil, errors.Errorf("failed to encode request body: %w", err) - } - reqEditors = append(reqEditors, func(ctx context.Context, req *http.Request) error { - req.Header.Set("Content-Type", "application/json") - return nil - }) - } - // Creates request - req, err := http.NewRequestWithContext(ctx, method, url, &body) - if err != nil { - return nil, errors.Errorf("failed to initialise http request: %w", err) - } - for _, edit := range reqEditors { - if err := edit(ctx, req); err != nil { - return nil, err - } - } - req.Header.Set("User-Agent", "SupabaseCLI/"+Version) - // Sends request - resp, err := httpClient.Do(req) - if err != nil { - return nil, errors.Errorf("failed to execute http request: %w", err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - data, err := io.ReadAll(resp.Body) - if err != nil { - return nil, errors.Errorf("failed to read response body: %w", err) - } - return nil, errors.Errorf("Error status %d: %s", resp.StatusCode, data) - } - // Parses response - var data T - dec := json.NewDecoder(resp.Body) - if err := dec.Decode(&data); err != nil { - return nil, errors.Errorf("failed to parse response body: %w", err) - } - return &data, nil -} - -func TextResponse(ctx context.Context, method, url string, body io.Reader, reqEditors ...openapi.RequestEditorFn) (string, error) { - req, err := http.NewRequestWithContext(ctx, method, url, body) - if err != nil { - return "", errors.Errorf("failed to initialise http request: %w", err) - } - for _, edit := range reqEditors { - if err := edit(ctx, req); err != nil { - return "", err - } - } - req.Header.Set("User-Agent", "SupabaseCLI/"+Version) - // Sends request - resp, err := httpClient.Do(req) - if err != nil { - return "", errors.Errorf("failed to execute http request: %w", err) - } - defer resp.Body.Close() - data, err := io.ReadAll(resp.Body) - if err != nil { - return "", errors.Errorf("failed to read response body: %w", err) - } - if resp.StatusCode != http.StatusOK { - return "", errors.Errorf("Error status %d: %s", resp.StatusCode, body) - } - return string(data), nil -} - -func DownloadFile(ctx context.Context, path, url string, fsys afero.Fs, reqEditors ...openapi.RequestEditorFn) error { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return errors.Errorf("failed to initialise http request: %w", err) - } - for _, edit := range reqEditors { - if err := edit(ctx, req); err != nil { - return err - } - } - // Sends request - resp, err := httpClient.Do(req) - if err != nil { - return errors.Errorf("failed to send http request: %w", err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - data, err := io.ReadAll(resp.Body) - if err != nil { - return errors.Errorf("Error status %d: %w", resp.StatusCode, err) - } - return errors.Errorf("Error status %d: %s", resp.StatusCode, data) - } - if err := afero.WriteReader(fsys, path, resp.Body); err != nil { - return errors.Errorf("failed to write file: %w", err) - } - return nil -} diff --git a/internal/utils/misc.go b/internal/utils/misc.go index 75d56646e..a1927cf63 100644 --- a/internal/utils/misc.go +++ b/internal/utils/misc.go @@ -34,9 +34,9 @@ const ( DifferImage = "supabase/pgadmin-schema-diff:cli-0.0.5" MigraImage = "supabase/migra:3.0.1663481299" PgmetaImage = "supabase/postgres-meta:v0.80.0" - StudioImage = "supabase/studio:20240422-5cf8f30" + StudioImage = "supabase/studio:20240506-2976cd6" ImageProxyImage = "darthsim/imgproxy:v3.8.0" - EdgeRuntimeImage = "supabase/edge-runtime:v1.45.2" + EdgeRuntimeImage = "supabase/edge-runtime:v1.49.0" VectorImage = "timberio/vector:0.28.1-alpine" PgbouncerImage = "bitnami/pgbouncer:1.20.1-debian-11-r39" PgProveImage = "supabase/pg_prove:3.36" @@ -158,6 +158,7 @@ var ( "supabase_auth_admin", "supabase_functions_admin", "supabase_read_only_user", + "supabase_realtime_admin", "supabase_replication_admin", "supabase_storage_admin", // Managed by extensions @@ -187,6 +188,7 @@ var ( RestVersionPath = filepath.Join(TempDir, "rest-version") StorageVersionPath = filepath.Join(TempDir, "storage-version") CurrBranchPath = filepath.Join(SupabaseDirPath, ".branches", "_current_branch") + SchemasDir = filepath.Join(SupabaseDirPath, "schemas") MigrationsDir = filepath.Join(SupabaseDirPath, "migrations") FunctionsDir = filepath.Join(SupabaseDirPath, "functions") FallbackImportMapPath = filepath.Join(FunctionsDir, "import_map.json") diff --git a/internal/utils/misc_test.go b/internal/utils/misc_test.go index f22921d31..6472c2145 100644 --- a/internal/utils/misc_test.go +++ b/internal/utils/misc_test.go @@ -25,18 +25,33 @@ func (m *MockFs) Stat(name string) (fs.FileInfo, error) { } func TestProjectRoot(t *testing.T) { - t.Run("searches project root recursively", func(t *testing.T) { + root := string(filepath.Separator) + + t.Run("stops at root dir", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() - _, err := fsys.Create(filepath.Join("/", ConfigPath)) + _, err := fsys.Create(filepath.Join(root, ConfigPath)) require.NoError(t, err) // Run test - path := getProjectRoot("/home/user/project", fsys) + cwd := filepath.Join(root, "home", "user", "project") + path := getProjectRoot(cwd, fsys) // Check error - assert.Equal(t, "/", path) + assert.Equal(t, root, path) }) - t.Run("stops at root dir", func(t *testing.T) { + t.Run("stops at closest parent", func(t *testing.T) { + // Setup in-memory fs + fsys := afero.NewMemMapFs() + _, err := fsys.Create(filepath.Join(root, "supabase", ConfigPath)) + require.NoError(t, err) + // Run test + cwd := filepath.Join(root, "supabase", "supabase", "functions") + path := getProjectRoot(cwd, fsys) + // Check error + assert.Equal(t, filepath.Join(root, "supabase"), path) + }) + + t.Run("ignores error on config not found", func(t *testing.T) { cwd, err := os.Getwd() require.NoError(t, err) // Setup in-memory fs diff --git a/internal/utils/templates/init_config.test.toml b/internal/utils/templates/init_config.test.toml index f485c5218..aab9265d4 100644 --- a/internal/utils/templates/init_config.test.toml +++ b/internal/utils/templates/init_config.test.toml @@ -123,6 +123,11 @@ max_frequency = "5s" enabled = true uri = "pg-functions://postgres/auth/custom-access-token-hook" +[auth.hook.send_sms] +enabled = true +uri = "http://host.docker.internal/functions/v1/send_sms" +secrets = "env(AUTH_SEND_SMS_SECRETS)" + # Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`. [auth.sms.twilio] diff --git a/internal/utils/tenant/client.go b/internal/utils/tenant/client.go index eea5625a3..72c01605b 100644 --- a/internal/utils/tenant/client.go +++ b/internal/utils/tenant/client.go @@ -3,17 +3,15 @@ package tenant import ( "context" "net/http" - "sync" + "time" "github.com/go-errors/errors" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/pkg/api" + "github.com/supabase/cli/pkg/fetcher" ) var ( - apiKey ApiKey - keyOnce sync.Once - ErrAuthToken = errors.New("Authorization failed for the access token and project ref pair") errMissingKey = errors.New("Anon key not found.") ) @@ -24,7 +22,7 @@ type ApiKey struct { } func (a ApiKey) IsEmpty() bool { - return len(apiKey.Anon) == 0 && len(apiKey.ServiceRole) == 0 + return len(a.Anon) == 0 && len(a.ServiceRole) == 0 } func NewApiKey(resp []api.ApiKeyResponse) ApiKey { @@ -40,49 +38,38 @@ func NewApiKey(resp []api.ApiKeyResponse) ApiKey { return result } -func SetApiKeys(keys ApiKey) { - keyOnce.Do(func() { - apiKey = keys - }) -} - func GetApiKeys(ctx context.Context, projectRef string) (ApiKey, error) { - var errKey error - keyOnce.Do(func() { - resp, err := utils.GetSupabase().GetProjectApiKeysWithResponse(ctx, projectRef) - if err != nil { - errKey = errors.Errorf("failed to get api keys: %w", err) - return - } - if resp.JSON200 == nil { - errKey = errors.Errorf("%w: %s", ErrAuthToken, string(resp.Body)) - return - } - apiKey = NewApiKey(*resp.JSON200) - if apiKey.IsEmpty() { - errKey = errors.New(errMissingKey) - } - }) - return apiKey, errKey -} - -func GetJsonResponse[T any](ctx context.Context, url, apiKey string) (*T, error) { - return utils.JsonResponse[T](ctx, http.MethodGet, url, nil, func(ctx context.Context, req *http.Request) error { - req.Header.Add("apikey", apiKey) - return nil - }) + resp, err := utils.GetSupabase().GetProjectApiKeysWithResponse(ctx, projectRef) + if err != nil { + return ApiKey{}, errors.Errorf("failed to get api keys: %w", err) + } + if resp.JSON200 == nil { + return ApiKey{}, errors.Errorf("%w: %s", ErrAuthToken, string(resp.Body)) + } + keys := NewApiKey(*resp.JSON200) + if keys.IsEmpty() { + return ApiKey{}, errors.New(errMissingKey) + } + return keys, nil } -func JsonResponseWithBearer[T any](ctx context.Context, method, url, token string, reqBody any) (*T, error) { - return utils.JsonResponse[T](ctx, method, url, reqBody, func(ctx context.Context, req *http.Request) error { - req.Header.Add("Authorization", "Bearer "+token) - return nil - }) +type TenantAPI struct { + *fetcher.Fetcher } -func GetTextResponse(ctx context.Context, url, apiKey string) (string, error) { - return utils.TextResponse(ctx, http.MethodGet, url, nil, func(ctx context.Context, req *http.Request) error { - req.Header.Add("apikey", apiKey) - return nil - }) +func NewTenantAPI(ctx context.Context, projectRef, anonKey string) TenantAPI { + server := "https://" + utils.GetSupabaseHost(projectRef) + client := &http.Client{ + Timeout: 10 * time.Second, + } + header := func(req *http.Request) { + req.Header.Add("apikey", anonKey) + } + api := TenantAPI{Fetcher: fetcher.NewFetcher( + server, + fetcher.WithHTTPClient(client), + fetcher.WithRequestEditor(header), + fetcher.WithUserAgent("SupabaseCLI/"+utils.Version), + )} + return api } diff --git a/internal/utils/tenant/client_test.go b/internal/utils/tenant/client_test.go deleted file mode 100644 index 7856a8355..000000000 --- a/internal/utils/tenant/client_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package tenant - -import ( - "context" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/supabase/cli/internal/utils" - "gopkg.in/h2non/gock.v1" -) - -func TestGetJSON(t *testing.T) { - t.Run("throws error on invalid url", func(t *testing.T) { - // Run test - data, err := GetJsonResponse[string](context.Background(), "http://h:p", "") - // Check error - assert.ErrorContains(t, err, "invalid port") - assert.Empty(t, data) - }) - - t.Run("throws error on server unavailable", func(t *testing.T) { - // Setup mock api - defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/"). - Reply(http.StatusServiceUnavailable) - // Run test - data, err := GetJsonResponse[string](context.Background(), utils.DefaultApiHost, "") - // Check error - assert.ErrorContains(t, err, "Error status 503") - assert.Empty(t, data) - }) - - t.Run("throws error on malformed json", func(t *testing.T) { - // Setup mock api - defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/"). - Reply(http.StatusOK). - JSON("malformed") - // Run test - data, err := GetJsonResponse[string](context.Background(), utils.DefaultApiHost, "") - // Check error - assert.ErrorContains(t, err, "invalid character") - assert.Empty(t, data) - }) -} diff --git a/internal/utils/tenant/gotrue.go b/internal/utils/tenant/gotrue.go index 8ff9a779e..4f3609389 100644 --- a/internal/utils/tenant/gotrue.go +++ b/internal/utils/tenant/gotrue.go @@ -2,10 +2,10 @@ package tenant import ( "context" - "fmt" + "net/http" "github.com/go-errors/errors" - "github.com/supabase/cli/internal/utils" + "github.com/supabase/cli/pkg/fetcher" ) var errGotrueVersion = errors.New("GoTrue version not found.") @@ -16,13 +16,13 @@ type HealthResponse struct { Description string `json:"description"` } -func GetGotrueVersion(ctx context.Context, projectRef string) (string, error) { - apiKey, err := GetApiKeys(ctx, projectRef) +func (t *TenantAPI) GetGotrueVersion(ctx context.Context) (string, error) { + resp, err := t.Send(ctx, http.MethodGet, "/auth/v1/health", nil) if err != nil { return "", err } - url := fmt.Sprintf("https://%s/auth/v1/health", utils.GetSupabaseHost(projectRef)) - data, err := GetJsonResponse[HealthResponse](ctx, url, apiKey.Anon) + defer resp.Body.Close() + data, err := fetcher.ParseJSON[HealthResponse](resp.Body) if err != nil { return "", err } diff --git a/internal/utils/tenant/gotrue_test.go b/internal/utils/tenant/gotrue_test.go index 5dda2661b..788979916 100644 --- a/internal/utils/tenant/gotrue_test.go +++ b/internal/utils/tenant/gotrue_test.go @@ -3,52 +3,41 @@ package tenant import ( "context" "errors" - "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" - "github.com/supabase/cli/internal/testing/apitest" - "github.com/supabase/cli/internal/utils" - "github.com/supabase/cli/pkg/api" + "github.com/supabase/cli/pkg/fetcher" "gopkg.in/h2non/gock.v1" ) -func TestGotrueVersion(t *testing.T) { - projectRef := apitest.RandomProjectRef() - token := apitest.RandomAccessToken(t) - t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) +var mockApi = TenantAPI{Fetcher: fetcher.NewFetcher( + "http://127.0.0.1", +)} +func TestGotrueVersion(t *testing.T) { t.Run("gets gotrue version", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{Name: "anon", ApiKey: "anon-key"}}) - gock.New(fmt.Sprintf("https://%s.supabase.co", projectRef)). + gock.New("http://127.0.0.1"). Get("/auth/v1/health"). Reply(http.StatusOK). JSON(HealthResponse{Version: "v2.92.1"}) // Run test - version, err := GetGotrueVersion(context.Background(), projectRef) + version, err := mockApi.GetGotrueVersion(context.Background()) // Check error assert.NoError(t, err) - assert.Equal(t, version, "v2.92.1") + assert.Equal(t, "v2.92.1", version) }) t.Run("throws error on network error", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{Name: "anon", ApiKey: "anon-key"}}) - gock.New(fmt.Sprintf("https://%s.supabase.co", projectRef)). + gock.New("http://127.0.0.1"). Get("/auth/v1/health"). ReplyError(errors.New("network error")) // Run test - version, err := GetGotrueVersion(context.Background(), projectRef) + version, err := mockApi.GetGotrueVersion(context.Background()) // Check error assert.ErrorContains(t, err, "network error") assert.Empty(t, version) @@ -57,16 +46,12 @@ func TestGotrueVersion(t *testing.T) { t.Run("throws error on missing version", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{Name: "anon", ApiKey: "anon-key"}}) - gock.New(fmt.Sprintf("https://%s.supabase.co", projectRef)). + gock.New("http://127.0.0.1"). Get("/auth/v1/health"). Reply(http.StatusOK). JSON(HealthResponse{}) // Run test - version, err := GetGotrueVersion(context.Background(), projectRef) + version, err := mockApi.GetGotrueVersion(context.Background()) // Check error assert.ErrorIs(t, err, errGotrueVersion) assert.Empty(t, version) diff --git a/internal/utils/tenant/postgrest.go b/internal/utils/tenant/postgrest.go index d0d7020b5..0560153d1 100644 --- a/internal/utils/tenant/postgrest.go +++ b/internal/utils/tenant/postgrest.go @@ -2,11 +2,11 @@ package tenant import ( "context" - "fmt" + "net/http" "strings" "github.com/go-errors/errors" - "github.com/supabase/cli/internal/utils" + "github.com/supabase/cli/pkg/fetcher" ) var errPostgrestVersion = errors.New("PostgREST version not found.") @@ -22,13 +22,13 @@ type SwaggerResponse struct { Info SwaggerInfo `json:"info"` } -func GetPostgrestVersion(ctx context.Context, projectRef string) (string, error) { - apiKey, err := GetApiKeys(ctx, projectRef) +func (t *TenantAPI) GetPostgrestVersion(ctx context.Context) (string, error) { + resp, err := t.Send(ctx, http.MethodGet, "/rest/v1/", nil) if err != nil { return "", err } - url := fmt.Sprintf("https://%s/rest/v1/", utils.GetSupabaseHost(projectRef)) - data, err := GetJsonResponse[SwaggerResponse](ctx, url, apiKey.Anon) + defer resp.Body.Close() + data, err := fetcher.ParseJSON[SwaggerResponse](resp.Body) if err != nil { return "", err } diff --git a/internal/utils/tenant/postgrest_test.go b/internal/utils/tenant/postgrest_test.go index 7093e2669..40999b9ad 100644 --- a/internal/utils/tenant/postgrest_test.go +++ b/internal/utils/tenant/postgrest_test.go @@ -3,70 +3,50 @@ package tenant import ( "context" "errors" - "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" - "github.com/supabase/cli/internal/testing/apitest" - "github.com/supabase/cli/internal/utils" - "github.com/supabase/cli/pkg/api" "gopkg.in/h2non/gock.v1" ) func TestPostgrestVersion(t *testing.T) { - projectRef := apitest.RandomProjectRef() - token := apitest.RandomAccessToken(t) - t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) - t.Run("appends prefix v", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{Name: "anon", ApiKey: "anon-key"}}) - gock.New(fmt.Sprintf("https://%s.supabase.co", projectRef)). + gock.New("http://127.0.0.1"). Get("/rest/v1/"). Reply(http.StatusOK). JSON(SwaggerResponse{Info: SwaggerInfo{Version: "11.1.0"}}) // Run test - version, err := GetPostgrestVersion(context.Background(), projectRef) + version, err := mockApi.GetPostgrestVersion(context.Background()) // Check error assert.NoError(t, err) - assert.Equal(t, version, "v11.1.0") + assert.Equal(t, "v11.1.0", version) }) t.Run("ignores commit hash", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{Name: "anon", ApiKey: "anon-key"}}) - gock.New(fmt.Sprintf("https://%s.supabase.co", projectRef)). + gock.New("http://127.0.0.1"). Get("/rest/v1/"). Reply(http.StatusOK). JSON(SwaggerResponse{Info: SwaggerInfo{Version: "11.2.0 (c820efb)"}}) // Run test - version, err := GetPostgrestVersion(context.Background(), projectRef) + version, err := mockApi.GetPostgrestVersion(context.Background()) // Check error assert.NoError(t, err) - assert.Equal(t, version, "v11.2.0") + assert.Equal(t, "v11.2.0", version) }) t.Run("throws error on network error", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{Name: "anon", ApiKey: "anon-key"}}) - gock.New(fmt.Sprintf("https://%s.supabase.co", projectRef)). + gock.New("http://127.0.0.1"). Get("/rest/v1/"). ReplyError(errors.New("network error")) // Run test - version, err := GetPostgrestVersion(context.Background(), projectRef) + version, err := mockApi.GetPostgrestVersion(context.Background()) // Check error assert.ErrorContains(t, err, "network error") assert.Empty(t, version) @@ -75,16 +55,12 @@ func TestPostgrestVersion(t *testing.T) { t.Run("throws error on missing version", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{Name: "anon", ApiKey: "anon-key"}}) - gock.New(fmt.Sprintf("https://%s.supabase.co", projectRef)). + gock.New("http://127.0.0.1"). Get("/rest/v1/"). Reply(http.StatusOK). JSON(SwaggerResponse{}) // Run test - version, err := GetPostgrestVersion(context.Background(), projectRef) + version, err := mockApi.GetPostgrestVersion(context.Background()) // Check error assert.ErrorIs(t, err, errPostgrestVersion) assert.Empty(t, version) diff --git a/internal/utils/tenant/storage.go b/internal/utils/tenant/storage.go index 7022b83f8..02d1490fc 100644 --- a/internal/utils/tenant/storage.go +++ b/internal/utils/tenant/storage.go @@ -2,26 +2,26 @@ package tenant import ( "context" - "fmt" + "io" + "net/http" "github.com/go-errors/errors" - "github.com/supabase/cli/internal/utils" ) var errStorageVersion = errors.New("Storage version not found.") -func GetStorageVersion(ctx context.Context, projectRef string) (string, error) { - apiKey, err := GetApiKeys(ctx, projectRef) +func (t *TenantAPI) GetStorageVersion(ctx context.Context) (string, error) { + resp, err := t.Send(ctx, http.MethodGet, "/storage/v1/version", nil) if err != nil { return "", err } - url := fmt.Sprintf("https://%s/storage/v1/version", utils.GetSupabaseHost(projectRef)) - data, err := GetTextResponse(ctx, url, apiKey.Anon) + defer resp.Body.Close() + data, err := io.ReadAll(resp.Body) if err != nil { - return "", err + return "", errors.Errorf("failed to read response body: %w", err) } - if len(data) == 0 || data == "0.0.0" { + if len(data) == 0 || string(data) == "0.0.0" { return "", errors.New(errStorageVersion) } - return "v" + data, nil + return "v" + string(data), nil } diff --git a/internal/utils/tenant/storage_test.go b/internal/utils/tenant/storage_test.go index bd1160391..0152e48bf 100644 --- a/internal/utils/tenant/storage_test.go +++ b/internal/utils/tenant/storage_test.go @@ -3,52 +3,36 @@ package tenant import ( "context" "errors" - "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" - "github.com/supabase/cli/internal/testing/apitest" - "github.com/supabase/cli/internal/utils" - "github.com/supabase/cli/pkg/api" "gopkg.in/h2non/gock.v1" ) func TestStorageVersion(t *testing.T) { - projectRef := apitest.RandomProjectRef() - token := apitest.RandomAccessToken(t) - t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) - t.Run("appends prefix v", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{Name: "anon", ApiKey: "anon-key"}}) - gock.New(fmt.Sprintf("https://%s.supabase.co", projectRef)). + gock.New("http://127.0.0.1"). Get("/storage/v1/version"). Reply(http.StatusOK). BodyString("0.40.4") // Run test - version, err := GetStorageVersion(context.Background(), projectRef) + version, err := mockApi.GetStorageVersion(context.Background()) // Check error assert.NoError(t, err) - assert.Equal(t, version, "v0.40.4") + assert.Equal(t, "v0.40.4", version) }) t.Run("throws error on network error", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{Name: "anon", ApiKey: "anon-key"}}) - gock.New(fmt.Sprintf("https://%s.supabase.co", projectRef)). + gock.New("http://127.0.0.1"). Get("/storage/v1/version"). ReplyError(errors.New("network error")) // Run test - version, err := GetStorageVersion(context.Background(), projectRef) + version, err := mockApi.GetStorageVersion(context.Background()) // Check error assert.ErrorContains(t, err, "network error") assert.Empty(t, version) @@ -57,16 +41,12 @@ func TestStorageVersion(t *testing.T) { t.Run("throws error on missing version", func(t *testing.T) { // Setup mock api defer gock.OffAll() - gock.New(utils.DefaultApiHost). - Get("/v1/projects/" + projectRef + "/api-keys"). - Reply(http.StatusOK). - JSON([]api.ApiKeyResponse{{Name: "anon", ApiKey: "anon-key"}}) - gock.New(fmt.Sprintf("https://%s.supabase.co", projectRef)). + gock.New("http://127.0.0.1"). Get("/storage/v1/version"). Reply(http.StatusOK). BodyString("0.0.0") // Run test - version, err := GetStorageVersion(context.Background(), projectRef) + version, err := mockApi.GetStorageVersion(context.Background()) // Check error assert.ErrorIs(t, err, errStorageVersion) assert.Empty(t, version) diff --git a/package.json b/package.json index 1c6f6b37e..749d1c7cd 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "bin-links": "^4.0.3", "https-proxy-agent": "^7.0.2", "node-fetch": "^3.3.2", - "tar": "7.0.1" + "tar": "7.1.0" }, "release": { "branches": [ diff --git a/pkg/api/client.gen.go b/pkg/api/client.gen.go index 0b540c74a..5fac1042a 100644 --- a/pkg/api/client.gen.go +++ b/pkg/api/client.gen.go @@ -100,6 +100,9 @@ type ClientInterface interface { UpdateBranch(ctx context.Context, branchId string, body UpdateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // ResetBranch request + ResetBranch(ctx context.Context, branchId string, reqEditors ...RequestEditorFn) (*http.Response, error) + // Authorize request Authorize(ctx context.Context, params *AuthorizeParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -174,6 +177,20 @@ type ClientInterface interface { UpdateProviderById(ctx context.Context, ref string, providerId string, body UpdateProviderByIdJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // ListTPAForProject request + ListTPAForProject(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateTPAForProjectWithBody request with any body + CreateTPAForProjectWithBody(ctx context.Context, ref string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateTPAForProject(ctx context.Context, ref string, body CreateTPAForProjectJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // DeleteTPAForProject request + DeleteTPAForProject(ctx context.Context, ref string, tpaId string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetTPAForProject request + GetTPAForProject(ctx context.Context, ref string, tpaId string, reqEditors ...RequestEditorFn) (*http.Response, error) + // V1GetPgbouncerConfig request V1GetPgbouncerConfig(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -400,6 +417,18 @@ func (c *Client) UpdateBranch(ctx context.Context, branchId string, body UpdateB return c.Client.Do(req) } +func (c *Client) ResetBranch(ctx context.Context, branchId string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewResetBranchRequest(c.Server, branchId) + 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) Authorize(ctx context.Context, params *AuthorizeParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewAuthorizeRequest(c.Server, params) if err != nil { @@ -724,6 +753,66 @@ func (c *Client) UpdateProviderById(ctx context.Context, ref string, providerId return c.Client.Do(req) } +func (c *Client) ListTPAForProject(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListTPAForProjectRequest(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) CreateTPAForProjectWithBody(ctx context.Context, ref string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateTPAForProjectRequestWithBody(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) CreateTPAForProject(ctx context.Context, ref string, body CreateTPAForProjectJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateTPAForProjectRequest(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) DeleteTPAForProject(ctx context.Context, ref string, tpaId string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteTPAForProjectRequest(c.Server, ref, tpaId) + 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) GetTPAForProject(ctx context.Context, ref string, tpaId string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetTPAForProjectRequest(c.Server, ref, tpaId) + 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) V1GetPgbouncerConfig(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewV1GetPgbouncerConfigRequest(c.Server, ref) if err != nil { @@ -1619,6 +1708,40 @@ func NewUpdateBranchRequestWithBody(server string, branchId string, contentType return req, nil } +// NewResetBranchRequest generates requests for ResetBranch +func NewResetBranchRequest(server string, branchId string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "branch_id", runtime.ParamLocationPath, branchId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/branches/%s/reset", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewAuthorizeRequest generates requests for Authorize func NewAuthorizeRequest(server string, params *AuthorizeParams) (*http.Request, error) { var err error @@ -2491,6 +2614,169 @@ func NewUpdateProviderByIdRequestWithBody(server string, ref string, providerId return req, nil } +// NewListTPAForProjectRequest generates requests for ListTPAForProject +func NewListTPAForProjectRequest(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/auth/third-party-auth", 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 +} + +// NewCreateTPAForProjectRequest calls the generic CreateTPAForProject builder with application/json body +func NewCreateTPAForProjectRequest(server string, ref string, body CreateTPAForProjectJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateTPAForProjectRequestWithBody(server, ref, "application/json", bodyReader) +} + +// NewCreateTPAForProjectRequestWithBody generates requests for CreateTPAForProject with any type of body +func NewCreateTPAForProjectRequestWithBody(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/auth/third-party-auth", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewDeleteTPAForProjectRequest generates requests for DeleteTPAForProject +func NewDeleteTPAForProjectRequest(server string, ref string, tpaId 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 + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "tpa_id", runtime.ParamLocationPath, tpaId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/projects/%s/config/auth/third-party-auth/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetTPAForProjectRequest generates requests for GetTPAForProject +func NewGetTPAForProjectRequest(server string, ref string, tpaId 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 + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "tpa_id", runtime.ParamLocationPath, tpaId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/v1/projects/%s/config/auth/third-party-auth/%s", pathParam0, pathParam1) + 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 +} + // NewV1GetPgbouncerConfigRequest generates requests for V1GetPgbouncerConfig func NewV1GetPgbouncerConfigRequest(server string, ref string) (*http.Request, error) { var err error @@ -4680,6 +4966,9 @@ type ClientWithResponsesInterface interface { UpdateBranchWithResponse(ctx context.Context, branchId string, body UpdateBranchJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateBranchResponse, error) + // ResetBranchWithResponse request + ResetBranchWithResponse(ctx context.Context, branchId string, reqEditors ...RequestEditorFn) (*ResetBranchResponse, error) + // AuthorizeWithResponse request AuthorizeWithResponse(ctx context.Context, params *AuthorizeParams, reqEditors ...RequestEditorFn) (*AuthorizeResponse, error) @@ -4754,6 +5043,20 @@ type ClientWithResponsesInterface interface { UpdateProviderByIdWithResponse(ctx context.Context, ref string, providerId string, body UpdateProviderByIdJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateProviderByIdResponse, error) + // ListTPAForProjectWithResponse request + ListTPAForProjectWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*ListTPAForProjectResponse, error) + + // CreateTPAForProjectWithBodyWithResponse request with any body + CreateTPAForProjectWithBodyWithResponse(ctx context.Context, ref string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateTPAForProjectResponse, error) + + CreateTPAForProjectWithResponse(ctx context.Context, ref string, body CreateTPAForProjectJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateTPAForProjectResponse, error) + + // DeleteTPAForProjectWithResponse request + DeleteTPAForProjectWithResponse(ctx context.Context, ref string, tpaId string, reqEditors ...RequestEditorFn) (*DeleteTPAForProjectResponse, error) + + // GetTPAForProjectWithResponse request + GetTPAForProjectWithResponse(ctx context.Context, ref string, tpaId string, reqEditors ...RequestEditorFn) (*GetTPAForProjectResponse, error) + // V1GetPgbouncerConfigWithResponse request V1GetPgbouncerConfigWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1GetPgbouncerConfigResponse, error) @@ -4935,6 +5238,7 @@ type ClientWithResponsesInterface interface { type DeleteBranchResponse struct { Body []byte HTTPResponse *http.Response + JSON200 *BranchDeleteResponse } // Status returns HTTPResponse.Status @@ -4997,6 +5301,28 @@ func (r UpdateBranchResponse) StatusCode() int { return 0 } +type ResetBranchResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *BranchResetResponse +} + +// Status returns HTTPResponse.Status +func (r ResetBranchResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ResetBranchResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type AuthorizeResponse struct { Body []byte HTTPResponse *http.Response @@ -5131,7 +5457,7 @@ func (r V1ListOrganizationMembersResponse) StatusCode() int { type GetProjectsResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]ProjectResponse + JSON200 *[]V1ProjectResponse } // Status returns HTTPResponse.Status @@ -5153,7 +5479,7 @@ func (r GetProjectsResponse) StatusCode() int { type CreateProjectResponse struct { Body []byte HTTPResponse *http.Response - JSON201 *ProjectResponse + JSON201 *V1ProjectResponse } // Status returns HTTPResponse.Status @@ -5175,7 +5501,7 @@ func (r CreateProjectResponse) StatusCode() int { type DeleteProjectResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *ProjectRefResponse + JSON200 *V1ProjectRefResponse } // Status returns HTTPResponse.Status @@ -5435,6 +5761,94 @@ func (r UpdateProviderByIdResponse) StatusCode() int { return 0 } +type ListTPAForProjectResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]ThirdPartyAuth +} + +// Status returns HTTPResponse.Status +func (r ListTPAForProjectResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListTPAForProjectResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreateTPAForProjectResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *ThirdPartyAuth +} + +// Status returns HTTPResponse.Status +func (r CreateTPAForProjectResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateTPAForProjectResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type DeleteTPAForProjectResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *ThirdPartyAuth +} + +// Status returns HTTPResponse.Status +func (r DeleteTPAForProjectResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteTPAForProjectResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetTPAForProjectResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *ThirdPartyAuth +} + +// Status returns HTTPResponse.Status +func (r GetTPAForProjectResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetTPAForProjectResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type V1GetPgbouncerConfigResponse struct { Body []byte HTTPResponse *http.Response @@ -5829,7 +6243,7 @@ func (r GetFunctionBodyResponse) StatusCode() int { type CheckServiceHealthResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *[]ServiceHealthResponse + JSON200 *[]V1ServiceHealthResponse } // Status returns HTTPResponse.Status @@ -6004,7 +6418,7 @@ func (r GetPostgRESTConfigResponse) StatusCode() int { type UpdatePostgRESTConfigResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *PostgrestConfigResponse + JSON200 *V1PostgrestConfigResponse } // Status returns HTTPResponse.Status @@ -6493,6 +6907,15 @@ func (c *ClientWithResponses) UpdateBranchWithResponse(ctx context.Context, bran return ParseUpdateBranchResponse(rsp) } +// ResetBranchWithResponse request returning *ResetBranchResponse +func (c *ClientWithResponses) ResetBranchWithResponse(ctx context.Context, branchId string, reqEditors ...RequestEditorFn) (*ResetBranchResponse, error) { + rsp, err := c.ResetBranch(ctx, branchId, reqEditors...) + if err != nil { + return nil, err + } + return ParseResetBranchResponse(rsp) +} + // AuthorizeWithResponse request returning *AuthorizeResponse func (c *ClientWithResponses) AuthorizeWithResponse(ctx context.Context, params *AuthorizeParams, reqEditors ...RequestEditorFn) (*AuthorizeResponse, error) { rsp, err := c.Authorize(ctx, params, reqEditors...) @@ -6729,6 +7152,50 @@ func (c *ClientWithResponses) UpdateProviderByIdWithResponse(ctx context.Context return ParseUpdateProviderByIdResponse(rsp) } +// ListTPAForProjectWithResponse request returning *ListTPAForProjectResponse +func (c *ClientWithResponses) ListTPAForProjectWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*ListTPAForProjectResponse, error) { + rsp, err := c.ListTPAForProject(ctx, ref, reqEditors...) + if err != nil { + return nil, err + } + return ParseListTPAForProjectResponse(rsp) +} + +// CreateTPAForProjectWithBodyWithResponse request with arbitrary body returning *CreateTPAForProjectResponse +func (c *ClientWithResponses) CreateTPAForProjectWithBodyWithResponse(ctx context.Context, ref string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateTPAForProjectResponse, error) { + rsp, err := c.CreateTPAForProjectWithBody(ctx, ref, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateTPAForProjectResponse(rsp) +} + +func (c *ClientWithResponses) CreateTPAForProjectWithResponse(ctx context.Context, ref string, body CreateTPAForProjectJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateTPAForProjectResponse, error) { + rsp, err := c.CreateTPAForProject(ctx, ref, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateTPAForProjectResponse(rsp) +} + +// DeleteTPAForProjectWithResponse request returning *DeleteTPAForProjectResponse +func (c *ClientWithResponses) DeleteTPAForProjectWithResponse(ctx context.Context, ref string, tpaId string, reqEditors ...RequestEditorFn) (*DeleteTPAForProjectResponse, error) { + rsp, err := c.DeleteTPAForProject(ctx, ref, tpaId, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteTPAForProjectResponse(rsp) +} + +// GetTPAForProjectWithResponse request returning *GetTPAForProjectResponse +func (c *ClientWithResponses) GetTPAForProjectWithResponse(ctx context.Context, ref string, tpaId string, reqEditors ...RequestEditorFn) (*GetTPAForProjectResponse, error) { + rsp, err := c.GetTPAForProject(ctx, ref, tpaId, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetTPAForProjectResponse(rsp) +} + // V1GetPgbouncerConfigWithResponse request returning *V1GetPgbouncerConfigResponse func (c *ClientWithResponses) V1GetPgbouncerConfigWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*V1GetPgbouncerConfigResponse, error) { rsp, err := c.V1GetPgbouncerConfig(ctx, ref, reqEditors...) @@ -7309,6 +7776,16 @@ func ParseDeleteBranchResponse(rsp *http.Response) (*DeleteBranchResponse, error HTTPResponse: rsp, } + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest BranchDeleteResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + return response, nil } @@ -7364,6 +7841,32 @@ func ParseUpdateBranchResponse(rsp *http.Response) (*UpdateBranchResponse, error return response, nil } +// ParseResetBranchResponse parses an HTTP response from a ResetBranchWithResponse call +func ParseResetBranchResponse(rsp *http.Response) (*ResetBranchResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ResetBranchResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest BranchResetResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + // ParseAuthorizeResponse parses an HTTP response from a AuthorizeWithResponse call func ParseAuthorizeResponse(rsp *http.Response) (*AuthorizeResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -7525,7 +8028,7 @@ func ParseGetProjectsResponse(rsp *http.Response) (*GetProjectsResponse, error) switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []ProjectResponse + var dest []V1ProjectResponse if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -7551,7 +8054,7 @@ func ParseCreateProjectResponse(rsp *http.Response) (*CreateProjectResponse, err switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest ProjectResponse + var dest V1ProjectResponse if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -7577,7 +8080,7 @@ func ParseDeleteProjectResponse(rsp *http.Response) (*DeleteProjectResponse, err switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ProjectRefResponse + var dest V1ProjectRefResponse if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -7864,6 +8367,110 @@ func ParseUpdateProviderByIdResponse(rsp *http.Response) (*UpdateProviderByIdRes return response, nil } +// ParseListTPAForProjectResponse parses an HTTP response from a ListTPAForProjectWithResponse call +func ParseListTPAForProjectResponse(rsp *http.Response) (*ListTPAForProjectResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListTPAForProjectResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []ThirdPartyAuth + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseCreateTPAForProjectResponse parses an HTTP response from a CreateTPAForProjectWithResponse call +func ParseCreateTPAForProjectResponse(rsp *http.Response) (*CreateTPAForProjectResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateTPAForProjectResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest ThirdPartyAuth + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + +// ParseDeleteTPAForProjectResponse parses an HTTP response from a DeleteTPAForProjectWithResponse call +func ParseDeleteTPAForProjectResponse(rsp *http.Response) (*DeleteTPAForProjectResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteTPAForProjectResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest ThirdPartyAuth + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseGetTPAForProjectResponse parses an HTTP response from a GetTPAForProjectWithResponse call +func ParseGetTPAForProjectResponse(rsp *http.Response) (*GetTPAForProjectResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetTPAForProjectResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest ThirdPartyAuth + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseV1GetPgbouncerConfigResponse parses an HTTP response from a V1GetPgbouncerConfigWithResponse call func ParseV1GetPgbouncerConfigResponse(rsp *http.Response) (*V1GetPgbouncerConfigResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -8297,7 +8904,7 @@ func ParseCheckServiceHealthResponse(rsp *http.Response) (*CheckServiceHealthRes switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest []ServiceHealthResponse + var dest []V1ServiceHealthResponse if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -8495,7 +9102,7 @@ func ParseUpdatePostgRESTConfigResponse(rsp *http.Response) (*UpdatePostgRESTCon switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest PostgrestConfigResponse + var dest V1PostgrestConfigResponse if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } diff --git a/pkg/api/types.gen.go b/pkg/api/types.gen.go index 2ceaaa993..835f7e6bf 100644 --- a/pkg/api/types.gen.go +++ b/pkg/api/types.gen.go @@ -11,6 +11,7 @@ import ( const ( BearerScopes = "bearer.Scopes" + Oauth2Scopes = "oauth2.Scopes" ) // Defines values for BillingPlanId. @@ -44,31 +45,6 @@ const ( RUNNINGMIGRATIONS BranchResponseStatus = "RUNNING_MIGRATIONS" ) -// Defines values for CreateProjectBodyPlan. -const ( - CreateProjectBodyPlanFree CreateProjectBodyPlan = "free" - CreateProjectBodyPlanPro CreateProjectBodyPlan = "pro" -) - -// Defines values for CreateProjectBodyRegion. -const ( - CreateProjectBodyRegionApEast1 CreateProjectBodyRegion = "ap-east-1" - CreateProjectBodyRegionApNortheast1 CreateProjectBodyRegion = "ap-northeast-1" - CreateProjectBodyRegionApNortheast2 CreateProjectBodyRegion = "ap-northeast-2" - CreateProjectBodyRegionApSouth1 CreateProjectBodyRegion = "ap-south-1" - CreateProjectBodyRegionApSoutheast1 CreateProjectBodyRegion = "ap-southeast-1" - CreateProjectBodyRegionApSoutheast2 CreateProjectBodyRegion = "ap-southeast-2" - CreateProjectBodyRegionCaCentral1 CreateProjectBodyRegion = "ca-central-1" - CreateProjectBodyRegionEuCentral1 CreateProjectBodyRegion = "eu-central-1" - CreateProjectBodyRegionEuWest1 CreateProjectBodyRegion = "eu-west-1" - CreateProjectBodyRegionEuWest2 CreateProjectBodyRegion = "eu-west-2" - CreateProjectBodyRegionEuWest3 CreateProjectBodyRegion = "eu-west-3" - CreateProjectBodyRegionSaEast1 CreateProjectBodyRegion = "sa-east-1" - CreateProjectBodyRegionUsEast1 CreateProjectBodyRegion = "us-east-1" - CreateProjectBodyRegionUsWest1 CreateProjectBodyRegion = "us-west-1" - CreateProjectBodyRegionUsWest2 CreateProjectBodyRegion = "us-west-2" -) - // Defines values for CreateProviderBodyType. const ( Saml CreateProviderBodyType = "saml" @@ -107,6 +83,20 @@ const ( N2 DatabaseUpgradeStatusStatus = 2 ) +// Defines values for DesiredInstanceSize. +const ( + Large DesiredInstanceSize = "large" + Medium DesiredInstanceSize = "medium" + Micro DesiredInstanceSize = "micro" + N12xlarge DesiredInstanceSize = "12xlarge" + N16xlarge DesiredInstanceSize = "16xlarge" + N2xlarge DesiredInstanceSize = "2xlarge" + N4xlarge DesiredInstanceSize = "4xlarge" + N8xlarge DesiredInstanceSize = "8xlarge" + Small DesiredInstanceSize = "small" + Xlarge DesiredInstanceSize = "xlarge" +) + // Defines values for FunctionResponseStatus. const ( FunctionResponseStatusACTIVE FunctionResponseStatus = "ACTIVE" @@ -151,23 +141,6 @@ const ( PostgresConfigResponseSessionReplicationRoleReplica PostgresConfigResponseSessionReplicationRole = "replica" ) -// Defines values for ServiceHealthResponseName. -const ( - ServiceHealthResponseNameAuth ServiceHealthResponseName = "auth" - ServiceHealthResponseNameDb ServiceHealthResponseName = "db" - ServiceHealthResponseNamePooler ServiceHealthResponseName = "pooler" - ServiceHealthResponseNameRealtime ServiceHealthResponseName = "realtime" - ServiceHealthResponseNameRest ServiceHealthResponseName = "rest" - ServiceHealthResponseNameStorage ServiceHealthResponseName = "storage" -) - -// Defines values for ServiceHealthResponseStatus. -const ( - ACTIVEHEALTHY ServiceHealthResponseStatus = "ACTIVE_HEALTHY" - COMINGUP ServiceHealthResponseStatus = "COMING_UP" - UNHEALTHY ServiceHealthResponseStatus = "UNHEALTHY" -) - // Defines values for SetUpReadReplicaBodyReadReplicaRegion. const ( SetUpReadReplicaBodyReadReplicaRegionApEast1 SetUpReadReplicaBodyReadReplicaRegion = "ap-east-1" @@ -246,10 +219,34 @@ const ( V1BackupStatusREMOVED V1BackupStatus = "REMOVED" ) +// Defines values for V1CreateProjectBodyPlan. +const ( + V1CreateProjectBodyPlanFree V1CreateProjectBodyPlan = "free" + V1CreateProjectBodyPlanPro V1CreateProjectBodyPlan = "pro" +) + +// Defines values for V1CreateProjectBodyRegion. +const ( + V1CreateProjectBodyRegionApEast1 V1CreateProjectBodyRegion = "ap-east-1" + V1CreateProjectBodyRegionApNortheast1 V1CreateProjectBodyRegion = "ap-northeast-1" + V1CreateProjectBodyRegionApNortheast2 V1CreateProjectBodyRegion = "ap-northeast-2" + V1CreateProjectBodyRegionApSouth1 V1CreateProjectBodyRegion = "ap-south-1" + V1CreateProjectBodyRegionApSoutheast1 V1CreateProjectBodyRegion = "ap-southeast-1" + V1CreateProjectBodyRegionApSoutheast2 V1CreateProjectBodyRegion = "ap-southeast-2" + V1CreateProjectBodyRegionCaCentral1 V1CreateProjectBodyRegion = "ca-central-1" + V1CreateProjectBodyRegionEuCentral1 V1CreateProjectBodyRegion = "eu-central-1" + V1CreateProjectBodyRegionEuWest1 V1CreateProjectBodyRegion = "eu-west-1" + V1CreateProjectBodyRegionEuWest2 V1CreateProjectBodyRegion = "eu-west-2" + V1CreateProjectBodyRegionEuWest3 V1CreateProjectBodyRegion = "eu-west-3" + V1CreateProjectBodyRegionSaEast1 V1CreateProjectBodyRegion = "sa-east-1" + V1CreateProjectBodyRegionUsEast1 V1CreateProjectBodyRegion = "us-east-1" + V1CreateProjectBodyRegionUsWest1 V1CreateProjectBodyRegion = "us-west-1" + V1CreateProjectBodyRegionUsWest2 V1CreateProjectBodyRegion = "us-west-2" +) + // Defines values for V1OrganizationSlugResponseOptInTags. const ( - AISQLGENERATOROPTIN V1OrganizationSlugResponseOptInTags = "AI_SQL_GENERATOR_OPT_IN" - PREVIEWBRANCHESOPTIN V1OrganizationSlugResponseOptInTags = "PREVIEW_BRANCHES_OPT_IN" + AISQLGENERATOROPTIN V1OrganizationSlugResponseOptInTags = "AI_SQL_GENERATOR_OPT_IN" ) // Defines values for V1PgbouncerConfigResponsePoolMode. @@ -259,6 +256,38 @@ const ( Transaction V1PgbouncerConfigResponsePoolMode = "transaction" ) +// Defines values for V1ProjectResponseStatus. +const ( + V1ProjectResponseStatusACTIVEHEALTHY V1ProjectResponseStatus = "ACTIVE_HEALTHY" + V1ProjectResponseStatusACTIVEUNHEALTHY V1ProjectResponseStatus = "ACTIVE_UNHEALTHY" + V1ProjectResponseStatusCOMINGUP V1ProjectResponseStatus = "COMING_UP" + V1ProjectResponseStatusGOINGDOWN V1ProjectResponseStatus = "GOING_DOWN" + V1ProjectResponseStatusINACTIVE V1ProjectResponseStatus = "INACTIVE" + V1ProjectResponseStatusINITFAILED V1ProjectResponseStatus = "INIT_FAILED" + V1ProjectResponseStatusPAUSING V1ProjectResponseStatus = "PAUSING" + V1ProjectResponseStatusREMOVED V1ProjectResponseStatus = "REMOVED" + V1ProjectResponseStatusRESTORING V1ProjectResponseStatus = "RESTORING" + V1ProjectResponseStatusUNKNOWN V1ProjectResponseStatus = "UNKNOWN" + V1ProjectResponseStatusUPGRADING V1ProjectResponseStatus = "UPGRADING" +) + +// Defines values for V1ServiceHealthResponseName. +const ( + V1ServiceHealthResponseNameAuth V1ServiceHealthResponseName = "auth" + V1ServiceHealthResponseNameDb V1ServiceHealthResponseName = "db" + V1ServiceHealthResponseNamePooler V1ServiceHealthResponseName = "pooler" + V1ServiceHealthResponseNameRealtime V1ServiceHealthResponseName = "realtime" + V1ServiceHealthResponseNameRest V1ServiceHealthResponseName = "rest" + V1ServiceHealthResponseNameStorage V1ServiceHealthResponseName = "storage" +) + +// Defines values for V1ServiceHealthResponseStatus. +const ( + V1ServiceHealthResponseStatusACTIVEHEALTHY V1ServiceHealthResponseStatus = "ACTIVE_HEALTHY" + V1ServiceHealthResponseStatusCOMINGUP V1ServiceHealthResponseStatus = "COMING_UP" + V1ServiceHealthResponseStatusUNHEALTHY V1ServiceHealthResponseStatus = "UNHEALTHY" +) + // Defines values for VanitySubdomainConfigResponseStatus. const ( Active VanitySubdomainConfigResponseStatus = "active" @@ -333,6 +362,7 @@ type AttributeValue_Default struct { // AuthConfigResponse defines model for AuthConfigResponse. type AuthConfigResponse struct { 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"` @@ -414,16 +444,19 @@ type AuthConfigResponse struct { 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"` 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"` RateLimitSmsSent *float32 `json:"rate_limit_sms_sent"` RateLimitTokenRefresh *float32 `json:"rate_limit_token_refresh"` @@ -483,6 +516,11 @@ type AuthHealthResponse struct { // BillingPlanId defines model for BillingPlanId. type BillingPlanId string +// BranchDeleteResponse defines model for BranchDeleteResponse. +type BranchDeleteResponse struct { + Message string `json:"message"` +} + // BranchDetailResponse defines model for BranchDetailResponse. type BranchDetailResponse struct { DbHost string `json:"db_host"` @@ -498,6 +536,11 @@ type BranchDetailResponse struct { // BranchDetailResponseStatus defines model for BranchDetailResponse.Status. type BranchDetailResponseStatus string +// BranchResetResponse defines model for BranchResetResponse. +type BranchResetResponse struct { + Message string `json:"message"` +} + // BranchResponse defines model for BranchResponse. type BranchResponse struct { CreatedAt string `json:"created_at"` @@ -506,6 +549,7 @@ type BranchResponse struct { IsDefault bool `json:"is_default"` Name string `json:"name"` ParentProjectRef string `json:"parent_project_ref"` + Persistent bool `json:"persistent"` PrNumber *float32 `json:"pr_number,omitempty"` ProjectRef string `json:"project_ref"` ResetOnPush bool `json:"reset_on_push"` @@ -523,49 +567,11 @@ type CreateBranchBody struct { Region *string `json:"region,omitempty"` } -// CreateFunctionBody defines model for CreateFunctionBody. -type CreateFunctionBody struct { - Body string `json:"body"` - Name string `json:"name"` - Slug string `json:"slug"` - VerifyJwt *bool `json:"verify_jwt,omitempty"` -} - // CreateOrganizationBodyV1 defines model for CreateOrganizationBodyV1. type CreateOrganizationBodyV1 struct { Name string `json:"name"` } -// CreateProjectBody defines model for CreateProjectBody. -type CreateProjectBody struct { - // DbPass Database password - DbPass string `json:"db_pass"` - // Deprecated: - KpsEnabled *bool `json:"kps_enabled,omitempty"` - - // Name Name of your project, should not contain dots - Name string `json:"name"` - - // OrganizationId Slug of your organization - OrganizationId string `json:"organization_id"` - - // Plan Subscription plan is now set on organization level and is ignored in this request - // Deprecated: - Plan *CreateProjectBodyPlan `json:"plan,omitempty"` - - // Region Region you want your server to reside in - Region CreateProjectBodyRegion `json:"region"` - - // TemplateUrl Template URL used to create the project from the CLI. - TemplateUrl *string `json:"template_url,omitempty"` -} - -// CreateProjectBodyPlan Subscription plan is now set on organization level and is ignored in this request -type CreateProjectBodyPlan string - -// CreateProjectBodyRegion Region you want your server to reside in -type CreateProjectBodyRegion string - // CreateProviderBody defines model for CreateProviderBody. type CreateProviderBody struct { AttributeMapping *AttributeMapping `json:"attribute_mapping,omitempty"` @@ -596,13 +602,11 @@ type CreateSecretBody struct { Value string `json:"value"` } -// DatabaseResponse defines model for DatabaseResponse. -type DatabaseResponse struct { - // Host Database host - Host string `json:"host"` - - // Version Database version - Version string `json:"version"` +// CreateThirdPartyAuthBody defines model for CreateThirdPartyAuthBody. +type CreateThirdPartyAuthBody struct { + CustomJwks *map[string]interface{} `json:"custom_jwks,omitempty"` + JwksUrl *string `json:"jwks_url,omitempty"` + OidcIssuerUrl *string `json:"oidc_issuer_url,omitempty"` } // DatabaseUpgradeStatus defines model for DatabaseUpgradeStatus. @@ -637,6 +641,9 @@ type DeleteProviderResponse struct { UpdatedAt *string `json:"updated_at,omitempty"` } +// DesiredInstanceSize defines model for DesiredInstanceSize. +type DesiredInstanceSize string + // Domain defines model for Domain. type Domain struct { CreatedAt *string `json:"created_at,omitempty"` @@ -756,12 +763,6 @@ type PgsodiumConfigResponse struct { RootKey string `json:"root_key"` } -// PhysicalBackup defines model for PhysicalBackup. -type PhysicalBackup struct { - EarliestPhysicalBackupDateUnix *float32 `json:"earliest_physical_backup_date_unix,omitempty"` - LatestPhysicalBackupDateUnix *float32 `json:"latest_physical_backup_date_unix,omitempty"` -} - // PostgresConfigResponse defines model for PostgresConfigResponse. type PostgresConfigResponse struct { EffectiveCacheSize *string `json:"effective_cache_size,omitempty"` @@ -771,6 +772,8 @@ type PostgresConfigResponse struct { MaxParallelMaintenanceWorkers *int `json:"max_parallel_maintenance_workers,omitempty"` MaxParallelWorkers *int `json:"max_parallel_workers,omitempty"` MaxParallelWorkersPerGather *int `json:"max_parallel_workers_per_gather,omitempty"` + MaxStandbyArchiveDelay *string `json:"max_standby_archive_delay,omitempty"` + MaxStandbyStreamingDelay *string `json:"max_standby_streaming_delay,omitempty"` MaxWorkerProcesses *int `json:"max_worker_processes,omitempty"` SessionReplicationRole *PostgresConfigResponseSessionReplicationRole `json:"session_replication_role,omitempty"` SharedBuffers *string `json:"shared_buffers,omitempty"` @@ -781,45 +784,15 @@ type PostgresConfigResponse struct { // PostgresConfigResponseSessionReplicationRole defines model for PostgresConfigResponse.SessionReplicationRole. type PostgresConfigResponseSessionReplicationRole string -// PostgrestConfigResponse defines model for PostgrestConfigResponse. -type PostgrestConfigResponse struct { - DbExtraSearchPath string `json:"db_extra_search_path"` - DbSchema string `json:"db_schema"` - MaxRows int `json:"max_rows"` -} - // PostgrestConfigWithJWTSecretResponse defines model for PostgrestConfigWithJWTSecretResponse. type PostgrestConfigWithJWTSecretResponse struct { - DbExtraSearchPath string `json:"db_extra_search_path"` - DbSchema string `json:"db_schema"` - JwtSecret *string `json:"jwt_secret,omitempty"` - MaxRows int `json:"max_rows"` -} - -// ProjectRefResponse defines model for ProjectRefResponse. -type ProjectRefResponse struct { - Id float32 `json:"id"` - Name string `json:"name"` - Ref string `json:"ref"` -} - -// ProjectResponse defines model for ProjectResponse. -type ProjectResponse struct { - // CreatedAt Creation timestamp - CreatedAt string `json:"created_at"` - Database *DatabaseResponse `json:"database,omitempty"` - - // Id Id of your project - Id string `json:"id"` - - // Name Name of your project - Name string `json:"name"` - - // OrganizationId Slug of your organization - OrganizationId string `json:"organization_id"` + DbExtraSearchPath string `json:"db_extra_search_path"` - // Region Region of your project - Region string `json:"region"` + // DbPool If `null`, the value is automatically configured based on compute size. + DbPool *int `json:"db_pool"` + DbSchema string `json:"db_schema"` + JwtSecret *string `json:"jwt_secret,omitempty"` + MaxRows int `json:"max_rows"` } // ProjectUpgradeEligibilityResponse defines model for ProjectUpgradeEligibilityResponse. @@ -878,11 +851,6 @@ type RemoveReadReplicaBody struct { DatabaseIdentifier string `json:"database_identifier"` } -// RunQueryBody defines model for RunQueryBody. -type RunQueryBody struct { - Query string `json:"query"` -} - // SamlDescriptor defines model for SamlDescriptor. type SamlDescriptor struct { AttributeMapping *AttributeMapping `json:"attribute_mapping,omitempty"` @@ -898,26 +866,6 @@ type SecretResponse struct { Value string `json:"value"` } -// ServiceHealthResponse defines model for ServiceHealthResponse. -type ServiceHealthResponse struct { - Error *string `json:"error,omitempty"` - Healthy bool `json:"healthy"` - Info *ServiceHealthResponse_Info `json:"info,omitempty"` - Name ServiceHealthResponseName `json:"name"` - Status ServiceHealthResponseStatus `json:"status"` -} - -// ServiceHealthResponse_Info defines model for ServiceHealthResponse.Info. -type ServiceHealthResponse_Info struct { - union json.RawMessage -} - -// ServiceHealthResponseName defines model for ServiceHealthResponse.Name. -type ServiceHealthResponseName string - -// ServiceHealthResponseStatus defines model for ServiceHealthResponse.Status. -type ServiceHealthResponseStatus string - // SetUpReadReplicaBody defines model for SetUpReadReplicaBody. type SetUpReadReplicaBody struct { // ReadReplicaRegion Region you want your read replica to reside in @@ -1013,6 +961,19 @@ type SubdomainAvailabilityResponse struct { Available bool `json:"available"` } +// ThirdPartyAuth defines model for ThirdPartyAuth. +type ThirdPartyAuth struct { + CustomJwks *map[string]interface{} `json:"custom_jwks"` + Id string `json:"id"` + InsertedAt string `json:"inserted_at"` + JwksUrl *string `json:"jwks_url"` + OidcIssuerUrl *string `json:"oidc_issuer_url"` + ResolvedAt *string `json:"resolved_at"` + ResolvedJwks *map[string]interface{} `json:"resolved_jwks"` + Type string `json:"type"` + UpdatedAt string `json:"updated_at"` +} + // TypescriptResponse defines model for TypescriptResponse. type TypescriptResponse struct { Types string `json:"types"` @@ -1021,6 +982,7 @@ type TypescriptResponse struct { // UpdateAuthConfigBody defines model for UpdateAuthConfigBody. type UpdateAuthConfigBody struct { DisableSignup *bool `json:"disable_signup,omitempty"` + ExternalAnonymousUsersEnabled *bool `json:"external_anonymous_users_enabled,omitempty"` ExternalAppleAdditionalClientIds *string `json:"external_apple_additional_client_ids,omitempty"` ExternalAppleClientId *string `json:"external_apple_client_id,omitempty"` ExternalAppleEnabled *bool `json:"external_apple_enabled,omitempty"` @@ -1093,6 +1055,10 @@ type UpdateAuthConfigBody struct { HookMfaVerificationAttemptUri *string `json:"hook_mfa_verification_attempt_uri,omitempty"` HookPasswordVerificationAttemptEnabled *bool `json:"hook_password_verification_attempt_enabled,omitempty"` HookPasswordVerificationAttemptUri *string `json:"hook_password_verification_attempt_uri,omitempty"` + HookSendEmailEnabled *bool `json:"hook_send_email_enabled,omitempty"` + HookSendEmailUri *string `json:"hook_send_email_uri,omitempty"` + HookSendSmsEnabled *bool `json:"hook_send_sms_enabled,omitempty"` + HookSendSmsUri *string `json:"hook_send_sms_uri,omitempty"` JwtExp *float32 `json:"jwt_exp,omitempty"` MailerAllowUnverifiedEmailSignIns *bool `json:"mailer_allow_unverified_email_sign_ins,omitempty"` MailerAutoconfirm *bool `json:"mailer_autoconfirm,omitempty"` @@ -1102,16 +1068,19 @@ type UpdateAuthConfigBody struct { MailerSubjectsEmailChange *string `json:"mailer_subjects_email_change,omitempty"` MailerSubjectsInvite *string `json:"mailer_subjects_invite,omitempty"` MailerSubjectsMagicLink *string `json:"mailer_subjects_magic_link,omitempty"` + MailerSubjectsReauthentication *string `json:"mailer_subjects_reauthentication,omitempty"` MailerSubjectsRecovery *string `json:"mailer_subjects_recovery,omitempty"` MailerTemplatesConfirmationContent *string `json:"mailer_templates_confirmation_content,omitempty"` MailerTemplatesEmailChangeContent *string `json:"mailer_templates_email_change_content,omitempty"` MailerTemplatesInviteContent *string `json:"mailer_templates_invite_content,omitempty"` 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"` PasswordHibpEnabled *bool `json:"password_hibp_enabled,omitempty"` PasswordMinLength *float32 `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"` RateLimitSmsSent *float32 `json:"rate_limit_sms_sent,omitempty"` RateLimitTokenRefresh *float32 `json:"rate_limit_token_refresh,omitempty"` @@ -1168,6 +1137,7 @@ type UpdateAuthConfigBodyPasswordRequiredCharacters string type UpdateBranchBody struct { BranchName *string `json:"branch_name,omitempty"` GitBranch *string `json:"git_branch,omitempty"` + Persistent *bool `json:"persistent,omitempty"` ResetOnPush *bool `json:"reset_on_push,omitempty"` } @@ -1186,13 +1156,6 @@ type UpdateCustomHostnameResponse struct { // UpdateCustomHostnameResponseStatus defines model for UpdateCustomHostnameResponse.Status. type UpdateCustomHostnameResponseStatus string -// UpdateFunctionBody defines model for UpdateFunctionBody. -type UpdateFunctionBody struct { - Body *string `json:"body,omitempty"` - Name *string `json:"name,omitempty"` - VerifyJwt *bool `json:"verify_jwt,omitempty"` -} - // UpdatePgsodiumConfigBody defines model for UpdatePgsodiumConfigBody. type UpdatePgsodiumConfigBody struct { RootKey string `json:"root_key"` @@ -1207,6 +1170,8 @@ type UpdatePostgresConfigBody struct { MaxParallelMaintenanceWorkers *int `json:"max_parallel_maintenance_workers,omitempty"` MaxParallelWorkers *int `json:"max_parallel_workers,omitempty"` MaxParallelWorkersPerGather *int `json:"max_parallel_workers_per_gather,omitempty"` + MaxStandbyArchiveDelay *string `json:"max_standby_archive_delay,omitempty"` + MaxStandbyStreamingDelay *string `json:"max_standby_streaming_delay,omitempty"` MaxWorkerProcesses *int `json:"max_worker_processes,omitempty"` SessionReplicationRole *UpdatePostgresConfigBodySessionReplicationRole `json:"session_replication_role,omitempty"` SharedBuffers *string `json:"shared_buffers,omitempty"` @@ -1220,6 +1185,7 @@ type UpdatePostgresConfigBodySessionReplicationRole string // UpdatePostgrestConfigBody defines model for UpdatePostgrestConfigBody. type UpdatePostgrestConfigBody struct { DbExtraSearchPath *string `json:"db_extra_search_path,omitempty"` + DbPool *int `json:"db_pool,omitempty"` DbSchema *string `json:"db_schema,omitempty"` MaxRows *int `json:"max_rows,omitempty"` } @@ -1258,11 +1224,59 @@ type V1BackupStatus string // V1BackupsResponse defines model for V1BackupsResponse. type V1BackupsResponse struct { - Backups []V1Backup `json:"backups"` - PhysicalBackupData PhysicalBackup `json:"physical_backup_data"` - PitrEnabled bool `json:"pitr_enabled"` - Region string `json:"region"` - WalgEnabled bool `json:"walg_enabled"` + Backups []V1Backup `json:"backups"` + PhysicalBackupData V1PhysicalBackup `json:"physical_backup_data"` + PitrEnabled bool `json:"pitr_enabled"` + Region string `json:"region"` + WalgEnabled bool `json:"walg_enabled"` +} + +// V1CreateFunctionBody defines model for V1CreateFunctionBody. +type V1CreateFunctionBody struct { + Body string `json:"body"` + Name string `json:"name"` + Slug string `json:"slug"` + VerifyJwt *bool `json:"verify_jwt,omitempty"` +} + +// V1CreateProjectBody defines model for V1CreateProjectBody. +type V1CreateProjectBody struct { + // DbPass Database password + DbPass string `json:"db_pass"` + DesiredInstanceSize *DesiredInstanceSize `json:"desired_instance_size,omitempty"` + // Deprecated: + KpsEnabled *bool `json:"kps_enabled,omitempty"` + + // Name Name of your project, should not contain dots + Name string `json:"name"` + + // OrganizationId Slug of your organization + OrganizationId string `json:"organization_id"` + + // Plan Subscription plan is now set on organization level and is ignored in this request + // Deprecated: + Plan *V1CreateProjectBodyPlan `json:"plan,omitempty"` + + // Region Region you want your server to reside in + Region V1CreateProjectBodyRegion `json:"region"` + + // TemplateUrl Template URL used to create the project from the CLI. + TemplateUrl *string `json:"template_url,omitempty"` +} + +// V1CreateProjectBodyPlan Subscription plan is now set on organization level and is ignored in this request +type V1CreateProjectBodyPlan string + +// V1CreateProjectBodyRegion Region you want your server to reside in +type V1CreateProjectBodyRegion string + +// V1DatabaseResponse defines model for V1DatabaseResponse. +type V1DatabaseResponse struct { + // Host Database host + Host string `json:"host"` + + // Version Database version + Version string `json:"version"` } // V1OrganizationMemberResponse defines model for V1OrganizationMemberResponse. @@ -1297,11 +1311,82 @@ type V1PgbouncerConfigResponse struct { // V1PgbouncerConfigResponsePoolMode defines model for V1PgbouncerConfigResponse.PoolMode. 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"` +} + +// V1PostgrestConfigResponse defines model for V1PostgrestConfigResponse. +type V1PostgrestConfigResponse struct { + DbExtraSearchPath string `json:"db_extra_search_path"` + + // DbPool If `null`, the value is automatically configured based on compute size. + DbPool *int `json:"db_pool"` + DbSchema string `json:"db_schema"` + MaxRows int `json:"max_rows"` +} + +// V1ProjectRefResponse defines model for V1ProjectRefResponse. +type V1ProjectRefResponse struct { + Id float32 `json:"id"` + Name string `json:"name"` + Ref string `json:"ref"` +} + +// V1ProjectResponse defines model for V1ProjectResponse. +type V1ProjectResponse struct { + // CreatedAt Creation timestamp + CreatedAt string `json:"created_at"` + Database *V1DatabaseResponse `json:"database,omitempty"` + + // Id Id of your project + Id string `json:"id"` + + // Name Name of your project + Name string `json:"name"` + + // OrganizationId Slug of your organization + OrganizationId string `json:"organization_id"` + + // Region Region of your project + Region string `json:"region"` + Status V1ProjectResponseStatus `json:"status"` +} + +// V1ProjectResponseStatus defines model for V1ProjectResponse.Status. +type V1ProjectResponseStatus string + // V1RestorePitrBody defines model for V1RestorePitrBody. type V1RestorePitrBody struct { RecoveryTimeTargetUnix float32 `json:"recovery_time_target_unix"` } +// V1RunQueryBody defines model for V1RunQueryBody. +type V1RunQueryBody struct { + Query string `json:"query"` +} + +// V1ServiceHealthResponse defines model for V1ServiceHealthResponse. +type V1ServiceHealthResponse struct { + Error *string `json:"error,omitempty"` + Healthy bool `json:"healthy"` + Info *V1ServiceHealthResponse_Info `json:"info,omitempty"` + Name V1ServiceHealthResponseName `json:"name"` + Status V1ServiceHealthResponseStatus `json:"status"` +} + +// V1ServiceHealthResponse_Info defines model for V1ServiceHealthResponse.Info. +type V1ServiceHealthResponse_Info struct { + union json.RawMessage +} + +// V1ServiceHealthResponseName defines model for V1ServiceHealthResponse.Name. +type V1ServiceHealthResponseName string + +// V1ServiceHealthResponseStatus defines model for V1ServiceHealthResponse.Status. +type V1ServiceHealthResponseStatus string + // V1StorageBucketResponse defines model for V1StorageBucketResponse. type V1StorageBucketResponse struct { CreatedAt string `json:"created_at"` @@ -1312,6 +1397,13 @@ type V1StorageBucketResponse struct { UpdatedAt string `json:"updated_at"` } +// V1UpdateFunctionBody defines model for V1UpdateFunctionBody. +type V1UpdateFunctionBody struct { + Body *string `json:"body,omitempty"` + Name *string `json:"name,omitempty"` + VerifyJwt *bool `json:"verify_jwt,omitempty"` +} + // VanitySubdomainBody defines model for VanitySubdomainBody. type VanitySubdomainBody struct { VanitySubdomain string `json:"vanity_subdomain"` @@ -1399,7 +1491,7 @@ type TokenFormdataRequestBody = OAuthTokenBody type CreateOrganizationJSONRequestBody = CreateOrganizationBodyV1 // CreateProjectJSONRequestBody defines body for CreateProject for application/json ContentType. -type CreateProjectJSONRequestBody = CreateProjectBody +type CreateProjectJSONRequestBody = V1CreateProjectBody // CreateBranchJSONRequestBody defines body for CreateBranch for application/json ContentType. type CreateBranchJSONRequestBody = CreateBranchBody @@ -1413,6 +1505,9 @@ type CreateProviderForProjectJSONRequestBody = CreateProviderBody // UpdateProviderByIdJSONRequestBody defines body for UpdateProviderById for application/json ContentType. type UpdateProviderByIdJSONRequestBody = UpdateProviderBody +// CreateTPAForProjectJSONRequestBody defines body for CreateTPAForProject for application/json ContentType. +type CreateTPAForProjectJSONRequestBody = CreateThirdPartyAuthBody + // UpdateConfigJSONRequestBody defines body for UpdateConfig for application/json ContentType. type UpdateConfigJSONRequestBody = UpdatePostgresConfigBody @@ -1423,13 +1518,13 @@ type CreateCustomHostnameConfigJSONRequestBody = UpdateCustomHostnameBody type V1RestorePitrJSONRequestBody = V1RestorePitrBody // V1RunQueryJSONRequestBody defines body for V1RunQuery for application/json ContentType. -type V1RunQueryJSONRequestBody = RunQueryBody +type V1RunQueryJSONRequestBody = V1RunQueryBody // CreateFunctionJSONRequestBody defines body for CreateFunction for application/json ContentType. -type CreateFunctionJSONRequestBody = CreateFunctionBody +type CreateFunctionJSONRequestBody = V1CreateFunctionBody // UpdateFunctionJSONRequestBody defines body for UpdateFunction for application/json ContentType. -type UpdateFunctionJSONRequestBody = UpdateFunctionBody +type UpdateFunctionJSONRequestBody = V1UpdateFunctionBody // RemoveNetworkBanJSONRequestBody defines body for RemoveNetworkBan for application/json ContentType. type RemoveNetworkBanJSONRequestBody = RemoveNetworkBanRequest @@ -1581,22 +1676,22 @@ func (t *AttributeValue_Default) UnmarshalJSON(b []byte) error { return err } -// AsAuthHealthResponse returns the union data inside the ServiceHealthResponse_Info as a AuthHealthResponse -func (t ServiceHealthResponse_Info) AsAuthHealthResponse() (AuthHealthResponse, error) { +// AsAuthHealthResponse returns the union data inside the V1ServiceHealthResponse_Info as a AuthHealthResponse +func (t V1ServiceHealthResponse_Info) AsAuthHealthResponse() (AuthHealthResponse, error) { var body AuthHealthResponse err := json.Unmarshal(t.union, &body) return body, err } -// FromAuthHealthResponse overwrites any union data inside the ServiceHealthResponse_Info as the provided AuthHealthResponse -func (t *ServiceHealthResponse_Info) FromAuthHealthResponse(v AuthHealthResponse) error { +// FromAuthHealthResponse overwrites any union data inside the V1ServiceHealthResponse_Info as the provided AuthHealthResponse +func (t *V1ServiceHealthResponse_Info) FromAuthHealthResponse(v AuthHealthResponse) error { b, err := json.Marshal(v) t.union = b return err } -// MergeAuthHealthResponse performs a merge with any union data inside the ServiceHealthResponse_Info, using the provided AuthHealthResponse -func (t *ServiceHealthResponse_Info) MergeAuthHealthResponse(v AuthHealthResponse) error { +// MergeAuthHealthResponse performs a merge with any union data inside the V1ServiceHealthResponse_Info, using the provided AuthHealthResponse +func (t *V1ServiceHealthResponse_Info) MergeAuthHealthResponse(v AuthHealthResponse) error { b, err := json.Marshal(v) if err != nil { return err @@ -1607,22 +1702,22 @@ func (t *ServiceHealthResponse_Info) MergeAuthHealthResponse(v AuthHealthRespons return err } -// AsRealtimeHealthResponse returns the union data inside the ServiceHealthResponse_Info as a RealtimeHealthResponse -func (t ServiceHealthResponse_Info) AsRealtimeHealthResponse() (RealtimeHealthResponse, error) { +// AsRealtimeHealthResponse returns the union data inside the V1ServiceHealthResponse_Info as a RealtimeHealthResponse +func (t V1ServiceHealthResponse_Info) AsRealtimeHealthResponse() (RealtimeHealthResponse, error) { var body RealtimeHealthResponse err := json.Unmarshal(t.union, &body) return body, err } -// FromRealtimeHealthResponse overwrites any union data inside the ServiceHealthResponse_Info as the provided RealtimeHealthResponse -func (t *ServiceHealthResponse_Info) FromRealtimeHealthResponse(v RealtimeHealthResponse) error { +// FromRealtimeHealthResponse overwrites any union data inside the V1ServiceHealthResponse_Info as the provided RealtimeHealthResponse +func (t *V1ServiceHealthResponse_Info) FromRealtimeHealthResponse(v RealtimeHealthResponse) error { b, err := json.Marshal(v) t.union = b return err } -// MergeRealtimeHealthResponse performs a merge with any union data inside the ServiceHealthResponse_Info, using the provided RealtimeHealthResponse -func (t *ServiceHealthResponse_Info) MergeRealtimeHealthResponse(v RealtimeHealthResponse) error { +// MergeRealtimeHealthResponse performs a merge with any union data inside the V1ServiceHealthResponse_Info, using the provided RealtimeHealthResponse +func (t *V1ServiceHealthResponse_Info) MergeRealtimeHealthResponse(v RealtimeHealthResponse) error { b, err := json.Marshal(v) if err != nil { return err @@ -1633,12 +1728,12 @@ func (t *ServiceHealthResponse_Info) MergeRealtimeHealthResponse(v RealtimeHealt return err } -func (t ServiceHealthResponse_Info) MarshalJSON() ([]byte, error) { +func (t V1ServiceHealthResponse_Info) MarshalJSON() ([]byte, error) { b, err := t.union.MarshalJSON() return b, err } -func (t *ServiceHealthResponse_Info) UnmarshalJSON(b []byte) error { +func (t *V1ServiceHealthResponse_Info) UnmarshalJSON(b []byte) error { err := t.union.UnmarshalJSON(b) return err } diff --git a/pkg/fetcher/http.go b/pkg/fetcher/http.go new file mode 100644 index 000000000..57b7be268 --- /dev/null +++ b/pkg/fetcher/http.go @@ -0,0 +1,107 @@ +package fetcher + +import ( + "bytes" + "context" + "encoding/json" + "io" + "net/http" + + "github.com/go-errors/errors" +) + +type Fetcher struct { + server string + client *http.Client + editors []RequestEditor +} + +type FetcherOption func(*Fetcher) + +func NewFetcher(server string, opts ...FetcherOption) *Fetcher { + api := &Fetcher{ + server: server, + client: http.DefaultClient, + } + for _, apply := range opts { + apply(api) + } + return api +} + +func WithHTTPClient(client *http.Client) FetcherOption { + return func(s *Fetcher) { + s.client = client + } +} + +func WithBearerToken(token string) FetcherOption { + addHeader := func(req *http.Request) { + req.Header.Add("Authorization", "Bearer "+token) + } + return WithRequestEditor(addHeader) +} + +func WithUserAgent(agent string) FetcherOption { + addHeader := func(req *http.Request) { + req.Header.Add("User-Agent", agent) + } + return WithRequestEditor(addHeader) +} + +func WithRequestEditor(fn RequestEditor) FetcherOption { + return func(s *Fetcher) { + s.editors = append(s.editors, fn) + } +} + +type RequestEditor func(req *http.Request) + +func (s *Fetcher) Send(ctx context.Context, method, path string, reqBody any, reqEditors ...RequestEditor) (*http.Response, error) { + body, ok := reqBody.(io.Reader) + if !ok && reqBody != nil { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + if err := enc.Encode(reqBody); err != nil { + return nil, errors.Errorf("failed to encode request body: %w", err) + } + reqEditors = append(reqEditors, func(req *http.Request) { + req.Header.Set("Content-Type", "application/json") + }) + body = &buf + } + // Creates request + req, err := http.NewRequestWithContext(ctx, method, s.server+path, body) + if err != nil { + return nil, errors.Errorf("failed to initialise http request: %w", err) + } + for _, apply := range s.editors { + apply(req) + } + for _, apply := range reqEditors { + apply(req) + } + // Sends request + resp, err := s.client.Do(req) + if err != nil { + return nil, errors.Errorf("failed to execute http request: %w", err) + } + if resp.StatusCode >= http.StatusBadRequest { + defer resp.Body.Close() + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, errors.Errorf("Error status %d: %w", resp.StatusCode, err) + } + return nil, errors.Errorf("Error status %d: %s", resp.StatusCode, data) + } + return resp, nil +} + +func ParseJSON[T any](r io.Reader) (*T, error) { + var data T + dec := json.NewDecoder(r) + if err := dec.Decode(&data); err != nil { + return nil, errors.Errorf("failed to parse response body: %w", err) + } + return &data, nil +} diff --git a/pkg/storage/api.go b/pkg/storage/api.go new file mode 100644 index 000000000..e7765eb9f --- /dev/null +++ b/pkg/storage/api.go @@ -0,0 +1,9 @@ +package storage + +import "github.com/supabase/cli/pkg/fetcher" + +type StorageAPI struct { + *fetcher.Fetcher +} + +const PAGE_LIMIT = 100 diff --git a/internal/storage/client/buckets.go b/pkg/storage/buckets.go similarity index 54% rename from internal/storage/client/buckets.go rename to pkg/storage/buckets.go index 33329b884..05a42ef43 100644 --- a/internal/storage/client/buckets.go +++ b/pkg/storage/buckets.go @@ -1,12 +1,10 @@ -package client +package storage import ( "context" - "fmt" "net/http" - "github.com/supabase/cli/internal/utils" - "github.com/supabase/cli/internal/utils/tenant" + "github.com/supabase/cli/pkg/fetcher" ) type BucketResponse struct { @@ -20,13 +18,13 @@ type BucketResponse struct { UpdatedAt string `json:"updated_at"` // "2023-10-13T17:48:58.491Z" } -func ListStorageBuckets(ctx context.Context, projectRef string) ([]BucketResponse, error) { - url := fmt.Sprintf("https://%s/storage/v1/bucket", utils.GetSupabaseHost(projectRef)) - apiKey, err := tenant.GetApiKeys(ctx, projectRef) +func (s *StorageAPI) ListBuckets(ctx context.Context) ([]BucketResponse, error) { + resp, err := s.Send(ctx, http.MethodGet, "/storage/v1/bucket", nil) if err != nil { return nil, err } - data, err := tenant.JsonResponseWithBearer[[]BucketResponse](ctx, http.MethodGet, url, apiKey.ServiceRole, nil) + defer resp.Body.Close() + data, err := fetcher.ParseJSON[[]BucketResponse](resp.Body) if err != nil { return nil, err } @@ -45,25 +43,25 @@ type CreateBucketResponse struct { Name string `json:"name"` } -func CreateStorageBucket(ctx context.Context, projectRef, bucketName string) (*CreateBucketResponse, error) { - url := fmt.Sprintf("https://%s/storage/v1/bucket", utils.GetSupabaseHost(projectRef)) - apiKey, err := tenant.GetApiKeys(ctx, projectRef) +func (s *StorageAPI) CreateBucket(ctx context.Context, bucketName string) (*CreateBucketResponse, error) { + body := CreateBucketRequest{Name: bucketName} + resp, err := s.Send(ctx, http.MethodPost, "/storage/v1/bucket", body) if err != nil { return nil, err } - body := CreateBucketRequest{Name: bucketName} - return tenant.JsonResponseWithBearer[CreateBucketResponse](ctx, http.MethodPost, url, apiKey.ServiceRole, body) + defer resp.Body.Close() + return fetcher.ParseJSON[CreateBucketResponse](resp.Body) } type DeleteBucketResponse struct { Message string `json:"message"` } -func DeleteStorageBucket(ctx context.Context, projectRef, bucketId string) (*DeleteBucketResponse, error) { - url := fmt.Sprintf("https://%s/storage/v1/bucket/%s", utils.GetSupabaseHost(projectRef), bucketId) - apiKey, err := tenant.GetApiKeys(ctx, projectRef) +func (s *StorageAPI) DeleteBucket(ctx context.Context, bucketId string) (*DeleteBucketResponse, error) { + resp, err := s.Send(ctx, http.MethodDelete, "/storage/v1/bucket/"+bucketId, nil) if err != nil { return nil, err } - return tenant.JsonResponseWithBearer[DeleteBucketResponse](ctx, http.MethodDelete, url, apiKey.ServiceRole, nil) + defer resp.Body.Close() + return fetcher.ParseJSON[DeleteBucketResponse](resp.Body) } diff --git a/internal/storage/client/objects.go b/pkg/storage/objects.go similarity index 57% rename from internal/storage/client/objects.go rename to pkg/storage/objects.go index b8d95213b..7fa542def 100644 --- a/internal/storage/client/objects.go +++ b/pkg/storage/objects.go @@ -1,8 +1,7 @@ -package client +package storage import ( "context" - "fmt" "io" "net/http" "path" @@ -10,12 +9,9 @@ import ( "github.com/go-errors/errors" "github.com/spf13/afero" - "github.com/supabase/cli/internal/utils" - "github.com/supabase/cli/internal/utils/tenant" + "github.com/supabase/cli/pkg/fetcher" ) -const PAGE_LIMIT = 100 - type ListObjectsQuery struct { Prefix string `json:"prefix"` Search string `json:"search,omitempty"` @@ -42,12 +38,7 @@ type ObjectMetadata struct { HttpStatusCode int `json:"httpStatusCode"` // 200 } -func ListStorageObjects(ctx context.Context, projectRef, bucket, prefix string, page int) ([]ObjectResponse, error) { - url := fmt.Sprintf("https://%s/storage/v1/object/list/%s", utils.GetSupabaseHost(projectRef), bucket) - apiKey, err := tenant.GetApiKeys(ctx, projectRef) - if err != nil { - return nil, err - } +func (s *StorageAPI) ListObjects(ctx context.Context, bucket, prefix string, page int) ([]ObjectResponse, error) { dir, name := path.Split(prefix) query := ListObjectsQuery{ Prefix: dir, @@ -55,7 +46,12 @@ func ListStorageObjects(ctx context.Context, projectRef, bucket, prefix string, Limit: PAGE_LIMIT, Offset: PAGE_LIMIT * page, } - data, err := tenant.JsonResponseWithBearer[[]ObjectResponse](ctx, http.MethodPost, url, apiKey.ServiceRole, query) + resp, err := s.Send(ctx, http.MethodPost, "/storage/v1/object/list/"+bucket, query) + if err != nil { + return nil, err + } + defer resp.Body.Close() + data, err := fetcher.ParseJSON[[]ObjectResponse](resp.Body) if err != nil { return nil, err } @@ -67,12 +63,7 @@ type FileOptions struct { ContentType string } -func UploadStorageObject(ctx context.Context, projectRef, remotePath, localPath string, fsys afero.Fs, opts ...func(*FileOptions)) error { - f, err := fsys.Open(localPath) - if err != nil { - return errors.Errorf("failed to open file: %w", err) - } - defer f.Close() +func ParseFileOptions(f afero.File, opts ...func(*FileOptions)) (*FileOptions, error) { // Customise file options fo := &FileOptions{} for _, apply := range opts { @@ -87,55 +78,52 @@ func UploadStorageObject(ctx context.Context, projectRef, remotePath, localPath header := io.LimitReader(f, 512) buf, err := io.ReadAll(header) if err != nil { - return errors.Errorf("failed to read file: %w", err) + return nil, errors.Errorf("failed to read file: %w", err) } fo.ContentType = http.DetectContentType(buf) _, err = f.Seek(0, io.SeekStart) if err != nil { - return errors.Errorf("failed to seek file: %w", err) + return nil, errors.Errorf("failed to seek file: %w", err) } } - // Prepare request - apiKey, err := tenant.GetApiKeys(ctx, projectRef) + return fo, nil +} + +func (s *StorageAPI) UploadObject(ctx context.Context, remotePath, localPath string, fsys afero.Fs, opts ...func(*FileOptions)) error { + f, err := fsys.Open(localPath) if err != nil { - return err + return errors.Errorf("failed to open file: %w", err) } - remotePath = strings.TrimPrefix(remotePath, "/") - url := fmt.Sprintf("https://%s/storage/v1/object/%s", utils.GetSupabaseHost(projectRef), remotePath) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, f) + defer f.Close() + fo, err := ParseFileOptions(f, opts...) if err != nil { - return errors.Errorf("failed to initialise http request: %w", err) + return err + } + headers := func(req *http.Request) { + req.Header.Add("Content-Type", fo.ContentType) + req.Header.Add("Cache-Control", fo.CacheControl) } - req.Header.Add("Authorization", "Bearer "+apiKey.ServiceRole) - req.Header.Add("Content-Type", fo.ContentType) - req.Header.Add("Cache-Control", fo.CacheControl) - // Sends request - resp, err := http.DefaultClient.Do(req) + // Prepare request + remotePath = strings.TrimPrefix(remotePath, "/") + resp, err := s.Send(ctx, http.MethodPost, "/storage/v1/object/"+remotePath, f, headers) if err != nil { - return errors.Errorf("failed to send http request: %w", err) + return err } defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - body, err := io.ReadAll(resp.Body) - if err != nil { - return errors.Errorf("failed to read http response: %w", err) - } - return errors.Errorf("Error status %d: %s", resp.StatusCode, body) - } return nil } -func DownloadStorageObject(ctx context.Context, projectRef, remotePath, localPath string, fsys afero.Fs) error { - apiKey, err := tenant.GetApiKeys(ctx, projectRef) +func (s *StorageAPI) DownloadObject(ctx context.Context, remotePath, localPath string, fsys afero.Fs) error { + remotePath = strings.TrimPrefix(remotePath, "/") + resp, err := s.Send(ctx, http.MethodGet, "/storage/v1/object/"+remotePath, nil) if err != nil { return err } - remotePath = strings.TrimPrefix(remotePath, "/") - url := fmt.Sprintf("https://%s/storage/v1/object/%s", utils.GetSupabaseHost(projectRef), remotePath) - return utils.DownloadFile(ctx, localPath, url, fsys, func(ctx context.Context, req *http.Request) error { - req.Header.Add("Authorization", "Bearer "+apiKey.ServiceRole) - return nil - }) + defer resp.Body.Close() + if err := afero.WriteReader(fsys, localPath, resp.Body); err != nil { + return errors.Errorf("failed to write file: %w", err) + } + return nil } type MoveObjectRequest struct { @@ -146,18 +134,18 @@ type MoveObjectRequest struct { type MoveObjectResponse = DeleteBucketResponse -func MoveStorageObject(ctx context.Context, projectRef, bucketId, srcPath, dstPath string) (*MoveObjectResponse, error) { - url := fmt.Sprintf("https://%s/storage/v1/object/move", utils.GetSupabaseHost(projectRef)) - apiKey, err := tenant.GetApiKeys(ctx, projectRef) - if err != nil { - return nil, err - } +func (s *StorageAPI) MoveObject(ctx context.Context, bucketId, srcPath, dstPath string) (*MoveObjectResponse, error) { body := MoveObjectRequest{ BucketId: bucketId, SourceKey: srcPath, DestinationKey: dstPath, } - return tenant.JsonResponseWithBearer[MoveObjectResponse](ctx, http.MethodPost, url, apiKey.ServiceRole, body) + resp, err := s.Send(ctx, http.MethodPost, "/storage/v1/object/move", body) + if err != nil { + return nil, err + } + defer resp.Body.Close() + return fetcher.ParseJSON[MoveObjectResponse](resp.Body) } type CopyObjectRequest = MoveObjectRequest @@ -166,18 +154,18 @@ type CopyObjectResponse struct { Key string `json:"key"` } -func CopyStorageObject(ctx context.Context, projectRef, bucketId, srcPath, dstPath string) (*CopyObjectResponse, error) { - url := fmt.Sprintf("https://%s/storage/v1/object/copy", utils.GetSupabaseHost(projectRef)) - apiKey, err := tenant.GetApiKeys(ctx, projectRef) - if err != nil { - return nil, err - } +func (s *StorageAPI) CopyObject(ctx context.Context, bucketId, srcPath, dstPath string) (*CopyObjectResponse, error) { body := CopyObjectRequest{ BucketId: bucketId, SourceKey: srcPath, DestinationKey: dstPath, } - return tenant.JsonResponseWithBearer[CopyObjectResponse](ctx, http.MethodPost, url, apiKey.ServiceRole, body) + resp, err := s.Send(ctx, http.MethodPost, "/storage/v1/object/copy", body) + if err != nil { + return nil, err + } + defer resp.Body.Close() + return fetcher.ParseJSON[CopyObjectResponse](resp.Body) } type DeleteObjectsRequest struct { @@ -197,14 +185,14 @@ type DeleteObjectsResponse struct { Metadata ObjectMetadata `json:"metadata"` // null } -func DeleteStorageObjects(ctx context.Context, projectRef, bucket string, prefixes []string) ([]DeleteObjectsResponse, error) { - url := fmt.Sprintf("https://%s/storage/v1/object/%s", utils.GetSupabaseHost(projectRef), bucket) - apiKey, err := tenant.GetApiKeys(ctx, projectRef) +func (s *StorageAPI) DeleteObjects(ctx context.Context, bucket string, prefixes []string) ([]DeleteObjectsResponse, error) { + body := DeleteObjectsRequest{Prefixes: prefixes} + resp, err := s.Send(ctx, http.MethodDelete, "/storage/v1/object/"+bucket, body) if err != nil { return nil, err } - body := DeleteObjectsRequest{Prefixes: prefixes} - data, err := tenant.JsonResponseWithBearer[[]DeleteObjectsResponse](ctx, http.MethodDelete, url, apiKey.ServiceRole, body) + defer resp.Body.Close() + data, err := fetcher.ParseJSON[[]DeleteObjectsResponse](resp.Body) if err != nil { return nil, err }