chore: add organization member api + cli (#13577)

This commit is contained in:
Steven Masley
2024-06-20 04:19:24 -10:00
committed by GitHub
parent 4699adee5e
commit 8e06ad46d0
10 changed files with 335 additions and 6 deletions

41
coderd/apidoc/docs.go generated
View File

@ -2356,6 +2356,47 @@ const docTemplate = `{
}
}
},
"/organizations/{organization}/members/{user}": {
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Members"
],
"summary": "Add organization member",
"operationId": "add-organization-member",
"parameters": [
{
"type": "string",
"description": "Organization ID",
"name": "organization",
"in": "path",
"required": true
},
{
"type": "string",
"description": "User ID, name, or me",
"name": "user",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.OrganizationMember"
}
}
}
}
},
"/organizations/{organization}/members/{user}/roles": {
"put": {
"security": [

View File

@ -2062,6 +2062,43 @@
}
}
},
"/organizations/{organization}/members/{user}": {
"post": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": ["application/json"],
"tags": ["Members"],
"summary": "Add organization member",
"operationId": "add-organization-member",
"parameters": [
{
"type": "string",
"description": "Organization ID",
"name": "organization",
"in": "path",
"required": true
},
{
"type": "string",
"description": "User ID, name, or me",
"name": "user",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.OrganizationMember"
}
}
}
}
},
"/organizations/{organization}/members/{user}/roles": {
"put": {
"security": [

View File

@ -845,11 +845,24 @@ func New(options *Options) *API {
})
r.Route("/{user}", func(r chi.Router) {
r.Use(
httpmw.ExtractOrganizationMemberParam(options.Database),
)
r.Put("/roles", api.putMemberRoles)
r.Post("/workspaces", api.postWorkspacesByOrganization)
r.Group(func(r chi.Router) {
r.Use(
// Adding a member requires "read" permission
// on the site user. So limited to owners and user-admins.
// TODO: Allow org-admins to add users via some new permission? Or give them
// read on site users.
httpmw.ExtractUserParam(options.Database),
)
r.Post("/", api.postOrganizationMember)
})
r.Group(func(r chi.Router) {
r.Use(
httpmw.ExtractOrganizationMemberParam(options.Database),
)
r.Put("/roles", api.putMemberRoles)
r.Post("/workspaces", api.postWorkspacesByOrganization)
})
})
})
})

View File

@ -9,12 +9,59 @@ import (
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/db2sdk"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/coderd/httpmw"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/codersdk"
)
// @Summary Add organization member
// @ID add-organization-member
// @Security CoderSessionToken
// @Produce json
// @Tags Members
// @Param organization path string true "Organization ID"
// @Param user path string true "User ID, name, or me"
// @Success 200 {object} codersdk.OrganizationMember
// @Router /organizations/{organization}/members/{user} [post]
func (api *API) postOrganizationMember(rw http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
organization = httpmw.OrganizationParam(r)
user = httpmw.UserParam(r)
)
member, err := api.Database.InsertOrganizationMember(ctx, database.InsertOrganizationMemberParams{
OrganizationID: organization.ID,
UserID: user.ID,
CreatedAt: dbtime.Now(),
UpdatedAt: dbtime.Now(),
Roles: []string{},
})
if httpapi.Is404Error(err) {
httpapi.ResourceNotFound(rw)
return
}
if err != nil {
httpapi.InternalServerError(rw, err)
return
}
resp, err := convertOrganizationMembers(ctx, api.Database, []database.OrganizationMember{member})
if err != nil {
httpapi.InternalServerError(rw, err)
return
}
if len(resp) == 0 {
httpapi.InternalServerError(rw, xerrors.Errorf("marshal member"))
return
}
httpapi.Write(ctx, rw, http.StatusOK, resp[0])
}
// @Summary List organization members
// @ID list-organization-members
// @Security CoderSessionToken

View File

@ -13,6 +13,65 @@ import (
"github.com/coder/coder/v2/testutil"
)
func TestAddMember(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
owner := coderdtest.New(t, nil)
first := coderdtest.CreateFirstUser(t, owner)
ctx := testutil.Context(t, testutil.WaitMedium)
org, err := owner.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{
Name: "other",
DisplayName: "",
Description: "",
Icon: "",
})
require.NoError(t, err)
// Make a user not in the second organization
_, user := coderdtest.CreateAnotherUser(t, owner, first.OrganizationID)
members, err := owner.OrganizationMembers(ctx, org.ID)
require.NoError(t, err)
require.Len(t, members, 1) // Verify just the 1 member
// Add user to org
_, err = owner.PostOrganizationMember(ctx, org.ID, user.Username)
require.NoError(t, err)
members, err = owner.OrganizationMembers(ctx, org.ID)
require.NoError(t, err)
// Owner + new member
require.Len(t, members, 2)
require.ElementsMatch(t,
[]uuid.UUID{first.UserID, user.ID},
db2sdk.List(members, onlyIDs))
})
t.Run("UserNotExists", func(t *testing.T) {
t.Parallel()
owner := coderdtest.New(t, nil)
_ = coderdtest.CreateFirstUser(t, owner)
ctx := testutil.Context(t, testutil.WaitMedium)
org, err := owner.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{
Name: "other",
DisplayName: "",
Description: "",
Icon: "",
})
require.NoError(t, err)
// Add user to org
_, err = owner.PostOrganizationMember(ctx, org.ID, uuid.NewString())
require.Error(t, err)
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Contains(t, apiErr.Message, "must be an existing")
})
}
func TestListMembers(t *testing.T) {
t.Parallel()