Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adds support for vanity subdomains #677

Merged
merged 1 commit into from
Dec 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 149 additions & 2 deletions api/beta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,119 @@ paths:
description: Failed to update project network restrictions
tags: *ref_18
security: *ref_19
/v1/projects/{ref}/vanity-subdomain:
get:
operationId: getVanitySubdomainConfig
summary: Gets current vanity subdomain config
parameters:
- name: ref
required: true
in: path
description: Project ref
schema:
minLength: 20
maxLength: 20
type: string
responses:
'200':
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/VanitySubdomainConfigResponse'
'403':
description: ''
'500':
description: Failed to get project vanity subdomain configuration
tags: &ref_20
- vanity subdomain (beta)
security: &ref_21
- bearer: []
delete:
operationId: removeVanitySubdomainConfig
summary: Deletes a project's vanity subdomain configuration
parameters:
- name: ref
required: true
in: path
description: Project ref
schema:
minLength: 20
maxLength: 20
type: string
responses:
'200':
description: ''
'403':
description: ''
'500':
description: Failed to delete project vanity subdomain configuration
tags: *ref_20
security: *ref_21
/v1/projects/{ref}/vanity-subdomain/check-availability:
post:
operationId: checkVanitySubdomainAvailability
summary: Checks vanity subdomain availability
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/VanitySubdomainBody'
responses:
'201':
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/SubdomainAvailabilityResponse'
'403':
description: ''
'500':
description: Failed to check project vanity subdomain configuration
tags: *ref_20
security: *ref_21
/v1/projects/{ref}/vanity-subdomain/activate:
post:
operationId: activateVanitySubdomainPlease
summary: Activates a vanity subdomain for a project.
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/VanitySubdomainBody'
responses:
'201':
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/ActivateVanitySubdomainResponse'
'403':
description: ''
'500':
description: Failed to activate project vanity subdomain configuration
tags: *ref_20
security: *ref_21
info:
title: Supabase API (v1)
description: ''
Expand Down Expand Up @@ -904,7 +1017,7 @@ components:
name:
type: string
status:
enum: &ref_20
enum: &ref_22
- ACTIVE
- REMOVED
- THROTTLED
Expand Down Expand Up @@ -937,7 +1050,7 @@ components:
name:
type: string
status:
enum: *ref_20
enum: *ref_22
type: string
version:
type: number
Expand Down Expand Up @@ -1075,3 +1188,37 @@ components:
- entitlement
- config
- status
VanitySubdomainConfigResponse:
type: object
properties:
status:
enum:
- not-used
- custom-domain-used
- active
type: string
custom_domain:
type: string
required:
- status
VanitySubdomainBody:
type: object
properties:
vanity_subdomain:
type: string
required:
- vanity_subdomain
SubdomainAvailabilityResponse:
type: object
properties:
available:
type: boolean
required:
- available
ActivateVanitySubdomainResponse:
type: object
properties:
custom_domain:
type: string
required:
- custom_domain
14 changes: 9 additions & 5 deletions cmd/domains.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ var (
GroupID: groupManagementAPI,
Use: "domains",
Short: "Manage custom domain names for Supabase projects",
Long: `Manage custom domain names for Supabase projects.

Use of custom domains and vanity subdomains is mutually exclusive.
`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if !experimental {
return errors.New("must set the --experimental flag to run this command")
Expand All @@ -32,7 +36,7 @@ var (

customHostnamesCreateCmd = &cobra.Command{
Use: "create",
Short: "Create a custom hostname.",
Short: "Create a custom hostname",
Long: `Create a custom hostname for your Supabase project.

Expects your custom hostname to have a CNAME record to your Supabase project's subdomain.`,
Expand All @@ -48,7 +52,7 @@ Expects your custom hostname to have a CNAME record to your Supabase project's s

customHostnamesGetCmd = &cobra.Command{
Use: "get",
Short: "Get the current custom hostname config.",
Short: "Get the current custom hostname config",
Long: "Retrieve the custom hostname config for your project, as stored in the Supabase platform.",
RunE: func(cmd *cobra.Command, args []string) error {
fsys := afero.NewOsFs()
Expand All @@ -62,7 +66,7 @@ Expects your custom hostname to have a CNAME record to your Supabase project's s

customHostnamesReverifyCmd = &cobra.Command{
Use: "reverify",
Short: "Re-verify the custom hostname config for your project.",
Short: "Re-verify the custom hostname config for your project",
RunE: func(cmd *cobra.Command, args []string) error {
ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
return reverify.Run(ctx, projectRef, rawOutput, afero.NewOsFs())
Expand All @@ -71,7 +75,7 @@ Expects your custom hostname to have a CNAME record to your Supabase project's s

customHostnamesActivateCmd = &cobra.Command{
Use: "activate",
Short: "Activate the custom hostname for a project.",
Short: "Activate the custom hostname for a project",
Long: `Activates the custom hostname configuration for a project.

This reconfigures your Supabase project to respond to requests on your custom hostname.
Expand All @@ -84,7 +88,7 @@ After the custom hostname is activated, your project's auth services will no lon

customHostnamesDeleteCmd = &cobra.Command{
Use: "delete",
Short: "Deletes the custom hostname config for your project.",
Short: "Deletes the custom hostname config for your project",
RunE: func(cmd *cobra.Command, args []string) error {
ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
return delete.Run(ctx, projectRef, afero.NewOsFs())
Expand Down
95 changes: 95 additions & 0 deletions cmd/vanitySubdomains.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package cmd

import (
"errors"
"os"
"os/signal"

"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/supabase/cli/internal/vanity_subdomains/activate"
"github.com/supabase/cli/internal/vanity_subdomains/check"
"github.com/supabase/cli/internal/vanity_subdomains/delete"
"github.com/supabase/cli/internal/vanity_subdomains/get"
)

var (
vanityCmd = &cobra.Command{
GroupID: groupManagementAPI,
Use: "vanity-subdomains",
Short: "Manage vanity subdomains for Supabase projects",
Long: `Manage vanity subdomains for Supabase projects.

Usage of vanity subdomains and custom domains is mutually exclusive.`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if !experimental {
return errors.New("must set the --experimental flag to run this command")
}
return cmd.Root().PersistentPreRunE(cmd, args)
},
}

desiredSubdomain string

vanityActivateCmd = &cobra.Command{
Use: "activate",
Short: "Activate a vanity subdomain",
Long: `Activate a vanity subdomain for your Supabase project.

This reconfigures your Supabase project to respond to requests on your vanity subdomain.
After the vanity subdomain is activated, your project's auth services will no longer function on the {project-ref}.{supabase-domain} hostname.
`,
RunE: func(cmd *cobra.Command, args []string) error {
fsys := afero.NewOsFs()
if err := PromptLogin(fsys); err != nil {
return err
}
ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
return activate.Run(ctx, projectRef, desiredSubdomain, fsys)
},
}

vanityGetCmd = &cobra.Command{
Use: "get",
Short: "Get the current vanity subdomain",
RunE: func(cmd *cobra.Command, args []string) error {
fsys := afero.NewOsFs()
if err := PromptLogin(fsys); err != nil {
return err
}
ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
return get.Run(ctx, projectRef, fsys)
},
}

vanityCheckCmd = &cobra.Command{
Use: "check-availability",
Short: "Checks if a desired subdomain is available for use",
RunE: func(cmd *cobra.Command, args []string) error {
ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
return check.Run(ctx, projectRef, desiredSubdomain, afero.NewOsFs())
},
}

vanityDeleteCmd = &cobra.Command{
Use: "delete",
Short: "Deletes a project's vanity subdomain",
Long: `Deletes the vanity subdomain for a project, and reverts to using the project ref for routing.`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
return delete.Run(ctx, projectRef, afero.NewOsFs())
},
}
)

func init() {
vanityCmd.PersistentFlags().StringVar(&projectRef, "project-ref", "", "Project ref of the Supabase project.")
vanityActivateCmd.Flags().StringVar(&desiredSubdomain, "desired-subdomain", "", "The desired vanity subdomain to use for your Supabase project.")
vanityCheckCmd.Flags().StringVar(&desiredSubdomain, "desired-subdomain", "", "The desired vanity subdomain to use for your Supabase project.")
vanityCmd.AddCommand(vanityGetCmd)
vanityCmd.AddCommand(vanityCheckCmd)
vanityCmd.AddCommand(vanityActivateCmd)
vanityCmd.AddCommand(vanityDeleteCmd)

rootCmd.AddCommand(vanityCmd)
}
47 changes: 47 additions & 0 deletions internal/vanity_subdomains/activate/activate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package activate

import (
"context"
"errors"
"fmt"
"strings"

"github.com/spf13/afero"
"github.com/supabase/cli/internal/utils"
"github.com/supabase/cli/pkg/api"
)

func Run(ctx context.Context, projectRefArg string, desiredSubdomain string, fsys afero.Fs) error {
// 1. Sanity checks.
projectRef := projectRefArg
subdomain := strings.TrimSpace(desiredSubdomain)
{
if len(projectRefArg) == 0 {
ref, err := utils.LoadProjectRef(fsys)
if err != nil {
return err
}
projectRef = ref
} else if !utils.ProjectRefPattern.MatchString(projectRef) {
return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
}
if len(subdomain) == 0 {
return errors.New("non-empty vanity subdomain expected")
}
}

// 2. create vanity subdomain
{
resp, err := utils.GetSupabase().ActivateVanitySubdomainPleaseWithResponse(ctx, projectRef, api.ActivateVanitySubdomainPleaseJSONRequestBody{
VanitySubdomain: subdomain,
})
if err != nil {
return err
}
if resp.JSON201 == nil {
return errors.New("failed to create vanity subdomain config: " + string(resp.Body))
}
fmt.Printf("Activated vanity subdomain at %s\n", resp.JSON201.CustomDomain)
return nil
}
}
Loading