feat: add regions endpoint for proxies feature (#7277)

* feat: add regions endpoint for proxies feature
This commit is contained in:
Dean Sheather
2023-04-25 07:37:52 -07:00
committed by GitHub
parent 6e8ff2d95c
commit a98341612c
13 changed files with 625 additions and 6 deletions

65
coderd/apidoc/docs.go generated
View File

@ -1727,6 +1727,31 @@ const docTemplate = `{
}
}
},
"/regions": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"WorkspaceProxies"
],
"summary": "Get site-wide regions for workspace connections",
"operationId": "get-site-wide-regions-for-workspace-connections",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.RegionsResponse"
}
}
}
}
},
"/replicas": {
"get": {
"security": [
@ -8316,6 +8341,46 @@ const docTemplate = `{
}
}
},
"codersdk.Region": {
"type": "object",
"properties": {
"display_name": {
"type": "string"
},
"healthy": {
"type": "boolean"
},
"icon_url": {
"type": "string"
},
"id": {
"type": "string",
"format": "uuid"
},
"name": {
"type": "string"
},
"path_app_url": {
"description": "PathAppURL is the URL to the base path for path apps. Optional\nunless wildcard_hostname is set.\nE.g. https://us.example.com",
"type": "string"
},
"wildcard_hostname": {
"description": "WildcardHostname is the wildcard hostname for subdomain apps.\nE.g. *.us.example.com\nE.g. *--suffix.au.example.com\nOptional. Does not need to be on the same domain as PathAppURL.",
"type": "string"
}
}
},
"codersdk.RegionsResponse": {
"type": "object",
"properties": {
"regions": {
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.Region"
}
}
}
},
"codersdk.Replica": {
"type": "object",
"properties": {

View File

@ -1501,6 +1501,27 @@
}
}
},
"/regions": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": ["application/json"],
"tags": ["WorkspaceProxies"],
"summary": "Get site-wide regions for workspace connections",
"operationId": "get-site-wide-regions-for-workspace-connections",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.RegionsResponse"
}
}
}
}
},
"/replicas": {
"get": {
"security": [
@ -7455,6 +7476,46 @@
}
}
},
"codersdk.Region": {
"type": "object",
"properties": {
"display_name": {
"type": "string"
},
"healthy": {
"type": "boolean"
},
"icon_url": {
"type": "string"
},
"id": {
"type": "string",
"format": "uuid"
},
"name": {
"type": "string"
},
"path_app_url": {
"description": "PathAppURL is the URL to the base path for path apps. Optional\nunless wildcard_hostname is set.\nE.g. https://us.example.com",
"type": "string"
},
"wildcard_hostname": {
"description": "WildcardHostname is the wildcard hostname for subdomain apps.\nE.g. *.us.example.com\nE.g. *--suffix.au.example.com\nOptional. Does not need to be on the same domain as PathAppURL.",
"type": "string"
}
}
},
"codersdk.RegionsResponse": {
"type": "object",
"properties": {
"regions": {
"type": "array",
"items": {
"$ref": "#/definitions/codersdk.Region"
}
}
}
},
"codersdk.Replica": {
"type": "object",
"properties": {

View File

@ -461,6 +461,11 @@ func New(options *Options) *API {
r.Post("/csp/reports", api.logReportCSPViolations)
r.Get("/buildinfo", buildInfo(api.AccessURL))
// /regions is overridden in the enterprise version
r.Group(func(r chi.Router) {
r.Use(apiKeyMiddleware)
r.Get("/regions", api.regions)
})
r.Route("/deployment", func(r chi.Router) {
r.Use(apiKeyMiddleware)
r.Get("/config", api.deploymentValues)

View File

@ -0,0 +1,67 @@
package coderd
import (
"context"
"database/sql"
"net/http"
"github.com/google/uuid"
"golang.org/x/xerrors"
"github.com/coder/coder/coderd/database/dbauthz"
"github.com/coder/coder/coderd/httpapi"
"github.com/coder/coder/codersdk"
)
func (api *API) PrimaryRegion(ctx context.Context) (codersdk.Region, error) {
deploymentIDStr, err := api.Database.GetDeploymentID(ctx)
if xerrors.Is(err, sql.ErrNoRows) {
// This shouldn't happen but it's pretty easy to avoid this causing
// issues by falling back to a nil UUID.
deploymentIDStr = uuid.Nil.String()
} else if err != nil {
return codersdk.Region{}, xerrors.Errorf("get deployment ID: %w", err)
}
deploymentID, err := uuid.Parse(deploymentIDStr)
if err != nil {
// This also shouldn't happen but we fallback to nil UUID.
deploymentID = uuid.Nil
}
return codersdk.Region{
ID: deploymentID,
// TODO: provide some way to customize these fields for the primary
// region
Name: "primary",
DisplayName: "Default",
IconURL: "/emojis/1f60e.png", // face with sunglasses
Healthy: true,
PathAppURL: api.AccessURL.String(),
WildcardHostname: api.AppHostname,
}, nil
}
// @Summary Get site-wide regions for workspace connections
// @ID get-site-wide-regions-for-workspace-connections
// @Security CoderSessionToken
// @Produce json
// @Tags WorkspaceProxies
// @Success 200 {object} codersdk.RegionsResponse
// @Router /regions [get]
func (api *API) regions(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
//nolint:gocritic // this route intentionally requests resources that users
// cannot usually access in order to give them a full list of available
// regions.
ctx = dbauthz.AsSystemRestricted(ctx)
region, err := api.PrimaryRegion(ctx)
if err != nil {
httpapi.InternalServerError(rw, err)
return
}
httpapi.Write(ctx, rw, http.StatusOK, codersdk.RegionsResponse{
Regions: []codersdk.Region{region},
})
}

View File

@ -0,0 +1,67 @@
package coderd_test
import (
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/coderd/database/dbtestutil"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/testutil"
)
func TestRegions(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
const appHostname = "*.apps.coder.test"
db, pubsub := dbtestutil.NewDB(t)
deploymentID := uuid.New()
ctx := testutil.Context(t, testutil.WaitLong)
err := db.InsertDeploymentID(ctx, deploymentID.String())
require.NoError(t, err)
client := coderdtest.New(t, &coderdtest.Options{
AppHostname: appHostname,
Database: db,
Pubsub: pubsub,
})
_ = coderdtest.CreateFirstUser(t, client)
regions, err := client.Regions(ctx)
require.NoError(t, err)
require.Len(t, regions, 1)
require.NotEqual(t, uuid.Nil, regions[0].ID)
require.Equal(t, regions[0].ID, deploymentID)
require.Equal(t, "primary", regions[0].Name)
require.Equal(t, "Default", regions[0].DisplayName)
require.NotEmpty(t, regions[0].IconURL)
require.True(t, regions[0].Healthy)
require.Equal(t, client.URL.String(), regions[0].PathAppURL)
require.Equal(t, appHostname, regions[0].WildcardHostname)
// Ensure the primary region ID is constant.
regions2, err := client.Regions(ctx)
require.NoError(t, err)
require.Equal(t, regions[0].ID, regions2[0].ID)
})
t.Run("RequireAuth", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
client := coderdtest.New(t, nil)
_ = coderdtest.CreateFirstUser(t, client)
unauthedClient := codersdk.New(client.URL)
regions, err := unauthedClient.Regions(ctx)
require.Error(t, err)
require.Empty(t, regions)
})
}