mirror of
https://github.com/coder/coder.git
synced 2025-07-09 11:45:56 +00:00
feat: add template RBAC/groups (#4235)
This commit is contained in:
@ -51,3 +51,8 @@ func IsConnectionErr(err error) bool {
|
||||
|
||||
return xerrors.As(err, &dnsErr) || xerrors.As(err, &opErr)
|
||||
}
|
||||
|
||||
func AsError(err error) (*Error, bool) {
|
||||
var e *Error
|
||||
return e, xerrors.As(err, &e)
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ const (
|
||||
FeatureBrowserOnly = "browser_only"
|
||||
FeatureSCIM = "scim"
|
||||
FeatureWorkspaceQuota = "workspace_quota"
|
||||
FeatureRBAC = "rbac"
|
||||
)
|
||||
|
||||
var FeatureNames = []string{
|
||||
@ -28,6 +29,7 @@ var FeatureNames = []string{
|
||||
FeatureBrowserOnly,
|
||||
FeatureSCIM,
|
||||
FeatureWorkspaceQuota,
|
||||
FeatureRBAC,
|
||||
}
|
||||
|
||||
type Feature struct {
|
||||
|
113
codersdk/groups.go
Normal file
113
codersdk/groups.go
Normal file
@ -0,0 +1,113 @@
|
||||
package codersdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
type CreateGroupRequest struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type Group struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
OrganizationID uuid.UUID `json:"organization_id"`
|
||||
Members []User `json:"members"`
|
||||
}
|
||||
|
||||
func (c *Client) CreateGroup(ctx context.Context, orgID uuid.UUID, req CreateGroupRequest) (Group, error) {
|
||||
res, err := c.Request(ctx, http.MethodPost,
|
||||
fmt.Sprintf("/api/v2/organizations/%s/groups", orgID.String()),
|
||||
req,
|
||||
)
|
||||
if err != nil {
|
||||
return Group{}, xerrors.Errorf("make request: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusCreated {
|
||||
return Group{}, readBodyAsError(res)
|
||||
}
|
||||
var resp Group
|
||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
func (c *Client) GroupsByOrganization(ctx context.Context, orgID uuid.UUID) ([]Group, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet,
|
||||
fmt.Sprintf("/api/v2/organizations/%s/groups", orgID.String()),
|
||||
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 groups []Group
|
||||
return groups, json.NewDecoder(res.Body).Decode(&groups)
|
||||
}
|
||||
|
||||
func (c *Client) Group(ctx context.Context, group uuid.UUID) (Group, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet,
|
||||
fmt.Sprintf("/api/v2/groups/%s", group.String()),
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return Group{}, xerrors.Errorf("make request: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return Group{}, readBodyAsError(res)
|
||||
}
|
||||
var resp Group
|
||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
type PatchGroupRequest struct {
|
||||
AddUsers []string `json:"add_users"`
|
||||
RemoveUsers []string `json:"remove_users"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func (c *Client) PatchGroup(ctx context.Context, group uuid.UUID, req PatchGroupRequest) (Group, error) {
|
||||
res, err := c.Request(ctx, http.MethodPatch,
|
||||
fmt.Sprintf("/api/v2/groups/%s", group.String()),
|
||||
req,
|
||||
)
|
||||
if err != nil {
|
||||
return Group{}, xerrors.Errorf("make request: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return Group{}, readBodyAsError(res)
|
||||
}
|
||||
var resp Group
|
||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
func (c *Client) DeleteGroup(ctx context.Context, group uuid.UUID) error {
|
||||
res, err := c.Request(ctx, http.MethodDelete,
|
||||
fmt.Sprintf("/api/v2/groups/%s", group.String()),
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("make request: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return readBodyAsError(res)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -36,6 +36,34 @@ type UpdateActiveTemplateVersion struct {
|
||||
ID uuid.UUID `json:"id" validate:"required"`
|
||||
}
|
||||
|
||||
type TemplateRole string
|
||||
|
||||
const (
|
||||
TemplateRoleAdmin TemplateRole = "admin"
|
||||
TemplateRoleView TemplateRole = "view"
|
||||
TemplateRoleDeleted TemplateRole = ""
|
||||
)
|
||||
|
||||
type TemplateACL struct {
|
||||
Users []TemplateUser `json:"users"`
|
||||
Groups []TemplateGroup `json:"group"`
|
||||
}
|
||||
|
||||
type TemplateGroup struct {
|
||||
Group
|
||||
Role TemplateRole `json:"role"`
|
||||
}
|
||||
|
||||
type TemplateUser struct {
|
||||
User
|
||||
Role TemplateRole `json:"role"`
|
||||
}
|
||||
|
||||
type UpdateTemplateACL struct {
|
||||
UserPerms map[string]TemplateRole `json:"user_perms,omitempty"`
|
||||
GroupPerms map[string]TemplateRole `json:"group_perms,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateTemplateMeta struct {
|
||||
Name string `json:"name,omitempty" validate:"omitempty,username"`
|
||||
Description string `json:"description,omitempty"`
|
||||
@ -86,6 +114,31 @@ func (c *Client) UpdateTemplateMeta(ctx context.Context, templateID uuid.UUID, r
|
||||
return updated, json.NewDecoder(res.Body).Decode(&updated)
|
||||
}
|
||||
|
||||
func (c *Client) UpdateTemplateACL(ctx context.Context, templateID uuid.UUID, req UpdateTemplateACL) error {
|
||||
res, err := c.Request(ctx, http.MethodPatch, fmt.Sprintf("/api/v2/templates/%s/acl", templateID), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return readBodyAsError(res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) TemplateACL(ctx context.Context, templateID uuid.UUID) (TemplateACL, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templates/%s/acl", templateID), nil)
|
||||
if err != nil {
|
||||
return TemplateACL{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return TemplateACL{}, readBodyAsError(res)
|
||||
}
|
||||
var acl TemplateACL
|
||||
return acl, json.NewDecoder(res.Body).Decode(&acl)
|
||||
}
|
||||
|
||||
// UpdateActiveTemplateVersion updates the active template version to the ID provided.
|
||||
// The template version must be attached to the template.
|
||||
func (c *Client) UpdateActiveTemplateVersion(ctx context.Context, template uuid.UUID, req UpdateActiveTemplateVersion) error {
|
||||
|
Reference in New Issue
Block a user