Skip to content

chore: Allow editing proxy fields via api. #7435

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 16 commits into from
May 9, 2023
Merged
Next Next commit
chore: Add ability to update workspace proxy fields
  • Loading branch information
Emyrk committed May 4, 2023
commit 94e38e8e1824fbf6d0c937d4fd9f9cf3e3180395
7 changes: 7 additions & 0 deletions coderd/database/dbauthz/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
25 changes: 25 additions & 0 deletions coderd/database/dbfake/databasefake.go
Original file line number Diff line number Diff line change
Expand Up @@ -5231,6 +5231,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()
Expand Down
2 changes: 1 addition & 1 deletion coderd/database/models.go

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

4 changes: 3 additions & 1 deletion coderd/database/querier.go

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

58 changes: 56 additions & 2 deletions coderd/database/queries.sql.go

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

38 changes: 30 additions & 8 deletions coderd/database/queries/proxies.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand All @@ -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.
Expand Down Expand Up @@ -94,11 +124,3 @@ WHERE
)
LIMIT
1;

-- name: GetWorkspaceProxies :many
SELECT
*
FROM
workspace_proxies
WHERE
deleted = false;
53 changes: 53 additions & 0 deletions codersdk/workspaceproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,37 @@ 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"`
}

type PatchWorkspaceProxyResponse struct {
Proxy WorkspaceProxy `json:"proxy" table:"proxy,recursive"`
// ProxyToken is only returned if 'RegenerateToken' is set.
ProxyToken string `json:"proxy_token" table:"proxy token,default_sort"`
}

func (c *Client) PatchWorkspaceProxy(ctx context.Context, req PatchWorkspaceProxy) (WorkspaceProxy, error) {
res, err := c.Request(ctx, http.MethodPatch,
fmt.Sprintf("/api/v2/workspaceproxies/%s", req.ID.String()),
nil,
)
if err != nil {
return WorkspaceProxy{}, xerrors.Errorf("make request: %w", err)
}
defer res.Body.Close()

if res.StatusCode != http.StatusCreated {
return WorkspaceProxy{}, ReadBodyAsError(res)
}
var resp WorkspaceProxy
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),
Expand All @@ -131,6 +162,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"`
}
Expand Down
77 changes: 77 additions & 0 deletions enterprise/cli/workspaceproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,83 @@ func (r *RootCmd) workspaceProxy() *clibase.Cmd {
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()
// 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)
}

updated, err := client.PatchWorkspaceProxy(ctx, codersdk.PatchWorkspaceProxy{
ID: proxy.ID,
Name: proxyName,
DisplayName: displayName,
Icon: proxyIcon,
})

_, _ = formatter.Format(ctx, updated)
return nil
},
}

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. If omitted, the name is reused as the display name.",
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{
Expand Down
2 changes: 2 additions & 0 deletions enterprise/coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ func New(ctx context.Context, options *Options) (*API, error) {
httpmw.ExtractWorkspaceProxyParam(api.Database),
)

r.Get("/", api.getWorkspaceProxy)
r.Patch("/", api.patchWorkspaceProxy)
r.Delete("/", api.deleteWorkspaceProxy)
})
})
Expand Down
Loading