mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: allow setting port share protocol (#12383)
Co-authored-by: Garrett Delfosse <garrett@coder.com>
This commit is contained in:
co-authored by
Garrett Delfosse
parent
23ff807a27
commit
46a2ff1061
Generated
+53
-2
@@ -12619,8 +12619,28 @@ const docTemplate = `{
|
||||
"port": {
|
||||
"type": "integer"
|
||||
},
|
||||
"protocol": {
|
||||
"enum": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.WorkspaceAgentPortShareProtocol"
|
||||
}
|
||||
]
|
||||
},
|
||||
"share_level": {
|
||||
"$ref": "#/definitions/codersdk.WorkspaceAgentPortShareLevel"
|
||||
"enum": [
|
||||
"owner",
|
||||
"authenticated",
|
||||
"public"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.WorkspaceAgentPortShareLevel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -13366,8 +13386,28 @@ const docTemplate = `{
|
||||
"port": {
|
||||
"type": "integer"
|
||||
},
|
||||
"protocol": {
|
||||
"enum": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.WorkspaceAgentPortShareProtocol"
|
||||
}
|
||||
]
|
||||
},
|
||||
"share_level": {
|
||||
"$ref": "#/definitions/codersdk.WorkspaceAgentPortShareLevel"
|
||||
"enum": [
|
||||
"owner",
|
||||
"authenticated",
|
||||
"public"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.WorkspaceAgentPortShareLevel"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspace_id": {
|
||||
"type": "string",
|
||||
@@ -13388,6 +13428,17 @@ const docTemplate = `{
|
||||
"WorkspaceAgentPortShareLevelPublic"
|
||||
]
|
||||
},
|
||||
"codersdk.WorkspaceAgentPortShareProtocol": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"WorkspaceAgentPortShareProtocolHTTP",
|
||||
"WorkspaceAgentPortShareProtocolHTTPS"
|
||||
]
|
||||
},
|
||||
"codersdk.WorkspaceAgentPortShares": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
Generated
+36
-2
@@ -11442,8 +11442,21 @@
|
||||
"port": {
|
||||
"type": "integer"
|
||||
},
|
||||
"protocol": {
|
||||
"enum": ["http", "https"],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.WorkspaceAgentPortShareProtocol"
|
||||
}
|
||||
]
|
||||
},
|
||||
"share_level": {
|
||||
"$ref": "#/definitions/codersdk.WorkspaceAgentPortShareLevel"
|
||||
"enum": ["owner", "authenticated", "public"],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.WorkspaceAgentPortShareLevel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -12164,8 +12177,21 @@
|
||||
"port": {
|
||||
"type": "integer"
|
||||
},
|
||||
"protocol": {
|
||||
"enum": ["http", "https"],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.WorkspaceAgentPortShareProtocol"
|
||||
}
|
||||
]
|
||||
},
|
||||
"share_level": {
|
||||
"$ref": "#/definitions/codersdk.WorkspaceAgentPortShareLevel"
|
||||
"enum": ["owner", "authenticated", "public"],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.WorkspaceAgentPortShareLevel"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspace_id": {
|
||||
"type": "string",
|
||||
@@ -12182,6 +12208,14 @@
|
||||
"WorkspaceAgentPortShareLevelPublic"
|
||||
]
|
||||
},
|
||||
"codersdk.WorkspaceAgentPortShareProtocol": {
|
||||
"type": "string",
|
||||
"enum": ["http", "https"],
|
||||
"x-enum-varnames": [
|
||||
"WorkspaceAgentPortShareProtocolHTTP",
|
||||
"WorkspaceAgentPortShareProtocolHTTPS"
|
||||
]
|
||||
},
|
||||
"codersdk.WorkspaceAgentPortShares": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -1608,6 +1608,7 @@ func (s *MethodTestSuite) TestWorkspacePortSharing() {
|
||||
AgentName: ps.AgentName,
|
||||
Port: ps.Port,
|
||||
ShareLevel: ps.ShareLevel,
|
||||
Protocol: ps.Protocol,
|
||||
}).Asserts(ws, rbac.ActionUpdate).Returns(ps)
|
||||
}))
|
||||
s.Run("GetWorkspaceAgentPortShare", s.Subtest(func(db database.Store, check *expects) {
|
||||
|
||||
@@ -140,6 +140,7 @@ func WorkspaceAgentPortShare(t testing.TB, db database.Store, orig database.Work
|
||||
AgentName: takeFirst(orig.AgentName, namesgenerator.GetRandomName(1)),
|
||||
Port: takeFirst(orig.Port, 8080),
|
||||
ShareLevel: takeFirst(orig.ShareLevel, database.AppSharingLevelPublic),
|
||||
Protocol: takeFirst(orig.Protocol, database.PortShareProtocolHttp),
|
||||
})
|
||||
require.NoError(t, err, "insert workspace agent")
|
||||
return ps
|
||||
|
||||
@@ -86,7 +86,7 @@ func New() database.Store {
|
||||
UpdatedAt: dbtime.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to create default organization: %w", err))
|
||||
panic(xerrors.Errorf("failed to create default organization: %w", err))
|
||||
}
|
||||
q.defaultProxyDisplayName = "Default"
|
||||
q.defaultProxyIconURL = "/emojis/1f3e1.png"
|
||||
@@ -7933,6 +7933,7 @@ func (q *FakeQuerier) UpsertWorkspaceAgentPortShare(_ context.Context, arg datab
|
||||
for i, share := range q.workspaceAgentPortShares {
|
||||
if share.WorkspaceID == arg.WorkspaceID && share.Port == arg.Port && share.AgentName == arg.AgentName {
|
||||
share.ShareLevel = arg.ShareLevel
|
||||
share.Protocol = arg.Protocol
|
||||
q.workspaceAgentPortShares[i] = share
|
||||
return share, nil
|
||||
}
|
||||
@@ -7944,6 +7945,7 @@ func (q *FakeQuerier) UpsertWorkspaceAgentPortShare(_ context.Context, arg datab
|
||||
AgentName: arg.AgentName,
|
||||
Port: arg.Port,
|
||||
ShareLevel: arg.ShareLevel,
|
||||
Protocol: arg.Protocol,
|
||||
}
|
||||
q.workspaceAgentPortShares = append(q.workspaceAgentPortShares, psl)
|
||||
|
||||
|
||||
Generated
+7
-1
@@ -95,6 +95,11 @@ CREATE TYPE parameter_type_system AS ENUM (
|
||||
'hcl'
|
||||
);
|
||||
|
||||
CREATE TYPE port_share_protocol AS ENUM (
|
||||
'http',
|
||||
'https'
|
||||
);
|
||||
|
||||
CREATE TYPE provisioner_job_status AS ENUM (
|
||||
'pending',
|
||||
'running',
|
||||
@@ -1027,7 +1032,8 @@ CREATE TABLE workspace_agent_port_share (
|
||||
workspace_id uuid NOT NULL,
|
||||
agent_name text NOT NULL,
|
||||
port integer NOT NULL,
|
||||
share_level app_sharing_level NOT NULL
|
||||
share_level app_sharing_level NOT NULL,
|
||||
protocol port_share_protocol DEFAULT 'http'::port_share_protocol NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE workspace_agent_scripts (
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE workspace_agent_port_share DROP COLUMN protocol;
|
||||
|
||||
DROP TYPE port_share_protocol;
|
||||
@@ -0,0 +1,4 @@
|
||||
CREATE TYPE port_share_protocol AS ENUM ('http', 'https');
|
||||
|
||||
ALTER TABLE workspace_agent_port_share
|
||||
ADD COLUMN protocol port_share_protocol NOT NULL DEFAULT 'http'::port_share_protocol;
|
||||
@@ -898,6 +898,64 @@ func AllParameterTypeSystemValues() []ParameterTypeSystem {
|
||||
}
|
||||
}
|
||||
|
||||
type PortShareProtocol string
|
||||
|
||||
const (
|
||||
PortShareProtocolHttp PortShareProtocol = "http"
|
||||
PortShareProtocolHttps PortShareProtocol = "https"
|
||||
)
|
||||
|
||||
func (e *PortShareProtocol) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = PortShareProtocol(s)
|
||||
case string:
|
||||
*e = PortShareProtocol(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for PortShareProtocol: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullPortShareProtocol struct {
|
||||
PortShareProtocol PortShareProtocol `json:"port_share_protocol"`
|
||||
Valid bool `json:"valid"` // Valid is true if PortShareProtocol is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullPortShareProtocol) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.PortShareProtocol, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.PortShareProtocol.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullPortShareProtocol) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.PortShareProtocol), nil
|
||||
}
|
||||
|
||||
func (e PortShareProtocol) Valid() bool {
|
||||
switch e {
|
||||
case PortShareProtocolHttp,
|
||||
PortShareProtocolHttps:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func AllPortShareProtocolValues() []PortShareProtocol {
|
||||
return []PortShareProtocol{
|
||||
PortShareProtocolHttp,
|
||||
PortShareProtocolHttps,
|
||||
}
|
||||
}
|
||||
|
||||
// Computed status of a provisioner job. Jobs could be stuck in a hung state, these states do not guarantee any transition to another state.
|
||||
type ProvisionerJobStatus string
|
||||
|
||||
@@ -2312,10 +2370,11 @@ type WorkspaceAgentMetadatum struct {
|
||||
}
|
||||
|
||||
type WorkspaceAgentPortShare struct {
|
||||
WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"`
|
||||
AgentName string `db:"agent_name" json:"agent_name"`
|
||||
Port int32 `db:"port" json:"port"`
|
||||
ShareLevel AppSharingLevel `db:"share_level" json:"share_level"`
|
||||
WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"`
|
||||
AgentName string `db:"agent_name" json:"agent_name"`
|
||||
Port int32 `db:"port" json:"port"`
|
||||
ShareLevel AppSharingLevel `db:"share_level" json:"share_level"`
|
||||
Protocol PortShareProtocol `db:"protocol" json:"protocol"`
|
||||
}
|
||||
|
||||
type WorkspaceAgentScript struct {
|
||||
|
||||
@@ -8469,7 +8469,12 @@ func (q *sqlQuerier) UpdateUserStatus(ctx context.Context, arg UpdateUserStatusP
|
||||
}
|
||||
|
||||
const deleteWorkspaceAgentPortShare = `-- name: DeleteWorkspaceAgentPortShare :exec
|
||||
DELETE FROM workspace_agent_port_share WHERE workspace_id = $1 AND agent_name = $2 AND port = $3
|
||||
DELETE FROM
|
||||
workspace_agent_port_share
|
||||
WHERE
|
||||
workspace_id = $1
|
||||
AND agent_name = $2
|
||||
AND port = $3
|
||||
`
|
||||
|
||||
type DeleteWorkspaceAgentPortShareParams struct {
|
||||
@@ -8484,7 +8489,17 @@ func (q *sqlQuerier) DeleteWorkspaceAgentPortShare(ctx context.Context, arg Dele
|
||||
}
|
||||
|
||||
const deleteWorkspaceAgentPortSharesByTemplate = `-- name: DeleteWorkspaceAgentPortSharesByTemplate :exec
|
||||
DELETE FROM workspace_agent_port_share WHERE workspace_id IN (SELECT id FROM workspaces WHERE template_id = $1)
|
||||
DELETE FROM
|
||||
workspace_agent_port_share
|
||||
WHERE
|
||||
workspace_id IN (
|
||||
SELECT
|
||||
id
|
||||
FROM
|
||||
workspaces
|
||||
WHERE
|
||||
template_id = $1
|
||||
)
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) DeleteWorkspaceAgentPortSharesByTemplate(ctx context.Context, templateID uuid.UUID) error {
|
||||
@@ -8493,7 +8508,14 @@ func (q *sqlQuerier) DeleteWorkspaceAgentPortSharesByTemplate(ctx context.Contex
|
||||
}
|
||||
|
||||
const getWorkspaceAgentPortShare = `-- name: GetWorkspaceAgentPortShare :one
|
||||
SELECT workspace_id, agent_name, port, share_level FROM workspace_agent_port_share WHERE workspace_id = $1 AND agent_name = $2 AND port = $3
|
||||
SELECT
|
||||
workspace_id, agent_name, port, share_level, protocol
|
||||
FROM
|
||||
workspace_agent_port_share
|
||||
WHERE
|
||||
workspace_id = $1
|
||||
AND agent_name = $2
|
||||
AND port = $3
|
||||
`
|
||||
|
||||
type GetWorkspaceAgentPortShareParams struct {
|
||||
@@ -8510,12 +8532,18 @@ func (q *sqlQuerier) GetWorkspaceAgentPortShare(ctx context.Context, arg GetWork
|
||||
&i.AgentName,
|
||||
&i.Port,
|
||||
&i.ShareLevel,
|
||||
&i.Protocol,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const listWorkspaceAgentPortShares = `-- name: ListWorkspaceAgentPortShares :many
|
||||
SELECT workspace_id, agent_name, port, share_level FROM workspace_agent_port_share WHERE workspace_id = $1
|
||||
SELECT
|
||||
workspace_id, agent_name, port, share_level, protocol
|
||||
FROM
|
||||
workspace_agent_port_share
|
||||
WHERE
|
||||
workspace_id = $1
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) ListWorkspaceAgentPortShares(ctx context.Context, workspaceID uuid.UUID) ([]WorkspaceAgentPortShare, error) {
|
||||
@@ -8532,6 +8560,7 @@ func (q *sqlQuerier) ListWorkspaceAgentPortShares(ctx context.Context, workspace
|
||||
&i.AgentName,
|
||||
&i.Port,
|
||||
&i.ShareLevel,
|
||||
&i.Protocol,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -8547,7 +8576,20 @@ func (q *sqlQuerier) ListWorkspaceAgentPortShares(ctx context.Context, workspace
|
||||
}
|
||||
|
||||
const reduceWorkspaceAgentShareLevelToAuthenticatedByTemplate = `-- name: ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate :exec
|
||||
UPDATE workspace_agent_port_share SET share_level = 'authenticated' WHERE share_level = 'public' AND workspace_id IN (SELECT id FROM workspaces WHERE template_id = $1)
|
||||
UPDATE
|
||||
workspace_agent_port_share
|
||||
SET
|
||||
share_level = 'authenticated'
|
||||
WHERE
|
||||
share_level = 'public'
|
||||
AND workspace_id IN (
|
||||
SELECT
|
||||
id
|
||||
FROM
|
||||
workspaces
|
||||
WHERE
|
||||
template_id = $1
|
||||
)
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx context.Context, templateID uuid.UUID) error {
|
||||
@@ -8556,16 +8598,38 @@ func (q *sqlQuerier) ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate(ctx
|
||||
}
|
||||
|
||||
const upsertWorkspaceAgentPortShare = `-- name: UpsertWorkspaceAgentPortShare :one
|
||||
INSERT INTO workspace_agent_port_share (workspace_id, agent_name, port, share_level)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (workspace_id, agent_name, port) DO UPDATE SET share_level = $4 RETURNING workspace_id, agent_name, port, share_level
|
||||
INSERT INTO
|
||||
workspace_agent_port_share (
|
||||
workspace_id,
|
||||
agent_name,
|
||||
port,
|
||||
share_level,
|
||||
protocol
|
||||
)
|
||||
VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5
|
||||
)
|
||||
ON CONFLICT (
|
||||
workspace_id,
|
||||
agent_name,
|
||||
port
|
||||
)
|
||||
DO UPDATE SET
|
||||
share_level = $4,
|
||||
protocol = $5
|
||||
RETURNING workspace_id, agent_name, port, share_level, protocol
|
||||
`
|
||||
|
||||
type UpsertWorkspaceAgentPortShareParams struct {
|
||||
WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"`
|
||||
AgentName string `db:"agent_name" json:"agent_name"`
|
||||
Port int32 `db:"port" json:"port"`
|
||||
ShareLevel AppSharingLevel `db:"share_level" json:"share_level"`
|
||||
WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"`
|
||||
AgentName string `db:"agent_name" json:"agent_name"`
|
||||
Port int32 `db:"port" json:"port"`
|
||||
ShareLevel AppSharingLevel `db:"share_level" json:"share_level"`
|
||||
Protocol PortShareProtocol `db:"protocol" json:"protocol"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpsertWorkspaceAgentPortShare(ctx context.Context, arg UpsertWorkspaceAgentPortShareParams) (WorkspaceAgentPortShare, error) {
|
||||
@@ -8574,6 +8638,7 @@ func (q *sqlQuerier) UpsertWorkspaceAgentPortShare(ctx context.Context, arg Upse
|
||||
arg.AgentName,
|
||||
arg.Port,
|
||||
arg.ShareLevel,
|
||||
arg.Protocol,
|
||||
)
|
||||
var i WorkspaceAgentPortShare
|
||||
err := row.Scan(
|
||||
@@ -8581,6 +8646,7 @@ func (q *sqlQuerier) UpsertWorkspaceAgentPortShare(ctx context.Context, arg Upse
|
||||
&i.AgentName,
|
||||
&i.Port,
|
||||
&i.ShareLevel,
|
||||
&i.Protocol,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@@ -1,19 +1,80 @@
|
||||
-- name: GetWorkspaceAgentPortShare :one
|
||||
SELECT * FROM workspace_agent_port_share WHERE workspace_id = $1 AND agent_name = $2 AND port = $3;
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
workspace_agent_port_share
|
||||
WHERE
|
||||
workspace_id = $1
|
||||
AND agent_name = $2
|
||||
AND port = $3;
|
||||
|
||||
-- name: ListWorkspaceAgentPortShares :many
|
||||
SELECT * FROM workspace_agent_port_share WHERE workspace_id = $1;
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
workspace_agent_port_share
|
||||
WHERE
|
||||
workspace_id = $1;
|
||||
|
||||
-- name: DeleteWorkspaceAgentPortShare :exec
|
||||
DELETE FROM workspace_agent_port_share WHERE workspace_id = $1 AND agent_name = $2 AND port = $3;
|
||||
DELETE FROM
|
||||
workspace_agent_port_share
|
||||
WHERE
|
||||
workspace_id = $1
|
||||
AND agent_name = $2
|
||||
AND port = $3;
|
||||
|
||||
-- name: UpsertWorkspaceAgentPortShare :one
|
||||
INSERT INTO workspace_agent_port_share (workspace_id, agent_name, port, share_level)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (workspace_id, agent_name, port) DO UPDATE SET share_level = $4 RETURNING *;
|
||||
INSERT INTO
|
||||
workspace_agent_port_share (
|
||||
workspace_id,
|
||||
agent_name,
|
||||
port,
|
||||
share_level,
|
||||
protocol
|
||||
)
|
||||
VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5
|
||||
)
|
||||
ON CONFLICT (
|
||||
workspace_id,
|
||||
agent_name,
|
||||
port
|
||||
)
|
||||
DO UPDATE SET
|
||||
share_level = $4,
|
||||
protocol = $5
|
||||
RETURNING *;
|
||||
|
||||
-- name: ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate :exec
|
||||
UPDATE workspace_agent_port_share SET share_level = 'authenticated' WHERE share_level = 'public' AND workspace_id IN (SELECT id FROM workspaces WHERE template_id = $1);
|
||||
UPDATE
|
||||
workspace_agent_port_share
|
||||
SET
|
||||
share_level = 'authenticated'
|
||||
WHERE
|
||||
share_level = 'public'
|
||||
AND workspace_id IN (
|
||||
SELECT
|
||||
id
|
||||
FROM
|
||||
workspaces
|
||||
WHERE
|
||||
template_id = $1
|
||||
);
|
||||
|
||||
-- name: DeleteWorkspaceAgentPortSharesByTemplate :exec
|
||||
DELETE FROM workspace_agent_port_share WHERE workspace_id IN (SELECT id FROM workspaces WHERE template_id = $1);
|
||||
DELETE FROM
|
||||
workspace_agent_port_share
|
||||
WHERE
|
||||
workspace_id IN (
|
||||
SELECT
|
||||
id
|
||||
FROM
|
||||
workspaces
|
||||
WHERE
|
||||
template_id = $1
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"net/http"
|
||||
"slices"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/httpapi"
|
||||
@@ -55,6 +56,12 @@ func (api *API) postWorkspaceAgentPortShare(rw http.ResponseWriter, r *http.Requ
|
||||
})
|
||||
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 {
|
||||
@@ -95,6 +102,7 @@ func (api *API) postWorkspaceAgentPortShare(rw http.ResponseWriter, r *http.Requ
|
||||
AgentName: req.AgentName,
|
||||
Port: req.Port,
|
||||
ShareLevel: database.AppSharingLevel(req.ShareLevel),
|
||||
Protocol: database.PortShareProtocol(req.Protocol),
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
@@ -179,6 +187,9 @@ func convertPortShares(shares []database.WorkspaceAgentPortShare) []codersdk.Wor
|
||||
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
|
||||
}
|
||||
|
||||
@@ -188,5 +199,6 @@ func convertPortShare(share database.WorkspaceAgentPortShare) codersdk.Workspace
|
||||
AgentName: share.AgentName,
|
||||
Port: share.Port,
|
||||
ShareLevel: codersdk.WorkspaceAgentPortShareLevel(share.ShareLevel),
|
||||
Protocol: codersdk.WorkspaceAgentPortShareProtocol(share.Protocol),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ func TestPostWorkspaceAgentPortShare(t *testing.T) {
|
||||
AgentName: agents[0].Name,
|
||||
Port: 8080,
|
||||
ShareLevel: codersdk.WorkspaceAgentPortShareLevel("owner"),
|
||||
Protocol: codersdk.WorkspaceAgentPortShareProtocolHTTP,
|
||||
})
|
||||
require.Error(t, err)
|
||||
|
||||
@@ -51,6 +52,16 @@ func TestPostWorkspaceAgentPortShare(t *testing.T) {
|
||||
AgentName: agents[0].Name,
|
||||
Port: 8080,
|
||||
ShareLevel: codersdk.WorkspaceAgentPortShareLevel("invalid"),
|
||||
Protocol: codersdk.WorkspaceAgentPortShareProtocolHTTP,
|
||||
})
|
||||
require.Error(t, err)
|
||||
|
||||
// invalid protocol should fail
|
||||
_, err = client.UpsertWorkspaceAgentPortShare(ctx, r.Workspace.ID, codersdk.UpsertWorkspaceAgentPortShareRequest{
|
||||
AgentName: agents[0].Name,
|
||||
Port: 8080,
|
||||
ShareLevel: codersdk.WorkspaceAgentPortShareLevelPublic,
|
||||
Protocol: codersdk.WorkspaceAgentPortShareProtocol("invalid"),
|
||||
})
|
||||
require.Error(t, err)
|
||||
|
||||
@@ -59,6 +70,7 @@ func TestPostWorkspaceAgentPortShare(t *testing.T) {
|
||||
AgentName: agents[0].Name,
|
||||
Port: 0,
|
||||
ShareLevel: codersdk.WorkspaceAgentPortShareLevelPublic,
|
||||
Protocol: codersdk.WorkspaceAgentPortShareProtocolHTTP,
|
||||
})
|
||||
require.Error(t, err)
|
||||
_, err = client.UpsertWorkspaceAgentPortShare(ctx, r.Workspace.ID, codersdk.UpsertWorkspaceAgentPortShareRequest{
|
||||
@@ -73,18 +85,54 @@ func TestPostWorkspaceAgentPortShare(t *testing.T) {
|
||||
AgentName: agents[0].Name,
|
||||
Port: 8080,
|
||||
ShareLevel: codersdk.WorkspaceAgentPortShareLevelPublic,
|
||||
Protocol: codersdk.WorkspaceAgentPortShareProtocolHTTPS,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, codersdk.WorkspaceAgentPortShareLevelPublic, ps.ShareLevel)
|
||||
require.EqualValues(t, codersdk.WorkspaceAgentPortShareProtocolHTTPS, ps.Protocol)
|
||||
|
||||
// update share level
|
||||
// list
|
||||
list, err := client.GetWorkspaceAgentPortShares(ctx, r.Workspace.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list.Shares, 1)
|
||||
require.EqualValues(t, agents[0].Name, list.Shares[0].AgentName)
|
||||
require.EqualValues(t, 8080, list.Shares[0].Port)
|
||||
require.EqualValues(t, codersdk.WorkspaceAgentPortShareLevelPublic, list.Shares[0].ShareLevel)
|
||||
require.EqualValues(t, codersdk.WorkspaceAgentPortShareProtocolHTTPS, list.Shares[0].Protocol)
|
||||
|
||||
// update share level and protocol
|
||||
ps, err = client.UpsertWorkspaceAgentPortShare(ctx, r.Workspace.ID, codersdk.UpsertWorkspaceAgentPortShareRequest{
|
||||
AgentName: agents[0].Name,
|
||||
Port: 8080,
|
||||
ShareLevel: codersdk.WorkspaceAgentPortShareLevelAuthenticated,
|
||||
Protocol: codersdk.WorkspaceAgentPortShareProtocolHTTP,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, codersdk.WorkspaceAgentPortShareLevelAuthenticated, ps.ShareLevel)
|
||||
require.EqualValues(t, codersdk.WorkspaceAgentPortShareProtocolHTTP, ps.Protocol)
|
||||
|
||||
// list
|
||||
list, err = client.GetWorkspaceAgentPortShares(ctx, r.Workspace.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list.Shares, 1)
|
||||
require.EqualValues(t, agents[0].Name, list.Shares[0].AgentName)
|
||||
require.EqualValues(t, 8080, list.Shares[0].Port)
|
||||
require.EqualValues(t, codersdk.WorkspaceAgentPortShareLevelAuthenticated, list.Shares[0].ShareLevel)
|
||||
require.EqualValues(t, codersdk.WorkspaceAgentPortShareProtocolHTTP, list.Shares[0].Protocol)
|
||||
|
||||
// list 2 ordered by port
|
||||
ps, err = client.UpsertWorkspaceAgentPortShare(ctx, r.Workspace.ID, codersdk.UpsertWorkspaceAgentPortShareRequest{
|
||||
AgentName: agents[0].Name,
|
||||
Port: 8081,
|
||||
ShareLevel: codersdk.WorkspaceAgentPortShareLevelPublic,
|
||||
Protocol: codersdk.WorkspaceAgentPortShareProtocolHTTPS,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
list, err = client.GetWorkspaceAgentPortShares(ctx, r.Workspace.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list.Shares, 2)
|
||||
require.EqualValues(t, 8080, list.Shares[0].Port)
|
||||
require.EqualValues(t, 8081, list.Shares[1].Port)
|
||||
}
|
||||
|
||||
func TestGetWorkspaceAgentPortShares(t *testing.T) {
|
||||
@@ -115,6 +163,7 @@ func TestGetWorkspaceAgentPortShares(t *testing.T) {
|
||||
AgentName: agents[0].Name,
|
||||
Port: 8080,
|
||||
ShareLevel: codersdk.WorkspaceAgentPortShareLevelPublic,
|
||||
Protocol: codersdk.WorkspaceAgentPortShareProtocolHTTP,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -155,6 +204,7 @@ func TestDeleteWorkspaceAgentPortShare(t *testing.T) {
|
||||
AgentName: agents[0].Name,
|
||||
Port: 8080,
|
||||
ShareLevel: codersdk.WorkspaceAgentPortShareLevelPublic,
|
||||
Protocol: codersdk.WorkspaceAgentPortShareProtocolHTTP,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, codersdk.WorkspaceAgentPortShareLevelPublic, ps.ShareLevel)
|
||||
|
||||
@@ -909,79 +909,6 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("PortSharingNoShare", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
userClient, _ := coderdtest.CreateAnotherUser(t, appDetails.SDKClient, appDetails.FirstUser.OrganizationID, rbac.RoleMember())
|
||||
userAppClient := appDetails.AppClient(t)
|
||||
userAppClient.SetSessionToken(userClient.SessionToken())
|
||||
|
||||
resp, err := requestWithRetries(ctx, t, userAppClient, http.MethodGet, appDetails.SubdomainAppURL(appDetails.Apps.Port).String(), nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
require.Equal(t, http.StatusNotFound, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("PortSharingAuthenticatedOK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
// we are shadowing the parent since we are changing the state
|
||||
appDetails := setupProxyTest(t, nil)
|
||||
|
||||
port, err := strconv.ParseInt(appDetails.Apps.Port.AppSlugOrPort, 10, 32)
|
||||
require.NoError(t, err)
|
||||
// set the port we have to be shared with authenticated users
|
||||
_, err = appDetails.SDKClient.UpsertWorkspaceAgentPortShare(ctx, appDetails.Workspace.ID, codersdk.UpsertWorkspaceAgentPortShareRequest{
|
||||
AgentName: proxyTestAgentName,
|
||||
Port: int32(port),
|
||||
ShareLevel: codersdk.WorkspaceAgentPortShareLevelAuthenticated,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
userClient, _ := coderdtest.CreateAnotherUser(t, appDetails.SDKClient, appDetails.FirstUser.OrganizationID, rbac.RoleMember())
|
||||
userAppClient := appDetails.AppClient(t)
|
||||
userAppClient.SetSessionToken(userClient.SessionToken())
|
||||
|
||||
resp, err := requestWithRetries(ctx, t, userAppClient, http.MethodGet, appDetails.SubdomainAppURL(appDetails.Apps.Port).String(), nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("PortSharingPublicOK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
// we are shadowing the parent since we are changing the state
|
||||
appDetails := setupProxyTest(t, nil)
|
||||
|
||||
port, err := strconv.ParseInt(appDetails.Apps.Port.AppSlugOrPort, 10, 32)
|
||||
require.NoError(t, err)
|
||||
// set the port we have to be shared with public
|
||||
_, err = appDetails.SDKClient.UpsertWorkspaceAgentPortShare(ctx, appDetails.Workspace.ID, codersdk.UpsertWorkspaceAgentPortShareRequest{
|
||||
AgentName: proxyTestAgentName,
|
||||
Port: int32(port),
|
||||
ShareLevel: codersdk.WorkspaceAgentPortShareLevelPublic,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
publicAppClient := appDetails.AppClient(t)
|
||||
publicAppClient.SetSessionToken("")
|
||||
|
||||
resp, err := requestWithRetries(ctx, t, publicAppClient, http.MethodGet, appDetails.SubdomainAppURL(appDetails.Apps.Port).String(), nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("ProxyError", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -1119,6 +1046,108 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("PortSharing", func(t *testing.T) {
|
||||
t.Run("NoShare", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
appDetails := setupProxyTest(t, nil)
|
||||
userClient, _ := coderdtest.CreateAnotherUser(t, appDetails.SDKClient, appDetails.FirstUser.OrganizationID, rbac.RoleMember())
|
||||
userAppClient := appDetails.AppClient(t)
|
||||
userAppClient.SetSessionToken(userClient.SessionToken())
|
||||
|
||||
resp, err := requestWithRetries(ctx, t, userAppClient, http.MethodGet, appDetails.SubdomainAppURL(appDetails.Apps.Port).String(), nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
require.Equal(t, http.StatusNotFound, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("AuthenticatedOK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
appDetails := setupProxyTest(t, nil)
|
||||
port, err := strconv.ParseInt(appDetails.Apps.Port.AppSlugOrPort, 10, 32)
|
||||
require.NoError(t, err)
|
||||
// set the port we have to be shared with authenticated users
|
||||
_, err = appDetails.SDKClient.UpsertWorkspaceAgentPortShare(ctx, appDetails.Workspace.ID, codersdk.UpsertWorkspaceAgentPortShareRequest{
|
||||
AgentName: proxyTestAgentName,
|
||||
Port: int32(port),
|
||||
ShareLevel: codersdk.WorkspaceAgentPortShareLevelAuthenticated,
|
||||
Protocol: codersdk.WorkspaceAgentPortShareProtocolHTTP,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
userClient, _ := coderdtest.CreateAnotherUser(t, appDetails.SDKClient, appDetails.FirstUser.OrganizationID, rbac.RoleMember())
|
||||
userAppClient := appDetails.AppClient(t)
|
||||
userAppClient.SetSessionToken(userClient.SessionToken())
|
||||
|
||||
resp, err := requestWithRetries(ctx, t, userAppClient, http.MethodGet, appDetails.SubdomainAppURL(appDetails.Apps.Port).String(), nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("PublicOK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
appDetails := setupProxyTest(t, nil)
|
||||
port, err := strconv.ParseInt(appDetails.Apps.Port.AppSlugOrPort, 10, 32)
|
||||
require.NoError(t, err)
|
||||
// set the port we have to be shared with public
|
||||
_, err = appDetails.SDKClient.UpsertWorkspaceAgentPortShare(ctx, appDetails.Workspace.ID, codersdk.UpsertWorkspaceAgentPortShareRequest{
|
||||
AgentName: proxyTestAgentName,
|
||||
Port: int32(port),
|
||||
ShareLevel: codersdk.WorkspaceAgentPortShareLevelPublic,
|
||||
Protocol: codersdk.WorkspaceAgentPortShareProtocolHTTP,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
publicAppClient := appDetails.AppClient(t)
|
||||
publicAppClient.SetSessionToken("")
|
||||
|
||||
resp, err := requestWithRetries(ctx, t, publicAppClient, http.MethodGet, appDetails.SubdomainAppURL(appDetails.Apps.Port).String(), nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("HTTPS", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
appDetails := setupProxyTest(t, &DeploymentOptions{
|
||||
ServeHTTPS: true,
|
||||
})
|
||||
port, err := strconv.ParseInt(appDetails.Apps.Port.AppSlugOrPort, 10, 32)
|
||||
require.NoError(t, err)
|
||||
_, err = appDetails.SDKClient.UpsertWorkspaceAgentPortShare(ctx, appDetails.Workspace.ID, codersdk.UpsertWorkspaceAgentPortShareRequest{
|
||||
AgentName: proxyTestAgentName,
|
||||
Port: int32(port),
|
||||
ShareLevel: codersdk.WorkspaceAgentPortShareLevelPublic,
|
||||
Protocol: codersdk.WorkspaceAgentPortShareProtocolHTTPS,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
publicAppClient := appDetails.AppClient(t)
|
||||
publicAppClient.SetSessionToken("")
|
||||
|
||||
resp, err := requestWithRetries(ctx, t, publicAppClient, http.MethodGet, appDetails.SubdomainAppURL(appDetails.Apps.Port).String(), nil)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("AppSharing", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -342,6 +342,10 @@ func (r Request) getDatabase(ctx context.Context, db database.Store) (*databaseR
|
||||
}
|
||||
// No port share found, so we keep default to owner.
|
||||
} else {
|
||||
if ps.Protocol == database.PortShareProtocolHttps {
|
||||
// Apply HTTPS protocol if specified.
|
||||
appURL = fmt.Sprintf("https://127.0.0.1:%d", portUint)
|
||||
}
|
||||
appSharingLevel = ps.ShareLevel
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -269,6 +269,3 @@ func Test_RequestValidate(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// getDatabase is tested heavily in auth_test.go, so we don't have specific
|
||||
// tests for it here.
|
||||
|
||||
Reference in New Issue
Block a user