Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
chore: Implement enterprise endpoints for moons
  • Loading branch information
Emyrk committed Mar 31, 2023
commit 5bd1392cab4fd75279f01e1ccbe03f636e056755
3 changes: 2 additions & 1 deletion coderd/audit/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 2 additions & 4 deletions coderd/database/dbauthz/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -1647,10 +1647,8 @@ 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, _ any) ([]database.WorkspaceProxy, error) {
return q.db.GetWorkspaceProxies(ctx)
})(ctx, nil)
func (q *querier) GetWorkspaceProxies(ctx context.Context, organizationID uuid.UUID) ([]database.WorkspaceProxy, error) {
return fetchWithPostFilter(q.auth, q.GetWorkspaceProxies)(ctx, organizationID)
}

func (q *querier) GetWorkspaceProxyByID(ctx context.Context, id uuid.UUID) (database.WorkspaceProxy, error) {
Expand Down
10 changes: 7 additions & 3 deletions coderd/database/dbfake/databasefake.go
Original file line number Diff line number Diff line change
Expand Up @@ -4896,12 +4896,16 @@ func (q *fakeQuerier) UpdateWorkspaceAgentStartupLogOverflowByID(_ context.Conte
return sql.ErrNoRows
}

func (q *fakeQuerier) GetWorkspaceProxies(ctx context.Context) ([]database.WorkspaceProxy, error) {
func (q *fakeQuerier) GetWorkspaceProxies(ctx context.Context, organizationID uuid.UUID) ([]database.WorkspaceProxy, error) {
q.mutex.Lock()
defer q.mutex.Unlock()

cpy := make([]database.WorkspaceProxy, len(q.workspaceProxies))
copy(cpy, q.workspaceProxies)
cpy := make([]database.WorkspaceProxy, 0, len(q.workspaceProxies))
for _, p := range q.workspaceProxies {
if p.OrganizationID == organizationID && !p.Deleted {
cpy = append(cpy, p)
}
}
return cpy, nil
}

Expand Down
2 changes: 1 addition & 1 deletion coderd/database/querier.go

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

5 changes: 3 additions & 2 deletions coderd/database/queries.sql.go

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

3 changes: 2 additions & 1 deletion coderd/database/queries/proxies.sql
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,5 @@ SELECT
FROM
workspace_proxies
WHERE
deleted = false;
deleted = false
AND organization_id = @organization_id;
4 changes: 4 additions & 0 deletions codersdk/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -1570,6 +1570,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"
)
Expand Down
28 changes: 28 additions & 0 deletions codersdk/workspaceproxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package codersdk

import (
"time"

"github.com/google/uuid"
)

type CreateWorkspaceProxyRequest struct {
Name string `json:"name"`
Icon string `json:"icon"`
URL string `json:"url"`
WildcardURL string `json:"wildcard_url"`
}

type WorkspaceProxy struct {
ID uuid.UUID `db:"id" json:"id"`
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
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"`
// URL with the wildcard for subdomain based app hosting: https://*.us.example.com
WildcardUrl string `db:"wildcard_url" json:"wildcard_url"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
Deleted bool `db:"deleted" json:"deleted"`
}
11 changes: 11 additions & 0 deletions enterprise/audit/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,17 @@ var auditableResourcesTypes = map[any]map[string]Action{
"exp": ActionTrack,
"uuid": ActionTrack,
},
&database.WorkspaceProxy{}: {
"id": ActionTrack,
"organization_id": ActionTrack,
"name": ActionTrack,
"icon": ActionTrack,
"url": ActionTrack,
"wildcard_url": ActionTrack,
"created_at": ActionTrack,
"updated_at": ActionTrack,
"deleted": ActionTrack,
},
}

// auditMap converts a map of struct pointers to a map of struct names as
Expand Down
17 changes: 17 additions & 0 deletions enterprise/coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,23 @@ func New(ctx context.Context, options *Options) (*API, error) {
r.Get("/", api.licenses)
r.Delete("/{id}", api.deleteLicense)
})
r.Route("/organizations/{organization}/workspaceproxys", func(r chi.Router) {
r.Use(
apiKeyMiddleware,
api.moonsEnabledMW,
httpmw.ExtractOrganizationParam(api.Database),
)
r.Post("/", api.postWorkspaceProxyByOrganization)
r.Get("/", api.workspaceProxiesByOrganization)
// 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,
Expand Down
10 changes: 10 additions & 0 deletions enterprise/coderd/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,3 +279,13 @@ 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) {
if !api.AGPL.Experiments.Enabled(codersdk.ExperimentMoons) {
httpapi.RouteNotFound(rw)
return
}
next.ServeHTTP(rw, r)
})
}
118 changes: 118 additions & 0 deletions enterprise/coderd/workspaceproxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package coderd

import (
"database/sql"
"fmt"
"net/http"

"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/coderd/httpmw"
"github.com/coder/coder/codersdk"
"github.com/google/uuid"
)

// @Summary Create workspace proxy for organization
// @ID create-workspace-proxy-for-organization
// @Security CoderSessionToken
// @Accept json
// @Produce json
// @Tags Templates
// @Param request body codersdk.CreateWorkspaceProxyRequest true "Create workspace proxy request"
// @Param organization path string true "Organization ID"
// @Success 201 {object} codersdk.WorkspaceProxy
// @Router /organizations/{organization}/workspaceproxys [post]
func (api *API) postWorkspaceProxyByOrganization(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
org = httpmw.OrganizationParam(r)
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
}

proxy, err := api.Database.InsertWorkspaceProxy(ctx, database.InsertWorkspaceProxyParams{
ID: uuid.New(),
OrganizationID: org.ID,
Name: req.Name,
Icon: req.Icon,
// TODO: validate URLs
Url: req.URL,
WildcardUrl: req.WildcardURL,
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))
}

// @Summary Get workspace proxies
// @ID get-workspace-proxies
// @Security CoderSessionToken
// @Produce json
// @Tags Enterprise
// @Param organization path string true "Organization ID" format(uuid)
// @Success 200 {array} codersdk.WorkspaceProxy
// @Router /organizations/{organization}/workspaceproxys [get]
func (api *API) workspaceProxiesByOrganization(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
org = httpmw.OrganizationParam(r)
)

proxies, err := api.Database.GetWorkspaceProxies(ctx, org.ID)
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,
OrganizationID: p.OrganizationID,
Name: p.Name,
Icon: p.Icon,
Url: p.Url,
WildcardUrl: p.WildcardUrl,
CreatedAt: p.CreatedAt,
UpdatedAt: p.UpdatedAt,
Deleted: p.Deleted,
}
}