coder/codersdk/workspaceproxy.go

223 lines
7.1 KiB
Go

package codersdk
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
"golang.org/x/xerrors"
"github.com/google/uuid"
)
type ProxyHealthStatus string
const (
// ProxyHealthy means the proxy access url is reachable and returns a healthy
// status code.
ProxyHealthy ProxyHealthStatus = "ok"
// ProxyUnreachable means the proxy access url is not responding.
ProxyUnreachable ProxyHealthStatus = "unreachable"
// ProxyUnhealthy means the proxy access url is responding, but there is some
// problem with the proxy. This problem may or may not be preventing functionality.
ProxyUnhealthy ProxyHealthStatus = "unhealthy"
// ProxyUnregistered means the proxy has not registered a url yet. This means
// the proxy was created with the cli, but has not yet been started.
ProxyUnregistered ProxyHealthStatus = "unregistered"
)
type WorkspaceProxyStatus struct {
Status ProxyHealthStatus `json:"status" table:"status,default_sort"`
// Report provides more information about the health of the workspace proxy.
Report ProxyHealthReport `json:"report,omitempty" table:"report"`
CheckedAt time.Time `json:"checked_at" table:"checked_at" format:"date-time"`
}
// ProxyHealthReport is a report of the health of the workspace proxy.
// A healthy report will have no errors. Warnings are not fatal.
type ProxyHealthReport struct {
// Errors are problems that prevent the workspace proxy from being healthy
Errors []string `json:"errors"`
// Warnings do not prevent the workspace proxy from being healthy, but
// should be addressed.
Warnings []string `json:"warnings"`
}
type WorkspaceProxy struct {
// Extends Region with extra information
Region `table:"region,recursive_inline"`
DerpEnabled bool `json:"derp_enabled" table:"derp_enabled"`
DerpOnly bool `json:"derp_only" table:"derp_only"`
// Status is the latest status check of the proxy. This will be empty for deleted
// proxies. This value can be used to determine if a workspace proxy is healthy
// and ready to use.
Status WorkspaceProxyStatus `json:"status,omitempty" table:"proxy,recursive"`
CreatedAt time.Time `json:"created_at" format:"date-time" table:"created_at"`
UpdatedAt time.Time `json:"updated_at" format:"date-time" table:"updated_at"`
Deleted bool `json:"deleted" table:"deleted"`
Version string `json:"version" table:"version"`
}
type CreateWorkspaceProxyRequest struct {
Name string `json:"name" validate:"required"`
DisplayName string `json:"display_name"`
Icon string `json:"icon"`
}
type UpdateWorkspaceProxyResponse struct {
Proxy WorkspaceProxy `json:"proxy" table:"p,recursive_inline"`
ProxyToken string `json:"proxy_token" table:"proxy token"`
}
func (c *Client) CreateWorkspaceProxy(ctx context.Context, req CreateWorkspaceProxyRequest) (UpdateWorkspaceProxyResponse, error) {
res, err := c.Request(ctx, http.MethodPost,
"/api/v2/workspaceproxies",
req,
)
if err != nil {
return UpdateWorkspaceProxyResponse{}, xerrors.Errorf("make request: %w", err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
return UpdateWorkspaceProxyResponse{}, ReadBodyAsError(res)
}
var resp UpdateWorkspaceProxyResponse
return resp, json.NewDecoder(res.Body).Decode(&resp)
}
func (c *Client) WorkspaceProxies(ctx context.Context) (RegionsResponse[WorkspaceProxy], error) {
res, err := c.Request(ctx, http.MethodGet,
"/api/v2/workspaceproxies",
nil,
)
if err != nil {
return RegionsResponse[WorkspaceProxy]{}, xerrors.Errorf("make request: %w", err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return RegionsResponse[WorkspaceProxy]{}, ReadBodyAsError(res)
}
var proxies RegionsResponse[WorkspaceProxy]
return proxies, json.NewDecoder(res.Body).Decode(&proxies)
}
type PatchWorkspaceProxy struct {
ID uuid.UUID `json:"id" format:"uuid" validate:"required"`
Name string `json:"name" validate:"required"`
DisplayName string `json:"display_name" validate:"required"`
Icon string `json:"icon" validate:"required"`
RegenerateToken bool `json:"regenerate_token"`
}
func (c *Client) PatchWorkspaceProxy(ctx context.Context, req PatchWorkspaceProxy) (UpdateWorkspaceProxyResponse, error) {
res, err := c.Request(ctx, http.MethodPatch,
fmt.Sprintf("/api/v2/workspaceproxies/%s", req.ID.String()),
req,
)
if err != nil {
return UpdateWorkspaceProxyResponse{}, xerrors.Errorf("make request: %w", err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return UpdateWorkspaceProxyResponse{}, ReadBodyAsError(res)
}
var resp UpdateWorkspaceProxyResponse
return resp, json.NewDecoder(res.Body).Decode(&resp)
}
func (c *Client) DeleteWorkspaceProxyByName(ctx context.Context, name string) error {
res, err := c.Request(ctx, http.MethodDelete,
fmt.Sprintf("/api/v2/workspaceproxies/%s", name),
nil,
)
if err != nil {
return xerrors.Errorf("make request: %w", err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return ReadBodyAsError(res)
}
return nil
}
func (c *Client) DeleteWorkspaceProxyByID(ctx context.Context, id uuid.UUID) error {
return c.DeleteWorkspaceProxyByName(ctx, id.String())
}
func (c *Client) WorkspaceProxyByName(ctx context.Context, name string) (WorkspaceProxy, error) {
res, err := c.Request(ctx, http.MethodGet,
fmt.Sprintf("/api/v2/workspaceproxies/%s", name),
nil,
)
if err != nil {
return WorkspaceProxy{}, xerrors.Errorf("make request: %w", err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return WorkspaceProxy{}, ReadBodyAsError(res)
}
var resp WorkspaceProxy
return resp, json.NewDecoder(res.Body).Decode(&resp)
}
func (c *Client) WorkspaceProxyByID(ctx context.Context, id uuid.UUID) (WorkspaceProxy, error) {
return c.WorkspaceProxyByName(ctx, id.String())
}
type RegionTypes interface {
Region | WorkspaceProxy
}
type RegionsResponse[R RegionTypes] struct {
Regions []R `json:"regions"`
}
type Region struct {
ID uuid.UUID `json:"id" format:"uuid" table:"id"`
Name string `json:"name" table:"name,default_sort"`
DisplayName string `json:"display_name" table:"display_name"`
IconURL string `json:"icon_url" table:"icon_url"`
Healthy bool `json:"healthy" table:"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" table:"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" table:"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[Region]
return regions.Regions, json.NewDecoder(res.Body).Decode(&regions)
}