supabase-cli/internal/hostnames/common.go

136 lines
5.2 KiB
Go

package hostnames
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/go-errors/errors"
"github.com/supabase/cli/internal/utils"
"github.com/supabase/cli/pkg/api"
)
func GetCustomHostnameConfig(ctx context.Context, projectRef string) (*api.V1GetHostnameConfigResponse, error) {
resp, err := utils.GetSupabase().V1GetHostnameConfigWithResponse(ctx, projectRef)
if err != nil {
return nil, errors.Errorf("failed to get custom hostname: %w", err)
}
if resp.JSON200 == nil {
return nil, errors.New("failed to get custom hostname config; received: " + string(resp.Body))
}
return resp, nil
}
func VerifyCNAME(ctx context.Context, projectRef string, customHostname string) error {
expectedEndpoint := fmt.Sprintf("%s.", utils.GetSupabaseHost(projectRef))
cname, err := utils.ResolveCNAME(ctx, customHostname)
if err != nil {
return errors.Errorf("expected custom hostname '%s' to have a CNAME record pointing to your project at '%s', but it failed to resolve: %w", customHostname, expectedEndpoint, err)
}
if cname != expectedEndpoint {
return errors.Errorf("expected custom hostname '%s' to have a CNAME record pointing to your project at '%s', but it is currently set to '%s'", customHostname, expectedEndpoint, cname)
}
return nil
}
type RawResponse struct {
Result struct {
CustomOriginServer string `json:"custom_origin_server"`
OwnershipVerification struct {
Name string
Type string
Value string
} `json:"ownership_verification"`
Ssl struct {
ValidationRecords []struct {
Status string `json:"status"`
TxtName string `json:"txt_name"`
TxtValue string `json:"txt_value"`
} `json:"validation_records"`
ValidationErrors []struct {
Message string `json:"message"`
} `json:"validation_errors"`
Status string `json:"status"`
}
} `json:"result"`
}
func serializeRawOutput(response *api.UpdateCustomHostnameResponse) (string, error) {
output, err := json.MarshalIndent(response, "", " ")
if err != nil {
return "", errors.Errorf("failed to serialize json: %w", err)
}
return string(output), nil
}
func appendRawOutputIfNeeded(status string, response *api.UpdateCustomHostnameResponse, includeRawOutput bool) string {
if !includeRawOutput {
return status
}
rawOutput, err := serializeRawOutput(response)
if err != nil {
return fmt.Sprintf("%s\nFailed to serialize raw output: %+v\n", status, err)
}
return fmt.Sprintf("%s\nRaw output follows:\n%s\n", status, rawOutput)
}
func TranslateStatus(response *api.UpdateCustomHostnameResponse, includeRawOutput bool) (string, error) {
if response.Status == api.N5ServicesReconfigured {
return appendRawOutputIfNeeded(fmt.Sprintf("Custom hostname setup completed. Project is now accessible at %s.", response.CustomHostname), response, includeRawOutput), nil
}
if response.Status == api.N4OriginSetupCompleted {
var res RawResponse
rawBody, err := json.Marshal(response.Data)
if err != nil {
return "", errors.Errorf("failed to serialize body: %w", err)
}
err = json.Unmarshal(rawBody, &res)
if err != nil {
return "", errors.Errorf("failed to deserialize body: %w", err)
}
return appendRawOutputIfNeeded(fmt.Sprintf(`Custom hostname configuration complete, and ready for activation.
Please ensure that your custom domain is set up as a CNAME record to your Supabase subdomain:
%s CNAME -> %s`, response.CustomHostname, res.Result.CustomOriginServer), response, includeRawOutput), nil
}
if response.Status == api.N2Initiated {
var res RawResponse
rawBody, err := json.Marshal(response.Data)
if err != nil {
return "", errors.Errorf("failed to serialize body: %w", err)
}
err = json.Unmarshal(rawBody, &res)
if err != nil {
return "", errors.Errorf("failed to deserialize body: %w", err)
}
ssl := res.Result.Ssl.ValidationRecords
if res.Result.Ssl.Status == "initializing" {
return appendRawOutputIfNeeded("Custom hostname setup is being initialized; please request re-verification in a few seconds.\n", response, includeRawOutput), nil
}
if len(res.Result.Ssl.ValidationErrors) > 0 {
var errorMessages []string
for _, valError := range res.Result.Ssl.ValidationErrors {
if strings.Contains(valError.Message, "caa_error") {
return appendRawOutputIfNeeded("CAA mismatch; please remove any existing CAA records on your domain, or add one for \"digicert.com\"\n", response, includeRawOutput), nil
}
errorMessages = append(errorMessages, valError.Message)
}
valErrors := strings.Join(errorMessages, "\n\t- ")
return appendRawOutputIfNeeded(fmt.Sprintf("SSL validation errors: \n\t- %s\n", valErrors), response, includeRawOutput), nil
}
if len(ssl) != 1 {
return "", errors.Errorf("expected a single SSL verification record, received: %+v", ssl)
}
records := ""
if ssl[0].TxtName != "" {
records = fmt.Sprintf("%s\n\t%s TXT -> %s", records, ssl[0].TxtName, ssl[0].TxtValue)
}
status := fmt.Sprintf("Custom hostname verification in-progress; please configure the appropriate DNS entries and request re-verification.\n"+
"Required outstanding validation records: %s\n",
records)
return appendRawOutputIfNeeded(status, response, includeRawOutput), nil
}
return appendRawOutputIfNeeded("Custom hostname configuration not started.", response, includeRawOutput), nil
}