mirror of https://github.com/coder/coder.git
feat: Implement experiment gated CRUD for workspace proxies (#6928)
* feat: Implement basic moon crud * chore: Implement enterprise endpoints for moons
This commit is contained in:
parent
385a4262e2
commit
b4afbe7720
|
@ -4995,6 +4995,71 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/workspaceproxies": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Enterprise"
|
||||
],
|
||||
"summary": "Get workspace proxies",
|
||||
"operationId": "get-workspace-proxies",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.WorkspaceProxy"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Templates"
|
||||
],
|
||||
"summary": "Create workspace proxy",
|
||||
"operationId": "create-workspace-proxy",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Create workspace proxy request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.CreateWorkspaceProxyRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.WorkspaceProxy"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspaces": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
@ -6706,6 +6771,26 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"codersdk.CreateWorkspaceProxyRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"wildcard_hostname": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.CreateWorkspaceRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
@ -7086,10 +7171,12 @@ const docTemplate = `{
|
|||
"codersdk.Experiment": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"template_editor"
|
||||
"template_editor",
|
||||
"moons"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"ExperimentTemplateEditor"
|
||||
"ExperimentTemplateEditor",
|
||||
"ExperimentMoons"
|
||||
]
|
||||
},
|
||||
"codersdk.Feature": {
|
||||
|
@ -9355,6 +9442,44 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"codersdk.WorkspaceProxy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"organization_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"url": {
|
||||
"description": "Full url including scheme of the proxy api url: https://us.example.com",
|
||||
"type": "string"
|
||||
},
|
||||
"wildcard_hostname": {
|
||||
"description": "WildcardHostname with the wildcard for subdomain based app hosting: *.us.example.com",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.WorkspaceQuota": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
@ -4399,6 +4399,61 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/workspaceproxies": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": ["application/json"],
|
||||
"tags": ["Enterprise"],
|
||||
"summary": "Get workspace proxies",
|
||||
"operationId": "get-workspace-proxies",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.WorkspaceProxy"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"consumes": ["application/json"],
|
||||
"produces": ["application/json"],
|
||||
"tags": ["Templates"],
|
||||
"summary": "Create workspace proxy",
|
||||
"operationId": "create-workspace-proxy",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Create workspace proxy request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.CreateWorkspaceProxyRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.WorkspaceProxy"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspaces": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
@ -5973,6 +6028,26 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"codersdk.CreateWorkspaceProxyRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"wildcard_hostname": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.CreateWorkspaceRequest": {
|
||||
"type": "object",
|
||||
"required": ["name", "template_id"],
|
||||
|
@ -6345,8 +6420,8 @@
|
|||
},
|
||||
"codersdk.Experiment": {
|
||||
"type": "string",
|
||||
"enum": ["template_editor"],
|
||||
"x-enum-varnames": ["ExperimentTemplateEditor"]
|
||||
"enum": ["template_editor", "moons"],
|
||||
"x-enum-varnames": ["ExperimentTemplateEditor", "ExperimentMoons"]
|
||||
},
|
||||
"codersdk.Feature": {
|
||||
"type": "object",
|
||||
|
@ -8447,6 +8522,44 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"codersdk.WorkspaceProxy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"organization_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"url": {
|
||||
"description": "Full url including scheme of the proxy api url: https://us.example.com",
|
||||
"type": "string"
|
||||
},
|
||||
"wildcard_hostname": {
|
||||
"description": "WildcardHostname with the wildcard for subdomain based app hosting: *.us.example.com",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.WorkspaceQuota": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
@ -16,7 +16,8 @@ type Auditable interface {
|
|||
database.GitSSHKey |
|
||||
database.WorkspaceBuild |
|
||||
database.AuditableGroup |
|
||||
database.License
|
||||
database.License |
|
||||
database.WorkspaceProxy
|
||||
}
|
||||
|
||||
// Map is a map of changed fields in an audited resource. It maps field names to
|
||||
|
|
|
@ -1685,6 +1685,34 @@ func (q *querier) GetWorkspaceByWorkspaceAppID(ctx context.Context, workspaceApp
|
|||
return fetch(q.log, q.auth, q.db.GetWorkspaceByWorkspaceAppID)(ctx, workspaceAppID)
|
||||
}
|
||||
|
||||
func (q *querier) GetWorkspaceProxies(ctx context.Context) ([]database.WorkspaceProxy, error) {
|
||||
return fetchWithPostFilter(q.auth, func(ctx context.Context, _ interface{}) ([]database.WorkspaceProxy, error) {
|
||||
return q.db.GetWorkspaceProxies(ctx)
|
||||
})(ctx, nil)
|
||||
}
|
||||
|
||||
func (q *querier) GetWorkspaceProxyByID(ctx context.Context, id uuid.UUID) (database.WorkspaceProxy, error) {
|
||||
return fetch(q.log, q.auth, q.db.GetWorkspaceProxyByID)(ctx, id)
|
||||
}
|
||||
|
||||
func (q *querier) InsertWorkspaceProxy(ctx context.Context, arg database.InsertWorkspaceProxyParams) (database.WorkspaceProxy, error) {
|
||||
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) UpdateWorkspaceProxyDeleted(ctx context.Context, arg database.UpdateWorkspaceProxyDeletedParams) error {
|
||||
fetch := func(ctx context.Context, arg database.UpdateWorkspaceProxyDeletedParams) (database.WorkspaceProxy, error) {
|
||||
return q.db.GetWorkspaceProxyByID(ctx, arg.ID)
|
||||
}
|
||||
return deleteQ(q.log, q.auth, fetch, q.db.UpdateWorkspaceProxyDeleted)(ctx, arg)
|
||||
}
|
||||
|
||||
func authorizedTemplateVersionFromJob(ctx context.Context, q *querier, job database.ProvisionerJob) (database.TemplateVersion, error) {
|
||||
switch job.Type {
|
||||
case database.ProvisionerJobTypeTemplateVersionDryRun:
|
||||
|
|
|
@ -438,6 +438,36 @@ func (s *MethodTestSuite) TestOrganization() {
|
|||
}))
|
||||
}
|
||||
|
||||
func (s *MethodTestSuite) TestWorkspaceProxy() {
|
||||
s.Run("InsertWorkspaceProxy", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args(database.InsertWorkspaceProxyParams{
|
||||
ID: uuid.New(),
|
||||
}).Asserts(rbac.ResourceWorkspaceProxy, rbac.ActionCreate)
|
||||
}))
|
||||
s.Run("UpdateWorkspaceProxy", s.Subtest(func(db database.Store, check *expects) {
|
||||
p := dbgen.WorkspaceProxy(s.T(), db, database.WorkspaceProxy{})
|
||||
check.Args(database.UpdateWorkspaceProxyParams{
|
||||
ID: p.ID,
|
||||
}).Asserts(p, rbac.ActionUpdate)
|
||||
}))
|
||||
s.Run("GetWorkspaceProxyByID", s.Subtest(func(db database.Store, check *expects) {
|
||||
p := dbgen.WorkspaceProxy(s.T(), db, database.WorkspaceProxy{})
|
||||
check.Args(p.ID).Asserts(p, rbac.ActionRead).Returns(p)
|
||||
}))
|
||||
s.Run("UpdateWorkspaceProxyDeleted", s.Subtest(func(db database.Store, check *expects) {
|
||||
p := dbgen.WorkspaceProxy(s.T(), db, database.WorkspaceProxy{})
|
||||
check.Args(database.UpdateWorkspaceProxyDeletedParams{
|
||||
ID: p.ID,
|
||||
Deleted: true,
|
||||
}).Asserts(p, rbac.ActionDelete)
|
||||
}))
|
||||
s.Run("GetWorkspaceProxies", s.Subtest(func(db database.Store, check *expects) {
|
||||
p1 := dbgen.WorkspaceProxy(s.T(), db, database.WorkspaceProxy{})
|
||||
p2 := dbgen.WorkspaceProxy(s.T(), db, database.WorkspaceProxy{})
|
||||
check.Args().Asserts(p1, rbac.ActionRead, p2, rbac.ActionRead).Returns(slice.New(p1, p2))
|
||||
}))
|
||||
}
|
||||
|
||||
func (s *MethodTestSuite) TestParameters() {
|
||||
s.Run("Workspace/InsertParameterValue", s.Subtest(func(db database.Store, check *expects) {
|
||||
w := dbgen.Workspace(s.T(), db, database.Workspace{})
|
||||
|
|
|
@ -65,6 +65,7 @@ func New() database.Store {
|
|||
workspaceApps: make([]database.WorkspaceApp, 0),
|
||||
workspaces: make([]database.Workspace, 0),
|
||||
licenses: make([]database.License, 0),
|
||||
workspaceProxies: make([]database.WorkspaceProxy, 0),
|
||||
locks: map[int64]struct{}{},
|
||||
},
|
||||
}
|
||||
|
@ -132,6 +133,7 @@ type data struct {
|
|||
workspaceResourceMetadata []database.WorkspaceResourceMetadatum
|
||||
workspaceResources []database.WorkspaceResource
|
||||
workspaces []database.Workspace
|
||||
workspaceProxies []database.WorkspaceProxy
|
||||
|
||||
// Locks is a map of lock names. Any keys within the map are currently
|
||||
// locked.
|
||||
|
@ -4979,3 +4981,86 @@ func (q *fakeQuerier) UpdateWorkspaceAgentStartupLogOverflowByID(_ context.Conte
|
|||
}
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetWorkspaceProxies(_ context.Context) ([]database.WorkspaceProxy, error) {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
cpy := make([]database.WorkspaceProxy, 0, len(q.workspaceProxies))
|
||||
|
||||
for _, p := range q.workspaceProxies {
|
||||
if !p.Deleted {
|
||||
cpy = append(cpy, p)
|
||||
}
|
||||
}
|
||||
return cpy, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) GetWorkspaceProxyByID(_ context.Context, id uuid.UUID) (database.WorkspaceProxy, error) {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
for _, proxy := range q.workspaceProxies {
|
||||
if proxy.ID == id {
|
||||
return proxy, nil
|
||||
}
|
||||
}
|
||||
return database.WorkspaceProxy{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) InsertWorkspaceProxy(_ context.Context, arg database.InsertWorkspaceProxyParams) (database.WorkspaceProxy, error) {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
for _, p := range q.workspaceProxies {
|
||||
if !p.Deleted && p.Name == arg.Name {
|
||||
return database.WorkspaceProxy{}, errDuplicateKey
|
||||
}
|
||||
}
|
||||
|
||||
p := database.WorkspaceProxy{
|
||||
ID: arg.ID,
|
||||
Name: arg.Name,
|
||||
Icon: arg.Icon,
|
||||
Url: arg.Url,
|
||||
WildcardHostname: arg.WildcardHostname,
|
||||
CreatedAt: arg.CreatedAt,
|
||||
UpdatedAt: arg.UpdatedAt,
|
||||
Deleted: false,
|
||||
}
|
||||
q.workspaceProxies = append(q.workspaceProxies, p)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (q *fakeQuerier) UpdateWorkspaceProxy(_ context.Context, arg database.UpdateWorkspaceProxyParams) (database.WorkspaceProxy, error) {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
for i, p := range q.workspaceProxies {
|
||||
if p.ID == arg.ID {
|
||||
p.Name = arg.Name
|
||||
p.Icon = arg.Icon
|
||||
p.Url = arg.Url
|
||||
p.WildcardHostname = arg.WildcardHostname
|
||||
p.UpdatedAt = database.Now()
|
||||
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()
|
||||
|
||||
for i, p := range q.workspaceProxies {
|
||||
if p.ID == arg.ID {
|
||||
p.Deleted = arg.Deleted
|
||||
p.UpdatedAt = database.Now()
|
||||
q.workspaceProxies[i] = p
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
|
|
@ -327,6 +327,21 @@ func WorkspaceResourceMetadatums(t testing.TB, db database.Store, seed database.
|
|||
return meta
|
||||
}
|
||||
|
||||
func WorkspaceProxy(t testing.TB, db database.Store, orig database.WorkspaceProxy) database.WorkspaceProxy {
|
||||
resource, err := db.InsertWorkspaceProxy(context.Background(), database.InsertWorkspaceProxyParams{
|
||||
ID: takeFirst(orig.ID, uuid.New()),
|
||||
Name: takeFirst(orig.Name, namesgenerator.GetRandomName(1)),
|
||||
DisplayName: takeFirst(orig.DisplayName, namesgenerator.GetRandomName(1)),
|
||||
Icon: takeFirst(orig.Icon, namesgenerator.GetRandomName(1)),
|
||||
Url: takeFirst(orig.Url, fmt.Sprintf("https://%s.com", namesgenerator.GetRandomName(1))),
|
||||
WildcardHostname: takeFirst(orig.WildcardHostname, fmt.Sprintf(".%s.com", namesgenerator.GetRandomName(1))),
|
||||
CreatedAt: takeFirst(orig.CreatedAt, database.Now()),
|
||||
UpdatedAt: takeFirst(orig.UpdatedAt, database.Now()),
|
||||
})
|
||||
require.NoError(t, err, "insert app")
|
||||
return resource
|
||||
}
|
||||
|
||||
func File(t testing.TB, db database.Store, orig database.File) database.File {
|
||||
file, err := db.InsertFile(context.Background(), database.InsertFileParams{
|
||||
ID: takeFirst(orig.ID, uuid.New()),
|
||||
|
|
|
@ -75,6 +75,13 @@ func TestGenerator(t *testing.T) {
|
|||
require.Equal(t, exp, must(db.GetWorkspaceResourceMetadataByResourceIDs(context.Background(), []uuid.UUID{exp[0].WorkspaceResourceID})))
|
||||
})
|
||||
|
||||
t.Run("WorkspaceProxy", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
db := dbfake.New()
|
||||
exp := dbgen.WorkspaceProxy(t, db, database.WorkspaceProxy{})
|
||||
require.Equal(t, exp, must(db.GetWorkspaceProxyByID(context.Background(), exp.ID)))
|
||||
})
|
||||
|
||||
t.Run("Job", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
db := dbfake.New()
|
||||
|
|
|
@ -635,6 +635,22 @@ CREATE TABLE workspace_builds (
|
|||
max_deadline timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE workspace_proxies (
|
||||
id uuid NOT NULL,
|
||||
name text NOT NULL,
|
||||
display_name text NOT NULL,
|
||||
icon text NOT NULL,
|
||||
url text NOT NULL,
|
||||
wildcard_hostname text NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
deleted boolean NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN workspace_proxies.url IS 'Full url including scheme of the proxy api url: https://us.example.com';
|
||||
|
||||
COMMENT ON COLUMN workspace_proxies.wildcard_hostname IS 'Hostname with the wildcard for subdomain based app hosting: *.us.example.com';
|
||||
|
||||
CREATE TABLE workspace_resource_metadata (
|
||||
workspace_resource_id uuid NOT NULL,
|
||||
key character varying(1024) NOT NULL,
|
||||
|
@ -804,6 +820,9 @@ ALTER TABLE ONLY workspace_builds
|
|||
ALTER TABLE ONLY workspace_builds
|
||||
ADD CONSTRAINT workspace_builds_workspace_id_build_number_key UNIQUE (workspace_id, build_number);
|
||||
|
||||
ALTER TABLE ONLY workspace_proxies
|
||||
ADD CONSTRAINT workspace_proxies_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY workspace_resource_metadata
|
||||
ADD CONSTRAINT workspace_resource_metadata_name UNIQUE (workspace_resource_id, key);
|
||||
|
||||
|
@ -860,6 +879,8 @@ CREATE INDEX workspace_agents_auth_token_idx ON workspace_agents USING btree (au
|
|||
|
||||
CREATE INDEX workspace_agents_resource_id_idx ON workspace_agents USING btree (resource_id);
|
||||
|
||||
CREATE UNIQUE INDEX workspace_proxies_name_idx ON workspace_proxies USING btree (name) WHERE (deleted = false);
|
||||
|
||||
CREATE INDEX workspace_resources_job_id_idx ON workspace_resources USING btree (job_id);
|
||||
|
||||
CREATE UNIQUE INDEX workspaces_owner_id_lower_idx ON workspaces USING btree (owner_id, lower((name)::text)) WHERE (deleted = false);
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
BEGIN;
|
||||
DROP TABLE workspace_proxies;
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,23 @@
|
|||
BEGIN;
|
||||
CREATE TABLE workspace_proxies (
|
||||
id uuid NOT NULL,
|
||||
name text NOT NULL,
|
||||
display_name text NOT NULL,
|
||||
icon text NOT NULL,
|
||||
url text NOT NULL,
|
||||
wildcard_hostname text NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
deleted boolean NOT NULL,
|
||||
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN workspace_proxies.url IS 'Full url including scheme of the proxy api url: https://us.example.com';
|
||||
COMMENT ON COLUMN workspace_proxies.wildcard_hostname IS 'Hostname with the wildcard for subdomain based app hosting: *.us.example.com';
|
||||
|
||||
|
||||
-- Enforces no active proxies have the same name.
|
||||
CREATE UNIQUE INDEX ON workspace_proxies (name) WHERE deleted = FALSE;
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,14 @@
|
|||
INSERT INTO workspace_proxies
|
||||
(id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted)
|
||||
VALUES
|
||||
(
|
||||
'cf8ede8c-ff47-441f-a738-d92e4e34a657',
|
||||
'us',
|
||||
'United States',
|
||||
'/emojis/us.png',
|
||||
'https://us.coder.com',
|
||||
'*.us.coder.com',
|
||||
'2023-03-30 12:00:00.000+02',
|
||||
'2023-03-30 12:00:00.000+02',
|
||||
false
|
||||
);
|
|
@ -181,6 +181,11 @@ func (p ProvisionerDaemon) RBACObject() rbac.Object {
|
|||
return rbac.ResourceProvisionerDaemon.WithID(p.ID)
|
||||
}
|
||||
|
||||
func (w WorkspaceProxy) RBACObject() rbac.Object {
|
||||
return rbac.ResourceWorkspaceProxy.
|
||||
WithID(w.ID)
|
||||
}
|
||||
|
||||
func (f File) RBACObject() rbac.Object {
|
||||
return rbac.ResourceFile.
|
||||
WithID(f.ID).
|
||||
|
|
|
@ -1663,6 +1663,20 @@ type WorkspaceBuildParameter struct {
|
|||
Value string `db:"value" json:"value"`
|
||||
}
|
||||
|
||||
type WorkspaceProxy struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
DisplayName string `db:"display_name" json:"display_name"`
|
||||
Icon string `db:"icon" json:"icon"`
|
||||
// Full url including scheme of the proxy api url: https://us.example.com
|
||||
Url string `db:"url" json:"url"`
|
||||
// Hostname with the wildcard for subdomain based app hosting: *.us.example.com
|
||||
WildcardHostname string `db:"wildcard_hostname" json:"wildcard_hostname"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
Deleted bool `db:"deleted" json:"deleted"`
|
||||
}
|
||||
|
||||
type WorkspaceResource struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
|
|
|
@ -146,6 +146,8 @@ type sqlcQuerier interface {
|
|||
GetWorkspaceByID(ctx context.Context, id uuid.UUID) (Workspace, error)
|
||||
GetWorkspaceByOwnerIDAndName(ctx context.Context, arg GetWorkspaceByOwnerIDAndNameParams) (Workspace, error)
|
||||
GetWorkspaceByWorkspaceAppID(ctx context.Context, workspaceAppID uuid.UUID) (Workspace, error)
|
||||
GetWorkspaceProxies(ctx context.Context) ([]WorkspaceProxy, error)
|
||||
GetWorkspaceProxyByID(ctx context.Context, id uuid.UUID) (WorkspaceProxy, error)
|
||||
GetWorkspaceResourceByID(ctx context.Context, id uuid.UUID) (WorkspaceResource, error)
|
||||
GetWorkspaceResourceMetadataByResourceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceResourceMetadatum, error)
|
||||
GetWorkspaceResourceMetadataCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceResourceMetadatum, error)
|
||||
|
@ -193,6 +195,7 @@ type sqlcQuerier interface {
|
|||
InsertWorkspaceApp(ctx context.Context, arg InsertWorkspaceAppParams) (WorkspaceApp, error)
|
||||
InsertWorkspaceBuild(ctx context.Context, arg InsertWorkspaceBuildParams) (WorkspaceBuild, error)
|
||||
InsertWorkspaceBuildParameters(ctx context.Context, arg InsertWorkspaceBuildParametersParams) error
|
||||
InsertWorkspaceProxy(ctx context.Context, arg InsertWorkspaceProxyParams) (WorkspaceProxy, error)
|
||||
InsertWorkspaceResource(ctx context.Context, arg InsertWorkspaceResourceParams) (WorkspaceResource, error)
|
||||
InsertWorkspaceResourceMetadata(ctx context.Context, arg InsertWorkspaceResourceMetadataParams) ([]WorkspaceResourceMetadatum, error)
|
||||
ParameterValue(ctx context.Context, id uuid.UUID) (ParameterValue, error)
|
||||
|
@ -241,6 +244,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
|
||||
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
|
||||
UpsertLastUpdateCheck(ctx context.Context, value string) error
|
||||
|
|
|
@ -2803,6 +2803,198 @@ func (q *sqlQuerier) UpdateProvisionerJobWithCompleteByID(ctx context.Context, a
|
|||
return err
|
||||
}
|
||||
|
||||
const getWorkspaceProxies = `-- name: GetWorkspaceProxies :many
|
||||
SELECT
|
||||
id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted
|
||||
FROM
|
||||
workspace_proxies
|
||||
WHERE
|
||||
deleted = false
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceProxies(ctx context.Context) ([]WorkspaceProxy, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getWorkspaceProxies)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []WorkspaceProxy
|
||||
for rows.Next() {
|
||||
var i WorkspaceProxy
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.DisplayName,
|
||||
&i.Icon,
|
||||
&i.Url,
|
||||
&i.WildcardHostname,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Deleted,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getWorkspaceProxyByID = `-- name: GetWorkspaceProxyByID :one
|
||||
SELECT
|
||||
id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted
|
||||
FROM
|
||||
workspace_proxies
|
||||
WHERE
|
||||
id = $1
|
||||
LIMIT
|
||||
1
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceProxyByID(ctx context.Context, id uuid.UUID) (WorkspaceProxy, error) {
|
||||
row := q.db.QueryRowContext(ctx, getWorkspaceProxyByID, 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,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const insertWorkspaceProxy = `-- name: InsertWorkspaceProxy :one
|
||||
INSERT INTO
|
||||
workspace_proxies (
|
||||
id,
|
||||
name,
|
||||
display_name,
|
||||
icon,
|
||||
url,
|
||||
wildcard_hostname,
|
||||
created_at,
|
||||
updated_at,
|
||||
deleted
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, false) RETURNING id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted
|
||||
`
|
||||
|
||||
type InsertWorkspaceProxyParams struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
DisplayName string `db:"display_name" json:"display_name"`
|
||||
Icon string `db:"icon" json:"icon"`
|
||||
Url string `db:"url" json:"url"`
|
||||
WildcardHostname string `db:"wildcard_hostname" json:"wildcard_hostname"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertWorkspaceProxy(ctx context.Context, arg InsertWorkspaceProxyParams) (WorkspaceProxy, error) {
|
||||
row := q.db.QueryRowContext(ctx, insertWorkspaceProxy,
|
||||
arg.ID,
|
||||
arg.Name,
|
||||
arg.DisplayName,
|
||||
arg.Icon,
|
||||
arg.Url,
|
||||
arg.WildcardHostname,
|
||||
arg.CreatedAt,
|
||||
arg.UpdatedAt,
|
||||
)
|
||||
var i WorkspaceProxy
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.DisplayName,
|
||||
&i.Icon,
|
||||
&i.Url,
|
||||
&i.WildcardHostname,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Deleted,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateWorkspaceProxy = `-- name: UpdateWorkspaceProxy :one
|
||||
UPDATE
|
||||
workspace_proxies
|
||||
SET
|
||||
name = $1,
|
||||
display_name = $2,
|
||||
url = $3,
|
||||
wildcard_hostname = $4,
|
||||
icon = $5,
|
||||
updated_at = Now()
|
||||
WHERE
|
||||
id = $6
|
||||
RETURNING id, name, display_name, icon, url, wildcard_hostname, created_at, updated_at, deleted
|
||||
`
|
||||
|
||||
type UpdateWorkspaceProxyParams struct {
|
||||
Name string `db:"name" json:"name"`
|
||||
DisplayName string `db:"display_name" json:"display_name"`
|
||||
Url string `db:"url" json:"url"`
|
||||
WildcardHostname string `db:"wildcard_hostname" json:"wildcard_hostname"`
|
||||
Icon string `db:"icon" json:"icon"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateWorkspaceProxy(ctx context.Context, arg UpdateWorkspaceProxyParams) (WorkspaceProxy, error) {
|
||||
row := q.db.QueryRowContext(ctx, updateWorkspaceProxy,
|
||||
arg.Name,
|
||||
arg.DisplayName,
|
||||
arg.Url,
|
||||
arg.WildcardHostname,
|
||||
arg.Icon,
|
||||
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,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateWorkspaceProxyDeleted = `-- name: UpdateWorkspaceProxyDeleted :exec
|
||||
UPDATE
|
||||
workspace_proxies
|
||||
SET
|
||||
updated_at = Now(),
|
||||
deleted = $1
|
||||
WHERE
|
||||
id = $2
|
||||
`
|
||||
|
||||
type UpdateWorkspaceProxyDeletedParams struct {
|
||||
Deleted bool `db:"deleted" json:"deleted"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateWorkspaceProxyDeleted(ctx context.Context, arg UpdateWorkspaceProxyDeletedParams) error {
|
||||
_, err := q.db.ExecContext(ctx, updateWorkspaceProxyDeleted, arg.Deleted, arg.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
const getQuotaAllowanceForUser = `-- name: GetQuotaAllowanceForUser :one
|
||||
SELECT
|
||||
coalesce(SUM(quota_allowance), 0)::BIGINT
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
-- name: InsertWorkspaceProxy :one
|
||||
INSERT INTO
|
||||
workspace_proxies (
|
||||
id,
|
||||
name,
|
||||
display_name,
|
||||
icon,
|
||||
url,
|
||||
wildcard_hostname,
|
||||
created_at,
|
||||
updated_at,
|
||||
deleted
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, false) RETURNING *;
|
||||
|
||||
-- name: UpdateWorkspaceProxy :one
|
||||
UPDATE
|
||||
workspace_proxies
|
||||
SET
|
||||
name = @name,
|
||||
display_name = @display_name,
|
||||
url = @url,
|
||||
wildcard_hostname = @wildcard_hostname,
|
||||
icon = @icon,
|
||||
updated_at = Now()
|
||||
WHERE
|
||||
id = @id
|
||||
RETURNING *;
|
||||
|
||||
|
||||
-- name: UpdateWorkspaceProxyDeleted :exec
|
||||
UPDATE
|
||||
workspace_proxies
|
||||
SET
|
||||
updated_at = Now(),
|
||||
deleted = @deleted
|
||||
WHERE
|
||||
id = @id;
|
||||
|
||||
-- name: GetWorkspaceProxyByID :one
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
workspace_proxies
|
||||
WHERE
|
||||
id = $1
|
||||
LIMIT
|
||||
1;
|
||||
|
||||
-- name: GetWorkspaceProxies :many
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
workspace_proxies
|
||||
WHERE
|
||||
deleted = false;
|
|
@ -31,5 +31,6 @@ const (
|
|||
UniqueTemplatesOrganizationIDNameIndex UniqueConstraint = "templates_organization_id_name_idx" // CREATE UNIQUE INDEX templates_organization_id_name_idx ON templates USING btree (organization_id, lower((name)::text)) WHERE (deleted = false);
|
||||
UniqueUsersEmailLowerIndex UniqueConstraint = "users_email_lower_idx" // CREATE UNIQUE INDEX users_email_lower_idx ON users USING btree (lower(email)) WHERE (deleted = false);
|
||||
UniqueUsersUsernameLowerIndex UniqueConstraint = "users_username_lower_idx" // CREATE UNIQUE INDEX users_username_lower_idx ON users USING btree (lower(username)) WHERE (deleted = false);
|
||||
UniqueWorkspaceProxiesNameIndex UniqueConstraint = "workspace_proxies_name_idx" // CREATE UNIQUE INDEX workspace_proxies_name_idx ON workspace_proxies USING btree (name) WHERE (deleted = false);
|
||||
UniqueWorkspacesOwnerIDLowerIndex UniqueConstraint = "workspaces_owner_id_lower_idx" // CREATE UNIQUE INDEX workspaces_owner_id_lower_idx ON workspaces USING btree (owner_id, lower((name)::text)) WHERE (deleted = false);
|
||||
)
|
||||
|
|
|
@ -22,6 +22,14 @@ var (
|
|||
Type: "workspace",
|
||||
}
|
||||
|
||||
// ResourceWorkspaceProxy CRUD. Org
|
||||
// create/delete = make or delete proxies
|
||||
// read = read proxy urls
|
||||
// update = edit workspace proxy fields
|
||||
ResourceWorkspaceProxy = Object{
|
||||
Type: "workspace_proxy",
|
||||
}
|
||||
|
||||
// ResourceWorkspaceExecution CRUD. Org + User owner
|
||||
// create = workspace remote execution
|
||||
// read = ?
|
||||
|
|
|
@ -45,6 +45,7 @@ const (
|
|||
FeatureExternalProvisionerDaemons FeatureName = "external_provisioner_daemons"
|
||||
FeatureAppearance FeatureName = "appearance"
|
||||
FeatureAdvancedTemplateScheduling FeatureName = "advanced_template_scheduling"
|
||||
FeatureWorkspaceProxy FeatureName = "workspace_proxy"
|
||||
)
|
||||
|
||||
// FeatureNames must be kept in-sync with the Feature enum above.
|
||||
|
@ -59,6 +60,7 @@ var FeatureNames = []FeatureName{
|
|||
FeatureExternalProvisionerDaemons,
|
||||
FeatureAppearance,
|
||||
FeatureAdvancedTemplateScheduling,
|
||||
FeatureWorkspaceProxy,
|
||||
}
|
||||
|
||||
// Humanize returns the feature name in a human-readable format.
|
||||
|
@ -1559,6 +1561,10 @@ const (
|
|||
// for all users.
|
||||
ExperimentTemplateEditor Experiment = "template_editor"
|
||||
|
||||
// ExperimentMoons enabled the workspace proxy endpoints and CRUD. This
|
||||
// feature is not yet complete in functionality.
|
||||
ExperimentMoons Experiment = "moons"
|
||||
|
||||
// Add new experiments here!
|
||||
// ExperimentExample Experiment = "example"
|
||||
)
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
package codersdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type CreateWorkspaceProxyRequest struct {
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Icon string `json:"icon"`
|
||||
URL string `json:"url"`
|
||||
WildcardHostname string `json:"wildcard_hostname"`
|
||||
}
|
||||
|
||||
type WorkspaceProxy struct {
|
||||
ID uuid.UUID `db:"id" json:"id" format:"uuid"`
|
||||
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id" format:"uuid"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Icon string `db:"icon" json:"icon"`
|
||||
// Full url including scheme of the proxy api url: https://us.example.com
|
||||
URL string `db:"url" json:"url"`
|
||||
// WildcardHostname with the wildcard for subdomain based app hosting: *.us.example.com
|
||||
WildcardHostname string `db:"wildcard_hostname" json:"wildcard_hostname"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at" format:"date-time"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at" format:"date-time"`
|
||||
Deleted bool `db:"deleted" json:"deleted"`
|
||||
}
|
||||
|
||||
func (c *Client) CreateWorkspaceProxy(ctx context.Context, req CreateWorkspaceProxyRequest) (WorkspaceProxy, error) {
|
||||
res, err := c.Request(ctx, http.MethodPost,
|
||||
"/api/v2/workspaceproxies",
|
||||
req,
|
||||
)
|
||||
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) WorkspaceProxiesByOrganization(ctx context.Context) ([]WorkspaceProxy, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet,
|
||||
"/api/v2/workspaceproxies",
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("make request: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, ReadBodyAsError(res)
|
||||
}
|
||||
|
||||
var proxies []WorkspaceProxy
|
||||
return proxies, json.NewDecoder(res.Body).Decode(&proxies)
|
||||
}
|
|
@ -20,6 +20,7 @@ We track the following resources:
|
|||
| User<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>avatar_url</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>true</td></tr><tr><td>email</td><td>true</td></tr><tr><td>hashed_password</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_seen_at</td><td>false</td></tr><tr><td>login_type</td><td>false</td></tr><tr><td>rbac_roles</td><td>true</td></tr><tr><td>status</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>username</td><td>true</td></tr></tbody></table> |
|
||||
| Workspace<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>autostart_schedule</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_used_at</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>owner_id</td><td>true</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>ttl</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
|
||||
| WorkspaceBuild<br><i>start, stop</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>build_number</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>daily_cost</td><td>false</td></tr><tr><td>deadline</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>initiator_id</td><td>false</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>max_deadline</td><td>false</td></tr><tr><td>provisioner_state</td><td>false</td></tr><tr><td>reason</td><td>false</td></tr><tr><td>template_version_id</td><td>true</td></tr><tr><td>transition</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>workspace_id</td><td>false</td></tr></tbody></table> |
|
||||
| WorkspaceProxy<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>true</td></tr><tr><td>deleted</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>updated_at</td><td>true</td></tr><tr><td>url</td><td>true</td></tr><tr><td>wildcard_hostname</td><td>true</td></tr></tbody></table> |
|
||||
|
||||
<!-- End generated by 'make docs/admin/audit-logs.md'. -->
|
||||
|
||||
|
|
|
@ -1181,3 +1181,61 @@ curl -X GET http://coder-server:8080/api/v2/workspace-quota/{user} \
|
|||
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.WorkspaceQuota](schemas.md#codersdkworkspacequota) |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## Get workspace proxies
|
||||
|
||||
### Code samples
|
||||
|
||||
```shell
|
||||
# Example request using curl
|
||||
curl -X GET http://coder-server:8080/api/v2/workspaceproxies \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Coder-Session-Token: API_KEY'
|
||||
```
|
||||
|
||||
`GET /workspaceproxies`
|
||||
|
||||
### Example responses
|
||||
|
||||
> 200 Response
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"deleted": true,
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"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 | array of [codersdk.WorkspaceProxy](schemas.md#codersdkworkspaceproxy) |
|
||||
|
||||
<h3 id="get-workspace-proxies-responseschema">Response Schema</h3>
|
||||
|
||||
Status Code **200**
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| --------------------- | ----------------- | -------- | ------------ | -------------------------------------------------------------------------------------- |
|
||||
| `[array item]` | array | false | | |
|
||||
| `» created_at` | string(date-time) | false | | |
|
||||
| `» deleted` | boolean | false | | |
|
||||
| `» icon` | string | false | | |
|
||||
| `» id` | string(uuid) | false | | |
|
||||
| `» name` | string | false | | |
|
||||
| `» organization_id` | string(uuid) | false | | |
|
||||
| `» updated_at` | string(date-time) | false | | |
|
||||
| `» url` | string | false | | Full URL including scheme of the proxy api url: https://us.example.com |
|
||||
| `» wildcard_hostname` | string | false | | Wildcard hostname with the wildcard for subdomain based app hosting: \*.us.example.com |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
|
|
@ -1543,6 +1543,28 @@ CreateParameterRequest is a structure used to create a new parameter value for a
|
|||
| `transition` | `stop` |
|
||||
| `transition` | `delete` |
|
||||
|
||||
## codersdk.CreateWorkspaceProxyRequest
|
||||
|
||||
```json
|
||||
{
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"name": "string",
|
||||
"url": "string",
|
||||
"wildcard_hostname": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ------------------- | ------ | -------- | ------------ | ----------- |
|
||||
| `display_name` | string | false | | |
|
||||
| `icon` | string | false | | |
|
||||
| `name` | string | false | | |
|
||||
| `url` | string | false | | |
|
||||
| `wildcard_hostname` | string | false | | |
|
||||
|
||||
## codersdk.CreateWorkspaceRequest
|
||||
|
||||
```json
|
||||
|
@ -2430,6 +2452,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a
|
|||
| Value |
|
||||
| ----------------- |
|
||||
| `template_editor` |
|
||||
| `moons` |
|
||||
|
||||
## codersdk.Feature
|
||||
|
||||
|
@ -5085,6 +5108,36 @@ Parameter represents a set value for the scope.
|
|||
| `stopped` | integer | false | | |
|
||||
| `tx_bytes` | integer | false | | |
|
||||
|
||||
## codersdk.WorkspaceProxy
|
||||
|
||||
```json
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"deleted": true,
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"url": "string",
|
||||
"wildcard_hostname": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ------------------- | ------- | -------- | ------------ | -------------------------------------------------------------------------------------- |
|
||||
| `created_at` | string | false | | |
|
||||
| `deleted` | boolean | false | | |
|
||||
| `icon` | string | false | | |
|
||||
| `id` | string | false | | |
|
||||
| `name` | string | false | | |
|
||||
| `organization_id` | string | false | | |
|
||||
| `updated_at` | string | false | | |
|
||||
| `url` | string | false | | Full URL including scheme of the proxy api url: https://us.example.com |
|
||||
| `wildcard_hostname` | string | false | | Wildcard hostname with the wildcard for subdomain based app hosting: \*.us.example.com |
|
||||
|
||||
## codersdk.WorkspaceQuota
|
||||
|
||||
```json
|
||||
|
|
|
@ -2543,3 +2543,61 @@ Status Code **200**
|
|||
| `type` | `bool` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## Create workspace proxy
|
||||
|
||||
### Code samples
|
||||
|
||||
```shell
|
||||
# Example request using curl
|
||||
curl -X POST http://coder-server:8080/api/v2/workspaceproxies \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Coder-Session-Token: API_KEY'
|
||||
```
|
||||
|
||||
`POST /workspaceproxies`
|
||||
|
||||
> Body parameter
|
||||
|
||||
```json
|
||||
{
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"name": "string",
|
||||
"url": "string",
|
||||
"wildcard_hostname": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | In | Type | Required | Description |
|
||||
| ------ | ---- | -------------------------------------------------------------------------------------- | -------- | ------------------------------ |
|
||||
| `body` | body | [codersdk.CreateWorkspaceProxyRequest](schemas.md#codersdkcreateworkspaceproxyrequest) | true | Create workspace proxy request |
|
||||
|
||||
### Example responses
|
||||
|
||||
> 201 Response
|
||||
|
||||
```json
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"deleted": true,
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"url": "string",
|
||||
"wildcard_hostname": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### Responses
|
||||
|
||||
| Status | Meaning | Description | Schema |
|
||||
| ------ | ------------------------------------------------------------ | ----------- | ------------------------------------------------------------ |
|
||||
| 201 | [Created](https://tools.ietf.org/html/rfc7231#section-6.3.2) | Created | [codersdk.WorkspaceProxy](schemas.md#codersdkworkspaceproxy) |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
|
|
@ -162,6 +162,17 @@ var auditableResourcesTypes = map[any]map[string]Action{
|
|||
"exp": ActionTrack,
|
||||
"uuid": ActionTrack,
|
||||
},
|
||||
&database.WorkspaceProxy{}: {
|
||||
"id": ActionTrack,
|
||||
"name": ActionTrack,
|
||||
"display_name": ActionTrack,
|
||||
"icon": ActionTrack,
|
||||
"url": ActionTrack,
|
||||
"wildcard_hostname": ActionTrack,
|
||||
"created_at": ActionTrack,
|
||||
"updated_at": ActionTrack,
|
||||
"deleted": ActionTrack,
|
||||
},
|
||||
}
|
||||
|
||||
// auditMap converts a map of struct pointers to a map of struct names as
|
||||
|
|
|
@ -81,6 +81,22 @@ func New(ctx context.Context, options *Options) (*API, error) {
|
|||
r.Get("/", api.licenses)
|
||||
r.Delete("/{id}", api.deleteLicense)
|
||||
})
|
||||
r.Route("/workspaceproxies", func(r chi.Router) {
|
||||
r.Use(
|
||||
apiKeyMiddleware,
|
||||
api.moonsEnabledMW,
|
||||
)
|
||||
r.Post("/", api.postWorkspaceProxy)
|
||||
r.Get("/", api.workspaceProxies)
|
||||
// TODO: Add specific workspace proxy endpoints.
|
||||
//r.Route("/{proxyName}", func(r chi.Router) {
|
||||
// r.Use(
|
||||
// httpmw.ExtractWorkspaceProxyByNameParam(api.Database),
|
||||
// )
|
||||
//
|
||||
// r.Get("/", api.workspaceProxyByName)
|
||||
//})
|
||||
})
|
||||
r.Route("/organizations/{organization}/groups", func(r chi.Router) {
|
||||
r.Use(
|
||||
apiKeyMiddleware,
|
||||
|
@ -254,6 +270,7 @@ func (api *API) updateEntitlements(ctx context.Context) error {
|
|||
codersdk.FeatureTemplateRBAC: api.RBAC,
|
||||
codersdk.FeatureExternalProvisionerDaemons: true,
|
||||
codersdk.FeatureAdvancedTemplateScheduling: true,
|
||||
codersdk.FeatureWorkspaceProxy: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -53,6 +53,7 @@ func TestEntitlements(t *testing.T) {
|
|||
codersdk.FeatureTemplateRBAC: 1,
|
||||
codersdk.FeatureExternalProvisionerDaemons: 1,
|
||||
codersdk.FeatureAdvancedTemplateScheduling: 1,
|
||||
codersdk.FeatureWorkspaceProxy: 1,
|
||||
},
|
||||
})
|
||||
res, err := client.Entitlements(context.Background())
|
||||
|
|
|
@ -279,3 +279,24 @@ func (api *API) templateRBACEnabledMW(next http.Handler) http.Handler {
|
|||
next.ServeHTTP(rw, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (api *API) moonsEnabledMW(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
// The experiment must be enabled.
|
||||
if !api.AGPL.Experiments.Enabled(codersdk.ExperimentMoons) {
|
||||
httpapi.RouteNotFound(rw)
|
||||
return
|
||||
}
|
||||
|
||||
// Entitlement must be enabled.
|
||||
api.entitlementsMu.RLock()
|
||||
proxy := api.entitlements.Features[codersdk.FeatureWorkspaceProxy].Enabled
|
||||
api.entitlementsMu.RUnlock()
|
||||
if !proxy {
|
||||
httpapi.RouteNotFound(rw)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(rw, r)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
package coderd
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/coderd/audit"
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/httpapi"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// @Summary Create workspace proxy
|
||||
// @ID create-workspace-proxy
|
||||
// @Security CoderSessionToken
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Tags Templates
|
||||
// @Param request body codersdk.CreateWorkspaceProxyRequest true "Create workspace proxy request"
|
||||
// @Success 201 {object} codersdk.WorkspaceProxy
|
||||
// @Router /workspaceproxies [post]
|
||||
func (api *API) postWorkspaceProxy(rw http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
auditor = api.AGPL.Auditor.Load()
|
||||
aReq, commitAudit = audit.InitRequest[database.WorkspaceProxy](rw, &audit.RequestParams{
|
||||
Audit: *auditor,
|
||||
Log: api.Logger,
|
||||
Request: r,
|
||||
Action: database.AuditActionCreate,
|
||||
})
|
||||
)
|
||||
defer commitAudit()
|
||||
|
||||
var req codersdk.CreateWorkspaceProxyRequest
|
||||
if !httpapi.Read(ctx, rw, r, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateProxyURL(req.URL); err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "URL is invalid.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := httpapi.CompileHostnamePattern(req.WildcardHostname); err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Wildcard URL is invalid.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
proxy, err := api.Database.InsertWorkspaceProxy(ctx, database.InsertWorkspaceProxyParams{
|
||||
ID: uuid.New(),
|
||||
Name: req.Name,
|
||||
DisplayName: req.DisplayName,
|
||||
Icon: req.Icon,
|
||||
Url: req.URL,
|
||||
WildcardHostname: req.WildcardHostname,
|
||||
CreatedAt: database.Now(),
|
||||
UpdatedAt: database.Now(),
|
||||
})
|
||||
if database.IsUniqueViolation(err) {
|
||||
httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{
|
||||
Message: fmt.Sprintf("Workspace proxy with name %q already exists.", req.Name),
|
||||
})
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
aReq.New = proxy
|
||||
httpapi.Write(ctx, rw, http.StatusCreated, convertProxy(proxy))
|
||||
}
|
||||
|
||||
// nolint:revive
|
||||
func validateProxyURL(u string) error {
|
||||
p, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p.Scheme != "http" && p.Scheme != "https" {
|
||||
return xerrors.New("scheme must be http or https")
|
||||
}
|
||||
if !(p.Path == "/" || p.Path == "") {
|
||||
return xerrors.New("path must be empty or /")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// @Summary Get workspace proxies
|
||||
// @ID get-workspace-proxies
|
||||
// @Security CoderSessionToken
|
||||
// @Produce json
|
||||
// @Tags Enterprise
|
||||
// @Success 200 {array} codersdk.WorkspaceProxy
|
||||
// @Router /workspaceproxies [get]
|
||||
func (api *API) workspaceProxies(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
proxies, err := api.Database.GetWorkspaceProxies(ctx)
|
||||
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusOK, convertProxies(proxies))
|
||||
}
|
||||
|
||||
func convertProxies(p []database.WorkspaceProxy) []codersdk.WorkspaceProxy {
|
||||
resp := make([]codersdk.WorkspaceProxy, 0, len(p))
|
||||
for _, proxy := range p {
|
||||
resp = append(resp, convertProxy(proxy))
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func convertProxy(p database.WorkspaceProxy) codersdk.WorkspaceProxy {
|
||||
return codersdk.WorkspaceProxy{
|
||||
ID: p.ID,
|
||||
Name: p.Name,
|
||||
Icon: p.Icon,
|
||||
URL: p.Url,
|
||||
WildcardHostname: p.WildcardHostname,
|
||||
CreatedAt: p.CreatedAt,
|
||||
UpdatedAt: p.UpdatedAt,
|
||||
Deleted: p.Deleted,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package coderd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_validateProxyURL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testcases := []struct {
|
||||
Name string
|
||||
URL string
|
||||
ExpectedError bool
|
||||
}{
|
||||
{
|
||||
Name: "Empty",
|
||||
URL: "",
|
||||
ExpectedError: true,
|
||||
},
|
||||
{
|
||||
Name: "EmptyWild",
|
||||
URL: "",
|
||||
ExpectedError: true,
|
||||
},
|
||||
{
|
||||
Name: "URL",
|
||||
URL: "https://example.com",
|
||||
ExpectedError: false,
|
||||
},
|
||||
{
|
||||
Name: "NoScheme",
|
||||
URL: "example.com",
|
||||
ExpectedError: true,
|
||||
},
|
||||
{
|
||||
Name: "BadScheme",
|
||||
URL: "ssh://example.com",
|
||||
ExpectedError: true,
|
||||
},
|
||||
{
|
||||
Name: "IncludePaths",
|
||||
URL: "https://example.com/test",
|
||||
ExpectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testcases {
|
||||
tt := tt
|
||||
t.Run(tt.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := validateProxyURL(tt.URL)
|
||||
if tt.ExpectedError {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package coderd_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/moby/moby/pkg/namesgenerator"
|
||||
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/enterprise/coderd/coderdenttest"
|
||||
"github.com/coder/coder/enterprise/coderd/license"
|
||||
"github.com/coder/coder/testutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWorkspaceProxyCRUD(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("create", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.Experiments = []string{
|
||||
string(codersdk.ExperimentMoons),
|
||||
"*",
|
||||
}
|
||||
client := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
},
|
||||
})
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
_ = coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureWorkspaceProxy: 1,
|
||||
},
|
||||
})
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
proxy, err := client.CreateWorkspaceProxy(ctx, codersdk.CreateWorkspaceProxyRequest{
|
||||
Name: namesgenerator.GetRandomName(1),
|
||||
Icon: "/emojis/flag.png",
|
||||
URL: "https://" + namesgenerator.GetRandomName(1) + ".com",
|
||||
WildcardHostname: "*.sub.example.com",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
proxies, err := client.WorkspaceProxiesByOrganization(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, proxies, 1)
|
||||
require.Equal(t, proxy, proxies[0])
|
||||
})
|
||||
}
|
|
@ -253,6 +253,15 @@ export interface CreateWorkspaceBuildRequest {
|
|||
readonly log_level?: ProvisionerLogLevel
|
||||
}
|
||||
|
||||
// From codersdk/workspaceproxy.go
|
||||
export interface CreateWorkspaceProxyRequest {
|
||||
readonly name: string
|
||||
readonly display_name: string
|
||||
readonly icon: string
|
||||
readonly url: string
|
||||
readonly wildcard_hostname: string
|
||||
}
|
||||
|
||||
// From codersdk/organizations.go
|
||||
export interface CreateWorkspaceRequest {
|
||||
readonly template_id: string
|
||||
|
@ -1201,6 +1210,19 @@ export interface WorkspaceOptions {
|
|||
readonly include_deleted?: boolean
|
||||
}
|
||||
|
||||
// From codersdk/workspaceproxy.go
|
||||
export interface WorkspaceProxy {
|
||||
readonly id: string
|
||||
readonly organization_id: string
|
||||
readonly name: string
|
||||
readonly icon: string
|
||||
readonly url: string
|
||||
readonly wildcard_hostname: string
|
||||
readonly created_at: string
|
||||
readonly updated_at: string
|
||||
readonly deleted: boolean
|
||||
}
|
||||
|
||||
// From codersdk/workspaces.go
|
||||
export interface WorkspaceQuota {
|
||||
readonly credits_consumed: number
|
||||
|
@ -1280,8 +1302,8 @@ export const Entitlements: Entitlement[] = [
|
|||
]
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export type Experiment = "template_editor"
|
||||
export const Experiments: Experiment[] = ["template_editor"]
|
||||
export type Experiment = "moons" | "template_editor"
|
||||
export const Experiments: Experiment[] = ["moons", "template_editor"]
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export type FeatureName =
|
||||
|
@ -1295,6 +1317,7 @@ export type FeatureName =
|
|||
| "scim"
|
||||
| "template_rbac"
|
||||
| "user_limit"
|
||||
| "workspace_proxy"
|
||||
export const FeatureNames: FeatureName[] = [
|
||||
"advanced_template_scheduling",
|
||||
"appearance",
|
||||
|
@ -1306,6 +1329,7 @@ export const FeatureNames: FeatureName[] = [
|
|||
"scim",
|
||||
"template_rbac",
|
||||
"user_limit",
|
||||
"workspace_proxy",
|
||||
]
|
||||
|
||||
// From codersdk/workspaceagents.go
|
||||
|
|
Loading…
Reference in New Issue