Skip to content

feat: add regions endpoint for proxies feature #7277

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

Merged
merged 5 commits into from
Apr 25, 2023
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
65 changes: 65 additions & 0 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

61 changes: 61 additions & 0 deletions coderd/apidoc/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
67 changes: 67 additions & 0 deletions coderd/workspaceproxies.go
Original file line number Diff line number Diff line change
@@ -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},
})
}
67 changes: 67 additions & 0 deletions coderd/workspaceproxies_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
41 changes: 41 additions & 0 deletions codersdk/workspaceproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,44 @@ func (c *Client) DeleteWorkspaceProxyByName(ctx context.Context, name string) er
func (c *Client) DeleteWorkspaceProxyByID(ctx context.Context, id uuid.UUID) error {
return c.DeleteWorkspaceProxyByName(ctx, id.String())
}

type RegionsResponse struct {
Regions []Region `json:"regions"`
}

type Region struct {
ID uuid.UUID `json:"id" format:"uuid"`
Name string `json:"name"`
DisplayName string `json:"display_name"`
IconURL string `json:"icon_url"`
Healthy bool `json:"healthy"`

// PathAppURL is the URL to the base path for path apps. Optional
// unless wildcard_hostname is set.
// E.g. https://us.example.com
PathAppURL string `json:"path_app_url"`

// WildcardHostname is the wildcard hostname for subdomain apps.
// E.g. *.us.example.com
// E.g. *--suffix.au.example.com
// Optional. Does not need to be on the same domain as PathAppURL.
WildcardHostname string `json:"wildcard_hostname"`
}

func (c *Client) Regions(ctx context.Context) ([]Region, error) {
res, err := c.Request(ctx, http.MethodGet,
"/api/v2/regions",
nil,
)
if err != nil {
return nil, xerrors.Errorf("make request: %w", err)
}
defer res.Body.Close()

if res.StatusCode != http.StatusOK {
return nil, ReadBodyAsError(res)
}

var regions RegionsResponse
return regions.Regions, json.NewDecoder(res.Body).Decode(&regions)
}
Loading