mirror of https://github.com/coder/coder.git
chore: Allow editing proxy fields via api. (#7435)
* chore: Add ability to update workspace proxy fields
This commit is contained in:
parent
fc1bc374cb
commit
b5ad628460
|
@ -5178,6 +5178,39 @@ const docTemplate = `{
|
|||
}
|
||||
},
|
||||
"/workspaceproxies/{workspaceproxy}": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Enterprise"
|
||||
],
|
||||
"summary": "Get workspace proxy",
|
||||
"operationId": "get-workspace-proxy",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"description": "Proxy ID or name",
|
||||
"name": "workspaceproxy",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.WorkspaceProxy"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
|
@ -5210,6 +5243,51 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"patch": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Enterprise"
|
||||
],
|
||||
"summary": "Update workspace proxy",
|
||||
"operationId": "update-workspace-proxy",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"description": "Proxy ID or name",
|
||||
"name": "workspaceproxy",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Update workspace proxy request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.PatchWorkspaceProxy"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.WorkspaceProxy"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspaces": {
|
||||
|
@ -8074,6 +8152,33 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"codersdk.PatchWorkspaceProxy": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"display_name",
|
||||
"icon",
|
||||
"id",
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"regenerate_token": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.PprofConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -9856,6 +9961,9 @@ const docTemplate = `{
|
|||
"deleted": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
@ -4554,6 +4554,35 @@
|
|||
}
|
||||
},
|
||||
"/workspaceproxies/{workspaceproxy}": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": ["application/json"],
|
||||
"tags": ["Enterprise"],
|
||||
"summary": "Get workspace proxy",
|
||||
"operationId": "get-workspace-proxy",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"description": "Proxy ID or name",
|
||||
"name": "workspaceproxy",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.WorkspaceProxy"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
|
@ -4582,6 +4611,45 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"patch": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"consumes": ["application/json"],
|
||||
"produces": ["application/json"],
|
||||
"tags": ["Enterprise"],
|
||||
"summary": "Update workspace proxy",
|
||||
"operationId": "update-workspace-proxy",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"description": "Proxy ID or name",
|
||||
"name": "workspaceproxy",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Update workspace proxy request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.PatchWorkspaceProxy"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.WorkspaceProxy"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspaces": {
|
||||
|
@ -7216,6 +7284,28 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"codersdk.PatchWorkspaceProxy": {
|
||||
"type": "object",
|
||||
"required": ["display_name", "icon", "id", "name"],
|
||||
"properties": {
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"regenerate_token": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.PprofConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -8893,6 +8983,9 @@
|
|||
"deleted": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
@ -1705,6 +1705,13 @@ func (q *querier) InsertWorkspaceProxy(ctx context.Context, arg database.InsertW
|
|||
return insert(q.log, q.auth, rbac.ResourceWorkspaceProxy, q.db.InsertWorkspaceProxy)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateWorkspaceProxy(ctx context.Context, arg database.UpdateWorkspaceProxyParams) (database.WorkspaceProxy, error) {
|
||||
fetch := func(ctx context.Context, arg database.UpdateWorkspaceProxyParams) (database.WorkspaceProxy, error) {
|
||||
return q.db.GetWorkspaceProxyByID(ctx, arg.ID)
|
||||
}
|
||||
return updateWithReturn(q.log, q.auth, fetch, q.db.UpdateWorkspaceProxy)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) RegisterWorkspaceProxy(ctx context.Context, arg database.RegisterWorkspaceProxyParams) (database.WorkspaceProxy, error) {
|
||||
fetch := func(ctx context.Context, arg database.RegisterWorkspaceProxyParams) (database.WorkspaceProxy, error) {
|
||||
return q.db.GetWorkspaceProxyByID(ctx, arg.ID)
|
||||
|
|
|
@ -5233,6 +5233,31 @@ func (q *fakeQuerier) RegisterWorkspaceProxy(_ context.Context, arg database.Reg
|
|||
return database.WorkspaceProxy{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) UpdateWorkspaceProxy(_ context.Context, arg database.UpdateWorkspaceProxyParams) (database.WorkspaceProxy, error) {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
for _, p := range q.workspaceProxies {
|
||||
if p.Name == arg.Name && p.ID != arg.ID {
|
||||
return database.WorkspaceProxy{}, errDuplicateKey
|
||||
}
|
||||
}
|
||||
|
||||
for i, p := range q.workspaceProxies {
|
||||
if p.ID == arg.ID {
|
||||
p.Name = arg.Name
|
||||
p.DisplayName = arg.DisplayName
|
||||
p.Icon = arg.Icon
|
||||
if len(p.TokenHashedSecret) > 0 {
|
||||
p.TokenHashedSecret = arg.TokenHashedSecret
|
||||
}
|
||||
q.workspaceProxies[i] = p
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
return database.WorkspaceProxy{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) UpdateWorkspaceProxyDeleted(_ context.Context, arg database.UpdateWorkspaceProxyDeletedParams) error {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
|
|
@ -255,6 +255,8 @@ type sqlcQuerier interface {
|
|||
UpdateWorkspaceBuildCostByID(ctx context.Context, arg UpdateWorkspaceBuildCostByIDParams) (WorkspaceBuild, error)
|
||||
UpdateWorkspaceDeletedByID(ctx context.Context, arg UpdateWorkspaceDeletedByIDParams) error
|
||||
UpdateWorkspaceLastUsedAt(ctx context.Context, arg UpdateWorkspaceLastUsedAtParams) error
|
||||
// This allows editing the properties of a workspace proxy.
|
||||
UpdateWorkspaceProxy(ctx context.Context, arg UpdateWorkspaceProxyParams) (WorkspaceProxy, error)
|
||||
UpdateWorkspaceProxyDeleted(ctx context.Context, arg UpdateWorkspaceProxyDeletedParams) error
|
||||
UpdateWorkspaceTTL(ctx context.Context, arg UpdateWorkspaceTTLParams) error
|
||||
UpdateWorkspaceTTLToBeWithinTemplateMax(ctx context.Context, arg UpdateWorkspaceTTLToBeWithinTemplateMaxParams) error
|
||||
|
|
|
@ -3077,6 +3077,60 @@ func (q *sqlQuerier) RegisterWorkspaceProxy(ctx context.Context, arg RegisterWor
|
|||
return i, err
|
||||
}
|
||||
|
||||
const updateWorkspaceProxy = `-- name: UpdateWorkspaceProxy :one
|
||||
UPDATE
|
||||
workspace_proxies
|
||||
SET
|
||||
-- These values should always be provided.
|
||||
name = $1,
|
||||
display_name = $2,
|
||||
icon = $3,
|
||||
-- Only update the token if a new one is provided.
|
||||
-- So this is an optional field.
|
||||
token_hashed_secret = CASE
|
||||
WHEN length($4 :: bytea) > 0 THEN $4 :: bytea
|
||||
ELSE workspace_proxies.token_hashed_secret
|
||||
END,
|
||||
-- Always update this timestamp.
|
||||
updated_at = Now()
|
||||
WHERE
|
||||
id = $5
|
||||
RETURNING id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted, token_hashed_secret
|
||||
`
|
||||
|
||||
type UpdateWorkspaceProxyParams struct {
|
||||
Name string `db:"name" json:"name"`
|
||||
DisplayName string `db:"display_name" json:"display_name"`
|
||||
Icon string `db:"icon" json:"icon"`
|
||||
TokenHashedSecret []byte `db:"token_hashed_secret" json:"token_hashed_secret"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
}
|
||||
|
||||
// This allows editing the properties of a workspace proxy.
|
||||
func (q *sqlQuerier) UpdateWorkspaceProxy(ctx context.Context, arg UpdateWorkspaceProxyParams) (WorkspaceProxy, error) {
|
||||
row := q.db.QueryRowContext(ctx, updateWorkspaceProxy,
|
||||
arg.Name,
|
||||
arg.DisplayName,
|
||||
arg.Icon,
|
||||
arg.TokenHashedSecret,
|
||||
arg.ID,
|
||||
)
|
||||
var i WorkspaceProxy
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.DisplayName,
|
||||
&i.Icon,
|
||||
&i.Url,
|
||||
&i.WildcardHostname,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Deleted,
|
||||
&i.TokenHashedSecret,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateWorkspaceProxyDeleted = `-- name: UpdateWorkspaceProxyDeleted :exec
|
||||
UPDATE
|
||||
workspace_proxies
|
||||
|
|
|
@ -36,6 +36,28 @@ SET
|
|||
WHERE
|
||||
id = @id;
|
||||
|
||||
-- name: UpdateWorkspaceProxy :one
|
||||
-- This allows editing the properties of a workspace proxy.
|
||||
UPDATE
|
||||
workspace_proxies
|
||||
SET
|
||||
-- These values should always be provided.
|
||||
name = @name,
|
||||
display_name = @display_name,
|
||||
icon = @icon,
|
||||
-- Only update the token if a new one is provided.
|
||||
-- So this is an optional field.
|
||||
token_hashed_secret = CASE
|
||||
WHEN length(@token_hashed_secret :: bytea) > 0 THEN @token_hashed_secret :: bytea
|
||||
ELSE workspace_proxies.token_hashed_secret
|
||||
END,
|
||||
-- Always update this timestamp.
|
||||
updated_at = Now()
|
||||
WHERE
|
||||
id = @id
|
||||
RETURNING *
|
||||
;
|
||||
|
||||
-- name: GetWorkspaceProxyByID :one
|
||||
SELECT
|
||||
*
|
||||
|
@ -57,6 +79,14 @@ WHERE
|
|||
LIMIT
|
||||
1;
|
||||
|
||||
-- name: GetWorkspaceProxies :many
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
workspace_proxies
|
||||
WHERE
|
||||
deleted = false;
|
||||
|
||||
-- Finds a workspace proxy that has an access URL or app hostname that matches
|
||||
-- the provided hostname. This is to check if a hostname matches any workspace
|
||||
-- proxy.
|
||||
|
@ -94,11 +124,3 @@ WHERE
|
|||
)
|
||||
LIMIT
|
||||
1;
|
||||
|
||||
-- name: GetWorkspaceProxies :many
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
workspace_proxies
|
||||
WHERE
|
||||
deleted = false;
|
||||
|
|
|
@ -46,9 +46,10 @@ type ProxyHealthReport struct {
|
|||
}
|
||||
|
||||
type WorkspaceProxy struct {
|
||||
ID uuid.UUID `json:"id" format:"uuid" table:"id"`
|
||||
Name string `json:"name" table:"name,default_sort"`
|
||||
Icon string `json:"icon" table:"icon"`
|
||||
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"`
|
||||
Icon string `json:"icon" table:"icon"`
|
||||
// Full url including scheme of the proxy api url: https://us.example.com
|
||||
URL string `json:"url" table:"url"`
|
||||
// WildcardHostname with the wildcard for subdomain based app hosting: *.us.example.com
|
||||
|
@ -69,26 +70,26 @@ type CreateWorkspaceProxyRequest struct {
|
|||
Icon string `json:"icon"`
|
||||
}
|
||||
|
||||
type CreateWorkspaceProxyResponse struct {
|
||||
type UpdateWorkspaceProxyResponse struct {
|
||||
Proxy WorkspaceProxy `json:"proxy" table:"proxy,recursive"`
|
||||
// The recursive table sort is not working very well.
|
||||
ProxyToken string `json:"proxy_token" table:"proxy token,default_sort"`
|
||||
}
|
||||
|
||||
func (c *Client) CreateWorkspaceProxy(ctx context.Context, req CreateWorkspaceProxyRequest) (CreateWorkspaceProxyResponse, error) {
|
||||
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 CreateWorkspaceProxyResponse{}, xerrors.Errorf("make request: %w", err)
|
||||
return UpdateWorkspaceProxyResponse{}, xerrors.Errorf("make request: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusCreated {
|
||||
return CreateWorkspaceProxyResponse{}, ReadBodyAsError(res)
|
||||
return UpdateWorkspaceProxyResponse{}, ReadBodyAsError(res)
|
||||
}
|
||||
var resp CreateWorkspaceProxyResponse
|
||||
var resp UpdateWorkspaceProxyResponse
|
||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
|
@ -110,6 +111,31 @@ func (c *Client) WorkspaceProxies(ctx context.Context) ([]WorkspaceProxy, error)
|
|||
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),
|
||||
|
@ -131,6 +157,28 @@ func (c *Client) DeleteWorkspaceProxyByID(ctx context.Context, id uuid.UUID) err
|
|||
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 RegionsResponse struct {
|
||||
Regions []Region `json:"regions"`
|
||||
}
|
||||
|
|
|
@ -1182,6 +1182,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaceproxies \
|
|||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"deleted": true,
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
|
@ -1215,6 +1216,7 @@ Status Code **200**
|
|||
| `[array item]` | array | false | | |
|
||||
| `» created_at` | string(date-time) | false | | |
|
||||
| `» deleted` | boolean | false | | |
|
||||
| `» display_name` | string | false | | |
|
||||
| `» icon` | string | false | | |
|
||||
| `» id` | string(uuid) | false | | |
|
||||
| `» name` | string | false | | |
|
||||
|
@ -1277,6 +1279,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaceproxies \
|
|||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"deleted": true,
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
|
@ -1302,6 +1305,59 @@ curl -X POST http://coder-server:8080/api/v2/workspaceproxies \
|
|||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## Get workspace proxy
|
||||
|
||||
### Code samples
|
||||
|
||||
```shell
|
||||
# Example request using curl
|
||||
curl -X GET http://coder-server:8080/api/v2/workspaceproxies/{workspaceproxy} \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Coder-Session-Token: API_KEY'
|
||||
```
|
||||
|
||||
`GET /workspaceproxies/{workspaceproxy}`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | In | Type | Required | Description |
|
||||
| ---------------- | ---- | ------------ | -------- | ---------------- |
|
||||
| `workspaceproxy` | path | string(uuid) | true | Proxy ID or name |
|
||||
|
||||
### Example responses
|
||||
|
||||
> 200 Response
|
||||
|
||||
```json
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"deleted": true,
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"status": {
|
||||
"checked_at": "2019-08-24T14:15:22Z",
|
||||
"report": {
|
||||
"errors": ["string"],
|
||||
"warnings": ["string"]
|
||||
},
|
||||
"status": "reachable"
|
||||
},
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"url": "string",
|
||||
"wildcard_hostname": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### Responses
|
||||
|
||||
| Status | Meaning | Description | Schema |
|
||||
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------------------ |
|
||||
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.WorkspaceProxy](schemas.md#codersdkworkspaceproxy) |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## Delete workspace proxy
|
||||
|
||||
### Code samples
|
||||
|
@ -1345,3 +1401,70 @@ curl -X DELETE http://coder-server:8080/api/v2/workspaceproxies/{workspaceproxy}
|
|||
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Response](schemas.md#codersdkresponse) |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## Update workspace proxy
|
||||
|
||||
### Code samples
|
||||
|
||||
```shell
|
||||
# Example request using curl
|
||||
curl -X PATCH http://coder-server:8080/api/v2/workspaceproxies/{workspaceproxy} \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Coder-Session-Token: API_KEY'
|
||||
```
|
||||
|
||||
`PATCH /workspaceproxies/{workspaceproxy}`
|
||||
|
||||
> Body parameter
|
||||
|
||||
```json
|
||||
{
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"regenerate_token": true
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | In | Type | Required | Description |
|
||||
| ---------------- | ---- | ---------------------------------------------------------------------- | -------- | ------------------------------ |
|
||||
| `workspaceproxy` | path | string(uuid) | true | Proxy ID or name |
|
||||
| `body` | body | [codersdk.PatchWorkspaceProxy](schemas.md#codersdkpatchworkspaceproxy) | true | Update workspace proxy request |
|
||||
|
||||
### Example responses
|
||||
|
||||
> 200 Response
|
||||
|
||||
```json
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"deleted": true,
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"status": {
|
||||
"checked_at": "2019-08-24T14:15:22Z",
|
||||
"report": {
|
||||
"errors": ["string"],
|
||||
"warnings": ["string"]
|
||||
},
|
||||
"status": "reachable"
|
||||
},
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"url": "string",
|
||||
"wildcard_hostname": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### Responses
|
||||
|
||||
| Status | Meaning | Description | Schema |
|
||||
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------------------ |
|
||||
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.WorkspaceProxy](schemas.md#codersdkworkspaceproxy) |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
|
|
@ -3212,6 +3212,28 @@ Parameter represents a set value for the scope.
|
|||
| ------ | ------ | -------- | ------------ | ----------- |
|
||||
| `name` | string | false | | |
|
||||
|
||||
## codersdk.PatchWorkspaceProxy
|
||||
|
||||
```json
|
||||
{
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"regenerate_token": true
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ------------------ | ------- | -------- | ------------ | ----------- |
|
||||
| `display_name` | string | true | | |
|
||||
| `icon` | string | true | | |
|
||||
| `id` | string | true | | |
|
||||
| `name` | string | true | | |
|
||||
| `regenerate_token` | boolean | false | | |
|
||||
|
||||
## codersdk.PprofConfig
|
||||
|
||||
```json
|
||||
|
@ -5329,6 +5351,7 @@ Parameter represents a set value for the scope.
|
|||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"deleted": true,
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
|
@ -5352,6 +5375,7 @@ Parameter represents a set value for the scope.
|
|||
| ------------------- | -------------------------------------------------------------- | -------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `created_at` | string | false | | |
|
||||
| `deleted` | boolean | false | | |
|
||||
| `display_name` | string | false | | |
|
||||
| `icon` | string | false | | |
|
||||
| `id` | string | false | | |
|
||||
| `name` | string | false | | |
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
|
@ -26,12 +27,158 @@ func (r *RootCmd) workspaceProxy() *clibase.Cmd {
|
|||
r.createProxy(),
|
||||
r.deleteProxy(),
|
||||
r.listProxies(),
|
||||
r.patchProxy(),
|
||||
r.regenerateProxyToken(),
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (r *RootCmd) regenerateProxyToken() *clibase.Cmd {
|
||||
formatter := newUpdateProxyResponseFormatter()
|
||||
client := new(codersdk.Client)
|
||||
cmd := &clibase.Cmd{
|
||||
Use: "regenerate-token <name|id>",
|
||||
Short: "Regenerate a workspace proxy authentication token. " +
|
||||
"This will invalidate the existing authentication token.",
|
||||
Middleware: clibase.Chain(
|
||||
clibase.RequireNArgs(1),
|
||||
r.InitClient(client),
|
||||
),
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
ctx := inv.Context()
|
||||
// This is cheeky, but you can also use a uuid string in
|
||||
// 'DeleteWorkspaceProxyByName' and it will work.
|
||||
proxy, err := client.WorkspaceProxyByName(ctx, inv.Args[0])
|
||||
if err != nil {
|
||||
return xerrors.Errorf("fetch workspace proxy %q: %w", inv.Args[0], err)
|
||||
}
|
||||
|
||||
// Only regenerate the token
|
||||
updated, err := client.PatchWorkspaceProxy(ctx, codersdk.PatchWorkspaceProxy{
|
||||
ID: proxy.ID,
|
||||
Name: proxy.Name,
|
||||
DisplayName: proxy.DisplayName,
|
||||
Icon: proxy.Icon,
|
||||
RegenerateToken: true,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("update workspace proxy %q: %w", inv.Args[0], err)
|
||||
}
|
||||
|
||||
output, err := formatter.Format(ctx, updated)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprintln(inv.Stdout, output)
|
||||
return err
|
||||
},
|
||||
}
|
||||
formatter.AttachOptions(&cmd.Options)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (r *RootCmd) patchProxy() *clibase.Cmd {
|
||||
var (
|
||||
proxyName string
|
||||
displayName string
|
||||
proxyIcon string
|
||||
formatter = cliui.NewOutputFormatter(
|
||||
// Text formatter should be human readable.
|
||||
cliui.ChangeFormatterData(cliui.TextFormat(), func(data any) (any, error) {
|
||||
response, ok := data.(codersdk.WorkspaceProxy)
|
||||
if !ok {
|
||||
return nil, xerrors.Errorf("unexpected type %T", data)
|
||||
}
|
||||
return fmt.Sprintf("Workspace Proxy %q updated successfully.", response.Name), nil
|
||||
}),
|
||||
cliui.JSONFormat(),
|
||||
// Table formatter expects a slice, make a slice of one.
|
||||
cliui.ChangeFormatterData(cliui.TableFormat([]codersdk.WorkspaceProxy{}, []string{"proxy name", "proxy url"}),
|
||||
func(data any) (any, error) {
|
||||
response, ok := data.(codersdk.WorkspaceProxy)
|
||||
if !ok {
|
||||
return nil, xerrors.Errorf("unexpected type %T", data)
|
||||
}
|
||||
return []codersdk.WorkspaceProxy{response}, nil
|
||||
}),
|
||||
)
|
||||
)
|
||||
client := new(codersdk.Client)
|
||||
cmd := &clibase.Cmd{
|
||||
Use: "edit <name|id>",
|
||||
Short: "Edit a workspace proxy",
|
||||
Middleware: clibase.Chain(
|
||||
clibase.RequireNArgs(1),
|
||||
r.InitClient(client),
|
||||
),
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
ctx := inv.Context()
|
||||
if proxyIcon == "" && displayName == "" && proxyName == "" {
|
||||
return xerrors.Errorf("specify at least one field to update")
|
||||
}
|
||||
|
||||
// This is cheeky, but you can also use a uuid string in
|
||||
// 'DeleteWorkspaceProxyByName' and it will work.
|
||||
proxy, err := client.WorkspaceProxyByName(ctx, inv.Args[0])
|
||||
if err != nil {
|
||||
return xerrors.Errorf("fetch workspace proxy %q: %w", inv.Args[0], err)
|
||||
}
|
||||
|
||||
// Use the existing values if the user didn't specify them.
|
||||
if proxyName == "" {
|
||||
proxyName = proxy.Name
|
||||
}
|
||||
if displayName == "" {
|
||||
displayName = proxy.DisplayName
|
||||
}
|
||||
if proxyIcon == "" {
|
||||
proxyIcon = proxy.Icon
|
||||
}
|
||||
|
||||
updated, err := client.PatchWorkspaceProxy(ctx, codersdk.PatchWorkspaceProxy{
|
||||
ID: proxy.ID,
|
||||
Name: proxyName,
|
||||
DisplayName: displayName,
|
||||
Icon: proxyIcon,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("update workspace proxy %q: %w", inv.Args[0], err)
|
||||
}
|
||||
|
||||
output, err := formatter.Format(ctx, updated.Proxy)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("format response: %w", err)
|
||||
}
|
||||
_, err = fmt.Fprintln(inv.Stdout, output)
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
formatter.AttachOptions(&cmd.Options)
|
||||
cmd.Options.Add(
|
||||
clibase.Option{
|
||||
Flag: "name",
|
||||
Description: "(Optional) Name of the proxy. This is used to identify the proxy.",
|
||||
Value: clibase.StringOf(&proxyName),
|
||||
},
|
||||
clibase.Option{
|
||||
Flag: "display-name",
|
||||
Description: "(Optional) Display of the proxy. A more human friendly name to be displayed.",
|
||||
Value: clibase.StringOf(&displayName),
|
||||
},
|
||||
clibase.Option{
|
||||
Flag: "icon",
|
||||
Description: "(Optional) Display icon of the proxy.",
|
||||
Value: clibase.StringOf(&proxyIcon),
|
||||
},
|
||||
)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (r *RootCmd) deleteProxy() *clibase.Cmd {
|
||||
client := new(codersdk.Client)
|
||||
cmd := &clibase.Cmd{
|
||||
|
@ -61,28 +208,7 @@ func (r *RootCmd) createProxy() *clibase.Cmd {
|
|||
proxyName string
|
||||
displayName string
|
||||
proxyIcon string
|
||||
onlyToken bool
|
||||
formatter = cliui.NewOutputFormatter(
|
||||
// Text formatter should be human readable.
|
||||
cliui.ChangeFormatterData(cliui.TextFormat(), func(data any) (any, error) {
|
||||
response, ok := data.(codersdk.CreateWorkspaceProxyResponse)
|
||||
if !ok {
|
||||
return nil, xerrors.Errorf("unexpected type %T", data)
|
||||
}
|
||||
return fmt.Sprintf("Workspace Proxy %q created successfully. Save this token, it will not be shown again."+
|
||||
"\nToken: %s", response.Proxy.Name, response.ProxyToken), nil
|
||||
}),
|
||||
cliui.JSONFormat(),
|
||||
// Table formatter expects a slice, make a slice of one.
|
||||
cliui.ChangeFormatterData(cliui.TableFormat([]codersdk.CreateWorkspaceProxyResponse{}, []string{"proxy name", "proxy url", "proxy token"}),
|
||||
func(data any) (any, error) {
|
||||
response, ok := data.(codersdk.CreateWorkspaceProxyResponse)
|
||||
if !ok {
|
||||
return nil, xerrors.Errorf("unexpected type %T", data)
|
||||
}
|
||||
return []codersdk.CreateWorkspaceProxyResponse{response}, nil
|
||||
}),
|
||||
)
|
||||
formatter = newUpdateProxyResponseFormatter()
|
||||
)
|
||||
|
||||
client := new(codersdk.Client)
|
||||
|
@ -108,16 +234,10 @@ func (r *RootCmd) createProxy() *clibase.Cmd {
|
|||
return xerrors.Errorf("create workspace proxy: %w", err)
|
||||
}
|
||||
|
||||
var output string
|
||||
if onlyToken {
|
||||
output = resp.ProxyToken
|
||||
} else {
|
||||
output, err = formatter.Format(ctx, resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
output, err := formatter.Format(ctx, resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintln(inv.Stdout, output)
|
||||
return err
|
||||
},
|
||||
|
@ -140,11 +260,6 @@ func (r *RootCmd) createProxy() *clibase.Cmd {
|
|||
Description: "Display icon of the proxy.",
|
||||
Value: clibase.StringOf(&proxyIcon),
|
||||
},
|
||||
clibase.Option{
|
||||
Flag: "only-token",
|
||||
Description: "Only print the token. This is useful for scripting.",
|
||||
Value: clibase.BoolOf(&onlyToken),
|
||||
},
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
@ -205,3 +320,56 @@ func (r *RootCmd) listProxies() *clibase.Cmd {
|
|||
formatter.AttachOptions(&cmd.Options)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// updateProxyResponseFormatter is used for both create and regenerate proxy commands.
|
||||
type updateProxyResponseFormatter struct {
|
||||
onlyToken bool
|
||||
formatter *cliui.OutputFormatter
|
||||
}
|
||||
|
||||
func (f *updateProxyResponseFormatter) Format(ctx context.Context, data codersdk.UpdateWorkspaceProxyResponse) (string, error) {
|
||||
if f.onlyToken {
|
||||
return data.ProxyToken, nil
|
||||
}
|
||||
return f.formatter.Format(ctx, data)
|
||||
}
|
||||
|
||||
func (f *updateProxyResponseFormatter) AttachOptions(opts *clibase.OptionSet) {
|
||||
opts.Add(
|
||||
clibase.Option{
|
||||
Flag: "only-token",
|
||||
Description: "Only print the token. This is useful for scripting.",
|
||||
Value: clibase.BoolOf(&f.onlyToken),
|
||||
},
|
||||
)
|
||||
f.formatter.AttachOptions(opts)
|
||||
}
|
||||
|
||||
func newUpdateProxyResponseFormatter() *updateProxyResponseFormatter {
|
||||
up := &updateProxyResponseFormatter{
|
||||
onlyToken: false,
|
||||
formatter: cliui.NewOutputFormatter(
|
||||
// Text formatter should be human readable.
|
||||
cliui.ChangeFormatterData(cliui.TextFormat(), func(data any) (any, error) {
|
||||
response, ok := data.(codersdk.UpdateWorkspaceProxyResponse)
|
||||
if !ok {
|
||||
return nil, xerrors.Errorf("unexpected type %T", data)
|
||||
}
|
||||
return fmt.Sprintf("Workspace Proxy %q created successfully. Save this token, it will not be shown again."+
|
||||
"\nToken: %s", response.Proxy.Name, response.ProxyToken), nil
|
||||
}),
|
||||
cliui.JSONFormat(),
|
||||
// Table formatter expects a slice, make a slice of one.
|
||||
cliui.ChangeFormatterData(cliui.TableFormat([]codersdk.UpdateWorkspaceProxyResponse{}, []string{"proxy name", "proxy url", "proxy token"}),
|
||||
func(data any) (any, error) {
|
||||
response, ok := data.(codersdk.UpdateWorkspaceProxyResponse)
|
||||
if !ok {
|
||||
return nil, xerrors.Errorf("unexpected type %T", data)
|
||||
}
|
||||
return []codersdk.UpdateWorkspaceProxyResponse{response}, nil
|
||||
}),
|
||||
),
|
||||
}
|
||||
|
||||
return up
|
||||
}
|
||||
|
|
|
@ -120,6 +120,8 @@ func New(ctx context.Context, options *Options) (*API, error) {
|
|||
httpmw.ExtractWorkspaceProxyParam(api.Database),
|
||||
)
|
||||
|
||||
r.Get("/", api.workspaceProxy)
|
||||
r.Patch("/", api.patchWorkspaceProxy)
|
||||
r.Delete("/", api.deleteWorkspaceProxy)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -90,6 +90,79 @@ func (api *API) regions(rw http.ResponseWriter, r *http.Request) {
|
|||
})
|
||||
}
|
||||
|
||||
// @Summary Update workspace proxy
|
||||
// @ID update-workspace-proxy
|
||||
// @Security CoderSessionToken
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Tags Enterprise
|
||||
// @Param workspaceproxy path string true "Proxy ID or name" format(uuid)
|
||||
// @Param request body codersdk.PatchWorkspaceProxy true "Update workspace proxy request"
|
||||
// @Success 200 {object} codersdk.WorkspaceProxy
|
||||
// @Router /workspaceproxies/{workspaceproxy} [patch]
|
||||
func (api *API) patchWorkspaceProxy(rw http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
proxy = httpmw.WorkspaceProxyParam(r)
|
||||
auditor = api.AGPL.Auditor.Load()
|
||||
aReq, commitAudit = audit.InitRequest[database.WorkspaceProxy](rw, &audit.RequestParams{
|
||||
Audit: *auditor,
|
||||
Log: api.Logger,
|
||||
Request: r,
|
||||
Action: database.AuditActionWrite,
|
||||
})
|
||||
)
|
||||
aReq.Old = proxy
|
||||
defer commitAudit()
|
||||
|
||||
var req codersdk.PatchWorkspaceProxy
|
||||
if !httpapi.Read(ctx, rw, r, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
var hashedSecret []byte
|
||||
var fullToken string
|
||||
if req.RegenerateToken {
|
||||
var err error
|
||||
fullToken, hashedSecret, err = generateWorkspaceProxyToken(proxy.ID)
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
updatedProxy, err := api.Database.UpdateWorkspaceProxy(ctx, database.UpdateWorkspaceProxyParams{
|
||||
Name: req.Name,
|
||||
DisplayName: req.DisplayName,
|
||||
Icon: req.Icon,
|
||||
ID: proxy.ID,
|
||||
// If hashedSecret is nil or empty, this will not update the secret.
|
||||
TokenHashedSecret: hashedSecret,
|
||||
})
|
||||
if httpapi.Is404Error(err) {
|
||||
httpapi.ResourceNotFound(rw)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
aReq.New = updatedProxy
|
||||
status, ok := api.ProxyHealth.HealthStatus()[updatedProxy.ID]
|
||||
if !ok {
|
||||
// The proxy should have some status, but just in case.
|
||||
status.Status = proxyhealth.Unknown
|
||||
}
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.UpdateWorkspaceProxyResponse{
|
||||
Proxy: convertProxy(updatedProxy, status),
|
||||
ProxyToken: fullToken,
|
||||
})
|
||||
|
||||
// Update the proxy cache.
|
||||
go api.forceWorkspaceProxyHealthUpdate(api.ctx)
|
||||
}
|
||||
|
||||
// @Summary Delete workspace proxy
|
||||
// @ID delete-workspace-proxy
|
||||
// @Security CoderSessionToken
|
||||
|
@ -107,7 +180,7 @@ func (api *API) deleteWorkspaceProxy(rw http.ResponseWriter, r *http.Request) {
|
|||
Audit: *auditor,
|
||||
Log: api.Logger,
|
||||
Request: r,
|
||||
Action: database.AuditActionCreate,
|
||||
Action: database.AuditActionDelete,
|
||||
})
|
||||
)
|
||||
aReq.Old = proxy
|
||||
|
@ -135,6 +208,23 @@ func (api *API) deleteWorkspaceProxy(rw http.ResponseWriter, r *http.Request) {
|
|||
go api.forceWorkspaceProxyHealthUpdate(api.ctx)
|
||||
}
|
||||
|
||||
// @Summary Get workspace proxy
|
||||
// @ID get-workspace-proxy
|
||||
// @Security CoderSessionToken
|
||||
// @Produce json
|
||||
// @Tags Enterprise
|
||||
// @Param workspaceproxy path string true "Proxy ID or name" format(uuid)
|
||||
// @Success 200 {object} codersdk.WorkspaceProxy
|
||||
// @Router /workspaceproxies/{workspaceproxy} [get]
|
||||
func (api *API) workspaceProxy(rw http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
proxy = httpmw.WorkspaceProxyParam(r)
|
||||
)
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusOK, convertProxy(proxy, api.ProxyHealth.HealthStatus()[proxy.ID]))
|
||||
}
|
||||
|
||||
// @Summary Create workspace proxy
|
||||
// @ID create-workspace-proxy
|
||||
// @Security CoderSessionToken
|
||||
|
@ -177,13 +267,11 @@ func (api *API) postWorkspaceProxy(rw http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
id := uuid.New()
|
||||
secret, err := cryptorand.HexString(64)
|
||||
fullToken, hashedSecret, err := generateWorkspaceProxyToken(id)
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
hashedSecret := sha256.Sum256([]byte(secret))
|
||||
fullToken := fmt.Sprintf("%s:%s", id, secret)
|
||||
|
||||
proxy, err := api.Database.InsertWorkspaceProxy(ctx, database.InsertWorkspaceProxyParams{
|
||||
ID: id,
|
||||
|
@ -206,7 +294,7 @@ func (api *API) postWorkspaceProxy(rw http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
aReq.New = proxy
|
||||
httpapi.Write(ctx, rw, http.StatusCreated, codersdk.CreateWorkspaceProxyResponse{
|
||||
httpapi.Write(ctx, rw, http.StatusCreated, codersdk.UpdateWorkspaceProxyResponse{
|
||||
Proxy: convertProxy(proxy, proxyhealth.ProxyStatus{
|
||||
Proxy: proxy,
|
||||
CheckedAt: time.Now(),
|
||||
|
@ -325,7 +413,20 @@ func (api *API) workspaceProxyRegister(rw http.ResponseWriter, r *http.Request)
|
|||
var (
|
||||
ctx = r.Context()
|
||||
proxy = httpmw.WorkspaceProxy(r)
|
||||
// TODO: This audit log does not work because it has no user id
|
||||
// associated with it. The audit log commitAudit() function ignores
|
||||
// the audit log if there is no user id. We should find a solution
|
||||
// to make sure this event is tracked.
|
||||
// auditor = api.AGPL.Auditor.Load()
|
||||
// aReq, commitAudit = audit.InitRequest[database.WorkspaceProxy](rw, &audit.RequestParams{
|
||||
// Audit: *auditor,
|
||||
// Log: api.Logger,
|
||||
// Request: r,
|
||||
// Action: database.AuditActionWrite,
|
||||
//})
|
||||
)
|
||||
// aReq.Old = proxy
|
||||
// defer commitAudit()
|
||||
|
||||
var req wsproxysdk.RegisterWorkspaceProxyRequest
|
||||
if !httpapi.Read(ctx, rw, r, &req) {
|
||||
|
@ -364,6 +465,7 @@ func (api *API) workspaceProxyRegister(rw http.ResponseWriter, r *http.Request)
|
|||
return
|
||||
}
|
||||
|
||||
// aReq.New = updatedProxy
|
||||
httpapi.Write(ctx, rw, http.StatusCreated, wsproxysdk.RegisterWorkspaceProxyResponse{
|
||||
AppSecurityKey: api.AppSecurityKey.String(),
|
||||
})
|
||||
|
@ -467,6 +569,16 @@ func (api *API) reconnectingPTYSignedToken(rw http.ResponseWriter, r *http.Reque
|
|||
})
|
||||
}
|
||||
|
||||
func generateWorkspaceProxyToken(id uuid.UUID) (token string, hashed []byte, err error) {
|
||||
secret, err := cryptorand.HexString(64)
|
||||
if err != nil {
|
||||
return "", nil, xerrors.Errorf("generate token: %w", err)
|
||||
}
|
||||
hashedSecret := sha256.Sum256([]byte(secret))
|
||||
fullToken := fmt.Sprintf("%s:%s", id, secret)
|
||||
return fullToken, hashedSecret[:], nil
|
||||
}
|
||||
|
||||
func convertProxies(p []database.WorkspaceProxy, statuses map[uuid.UUID]proxyhealth.ProxyStatus) []codersdk.WorkspaceProxy {
|
||||
resp := make([]codersdk.WorkspaceProxy, 0, len(p))
|
||||
for _, proxy := range p {
|
||||
|
@ -479,6 +591,7 @@ func convertProxy(p database.WorkspaceProxy, status proxyhealth.ProxyStatus) cod
|
|||
return codersdk.WorkspaceProxy{
|
||||
ID: p.ID,
|
||||
Name: p.Name,
|
||||
DisplayName: p.DisplayName,
|
||||
Icon: p.Icon,
|
||||
URL: p.Url,
|
||||
WildcardHostname: p.WildcardHostname,
|
||||
|
|
|
@ -177,7 +177,7 @@ func TestRegions(t *testing.T) {
|
|||
func TestWorkspaceProxyCRUD(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("create", func(t *testing.T) {
|
||||
t.Run("CreateAndUpdate", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
|
@ -203,14 +203,33 @@ func TestWorkspaceProxyCRUD(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
proxies, err := client.WorkspaceProxies(ctx)
|
||||
found, err := client.WorkspaceProxyByID(ctx, proxyRes.Proxy.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, proxies, 1)
|
||||
require.Equal(t, proxyRes.Proxy.ID, proxies[0].ID)
|
||||
// This will be different, so set it to the same
|
||||
found.Status = proxyRes.Proxy.Status
|
||||
require.Equal(t, proxyRes.Proxy, found, "expected proxy")
|
||||
require.NotEmpty(t, proxyRes.ProxyToken)
|
||||
|
||||
// Update the proxy
|
||||
expName := namesgenerator.GetRandomName(1)
|
||||
expDisplayName := namesgenerator.GetRandomName(1)
|
||||
expIcon := namesgenerator.GetRandomName(1)
|
||||
_, err = client.PatchWorkspaceProxy(ctx, codersdk.PatchWorkspaceProxy{
|
||||
ID: proxyRes.Proxy.ID,
|
||||
Name: expName,
|
||||
DisplayName: expDisplayName,
|
||||
Icon: expIcon,
|
||||
})
|
||||
require.NoError(t, err, "expected no error updating proxy")
|
||||
|
||||
found, err = client.WorkspaceProxyByID(ctx, proxyRes.Proxy.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expName, found.Name, "name")
|
||||
require.Equal(t, expDisplayName, found.DisplayName, "display name")
|
||||
require.Equal(t, expIcon, found.Icon, "icon")
|
||||
})
|
||||
|
||||
t.Run("delete", func(t *testing.T) {
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
|
|
|
@ -264,12 +264,6 @@ export interface CreateWorkspaceProxyRequest {
|
|||
readonly icon: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaceproxy.go
|
||||
export interface CreateWorkspaceProxyResponse {
|
||||
readonly proxy: WorkspaceProxy
|
||||
readonly proxy_token: string
|
||||
}
|
||||
|
||||
// From codersdk/organizations.go
|
||||
export interface CreateWorkspaceRequest {
|
||||
readonly template_id: string
|
||||
|
@ -632,6 +626,15 @@ export interface PatchTemplateVersionRequest {
|
|||
readonly name: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaceproxy.go
|
||||
export interface PatchWorkspaceProxy {
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly display_name: string
|
||||
readonly icon: string
|
||||
readonly regenerate_token: boolean
|
||||
}
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export interface PprofConfig {
|
||||
readonly enable: boolean
|
||||
|
@ -1034,6 +1037,12 @@ export interface UpdateWorkspaceAutostartRequest {
|
|||
readonly schedule?: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaceproxy.go
|
||||
export interface UpdateWorkspaceProxyResponse {
|
||||
readonly proxy: WorkspaceProxy
|
||||
readonly proxy_token: string
|
||||
}
|
||||
|
||||
// From codersdk/workspaces.go
|
||||
export interface UpdateWorkspaceRequest {
|
||||
readonly name?: string
|
||||
|
@ -1264,6 +1273,7 @@ export interface WorkspaceOptions {
|
|||
export interface WorkspaceProxy {
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly display_name: string
|
||||
readonly icon: string
|
||||
readonly url: string
|
||||
readonly wildcard_hostname: string
|
||||
|
|
Loading…
Reference in New Issue