coder/coderd/workspaceagentportshare.go

205 lines
5.7 KiB
Go

package coderd
import (
"database/sql"
"errors"
"net/http"
"slices"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/coderd/httpmw"
"github.com/coder/coder/v2/codersdk"
)
// @Summary Upsert workspace agent port share
// @ID upsert-workspace-agent-port-share
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags PortSharing
// @Param workspace path string true "Workspace ID" format(uuid)
// @Param request body codersdk.UpsertWorkspaceAgentPortShareRequest true "Upsert port sharing level request"
// @Success 200 {object} codersdk.WorkspaceAgentPortShare
// @Router /workspaces/{workspace}/port-share [post]
func (api *API) postWorkspaceAgentPortShare(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
workspace := httpmw.WorkspaceParam(r)
portSharer := *api.PortSharer.Load()
var req codersdk.UpsertWorkspaceAgentPortShareRequest
if !httpapi.Read(ctx, rw, r, &req) {
return
}
if !req.ShareLevel.ValidPortShareLevel() {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Port sharing level not allowed.",
Validations: []codersdk.ValidationError{
{
Field: "share_level",
Detail: "Port sharing level not allowed.",
},
},
})
return
}
if req.Port < 9 || req.Port > 65535 {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Port must be between 9 and 65535.",
Validations: []codersdk.ValidationError{
{
Field: "port",
Detail: "Port must be between 9 and 65535.",
},
},
})
return
}
if !req.Protocol.ValidPortProtocol() {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Port protocol not allowed.",
})
return
}
template, err := api.Database.GetTemplateByID(ctx, workspace.TemplateID)
if err != nil {
httpapi.InternalServerError(rw, err)
return
}
err = portSharer.AuthorizedPortSharingLevel(template, req.ShareLevel)
if err != nil {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: err.Error(),
})
return
}
agents, err := api.Database.GetWorkspaceAgentsInLatestBuildByWorkspaceID(ctx, workspace.ID)
if err != nil {
httpapi.InternalServerError(rw, err)
return
}
found := false
for _, agent := range agents {
if agent.Name == req.AgentName {
found = true
break
}
}
if !found {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Agent not found.",
})
return
}
psl, err := api.Database.UpsertWorkspaceAgentPortShare(ctx, database.UpsertWorkspaceAgentPortShareParams{
WorkspaceID: workspace.ID,
AgentName: req.AgentName,
Port: req.Port,
ShareLevel: database.AppSharingLevel(req.ShareLevel),
Protocol: database.PortShareProtocol(req.Protocol),
})
if err != nil {
httpapi.InternalServerError(rw, err)
return
}
httpapi.Write(ctx, rw, http.StatusOK, convertPortShare(psl))
}
// @Summary Get workspace agent port shares
// @ID get-workspace-agent-port-shares
// @Security CoderSessionToken
// @Produce json
// @Tags PortSharing
// @Param workspace path string true "Workspace ID" format(uuid)
// @Success 200 {object} codersdk.WorkspaceAgentPortShares
// @Router /workspaces/{workspace}/port-share [get]
func (api *API) workspaceAgentPortShares(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
workspace := httpmw.WorkspaceParam(r)
shares, err := api.Database.ListWorkspaceAgentPortShares(ctx, workspace.ID)
if err != nil {
httpapi.InternalServerError(rw, err)
return
}
httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspaceAgentPortShares{
Shares: convertPortShares(shares),
})
}
// @Summary Get workspace agent port shares
// @ID get-workspace-agent-port-shares
// @Security CoderSessionToken
// @Accept json
// @Tags PortSharing
// @Param workspace path string true "Workspace ID" format(uuid)
// @Param request body codersdk.DeleteWorkspaceAgentPortShareRequest true "Delete port sharing level request"
// @Success 200
// @Router /workspaces/{workspace}/port-share [delete]
func (api *API) deleteWorkspaceAgentPortShare(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
workspace := httpmw.WorkspaceParam(r)
var req codersdk.DeleteWorkspaceAgentPortShareRequest
if !httpapi.Read(ctx, rw, r, &req) {
return
}
_, err := api.Database.GetWorkspaceAgentPortShare(ctx, database.GetWorkspaceAgentPortShareParams{
WorkspaceID: workspace.ID,
AgentName: req.AgentName,
Port: req.Port,
})
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
Message: "Port share not found.",
})
return
}
httpapi.InternalServerError(rw, err)
return
}
err = api.Database.DeleteWorkspaceAgentPortShare(ctx, database.DeleteWorkspaceAgentPortShareParams{
WorkspaceID: workspace.ID,
AgentName: req.AgentName,
Port: req.Port,
})
if err != nil {
httpapi.InternalServerError(rw, err)
return
}
rw.WriteHeader(http.StatusOK)
}
func convertPortShares(shares []database.WorkspaceAgentPortShare) []codersdk.WorkspaceAgentPortShare {
converted := []codersdk.WorkspaceAgentPortShare{}
for _, share := range shares {
converted = append(converted, convertPortShare(share))
}
slices.SortFunc(converted, func(i, j codersdk.WorkspaceAgentPortShare) int {
return (int)(i.Port - j.Port)
})
return converted
}
func convertPortShare(share database.WorkspaceAgentPortShare) codersdk.WorkspaceAgentPortShare {
return codersdk.WorkspaceAgentPortShare{
WorkspaceID: share.WorkspaceID,
AgentName: share.AgentName,
Port: share.Port,
ShareLevel: codersdk.WorkspaceAgentPortShareLevel(share.ShareLevel),
Protocol: codersdk.WorkspaceAgentPortShareProtocol(share.Protocol),
}
}