Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit8e06ad4

Browse files
authored
chore: add organization member api + cli (#13577)
1 parent4699ade commit8e06ad4

File tree

10 files changed

+335
-6
lines changed

10 files changed

+335
-6
lines changed

‎cli/organization.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ func (r *RootCmd) organizations() *serpent.Command {
2929
r.currentOrganization(),
3030
r.switchOrganization(),
3131
r.createOrganization(),
32-
r.organizationRoles(),
3332
r.organizationMembers(),
33+
r.organizationRoles(),
3434
},
3535
}
3636

‎cli/organizationmembers.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ func (r *RootCmd) organizationMembers() *serpent.Command {
1919
Children: []*serpent.Command{
2020
r.listOrganizationMembers(),
2121
r.assignOrganizationRoles(),
22+
r.addOrganizationMember(),
2223
},
2324
Handler:func(inv*serpent.Invocation)error {
2425
returninv.Command.HelpHandler(inv)
@@ -28,6 +29,37 @@ func (r *RootCmd) organizationMembers() *serpent.Command {
2829
returncmd
2930
}
3031

32+
func (r*RootCmd)addOrganizationMember()*serpent.Command {
33+
client:=new(codersdk.Client)
34+
35+
cmd:=&serpent.Command{
36+
Use:"add <username | user_id>",
37+
Short:"Add a new member to the current organization",
38+
Middleware:serpent.Chain(
39+
r.InitClient(client),
40+
serpent.RequireNArgs(1),
41+
),
42+
Handler:func(inv*serpent.Invocation)error {
43+
ctx:=inv.Context()
44+
organization,err:=CurrentOrganization(r,inv,client)
45+
iferr!=nil {
46+
returnerr
47+
}
48+
user:=inv.Args[0]
49+
50+
_,err=client.PostOrganizationMember(ctx,organization.ID,user)
51+
iferr!=nil {
52+
returnxerrors.Errorf("could not add member to organization: %w",err)
53+
}
54+
55+
_,_=fmt.Fprintln(inv.Stdout,"Organization member added")
56+
returnnil
57+
},
58+
}
59+
60+
returncmd
61+
}
62+
3163
func (r*RootCmd)assignOrganizationRoles()*serpent.Command {
3264
client:=new(codersdk.Client)
3365

‎cli/organizationmembers_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/coder/coder/v2/cli/clitest"
1010
"github.com/coder/coder/v2/coderd/coderdtest"
1111
"github.com/coder/coder/v2/coderd/rbac"
12+
"github.com/coder/coder/v2/codersdk"
1213
"github.com/coder/coder/v2/testutil"
1314
)
1415

@@ -34,3 +35,40 @@ func TestListOrganizationMembers(t *testing.T) {
3435
require.Contains(t,buf.String(),owner.UserID.String())
3536
})
3637
}
38+
39+
funcTestAddOrganizationMembers(t*testing.T) {
40+
t.Parallel()
41+
42+
t.Run("OK",func(t*testing.T) {
43+
t.Parallel()
44+
45+
ownerClient:=coderdtest.New(t,&coderdtest.Options{})
46+
owner:=coderdtest.CreateFirstUser(t,ownerClient)
47+
_,user:=coderdtest.CreateAnotherUser(t,ownerClient,owner.OrganizationID)
48+
49+
ctx:=testutil.Context(t,testutil.WaitMedium)
50+
//nolint:gocritic // must be an owner, only owners can create orgs
51+
otherOrg,err:=ownerClient.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{
52+
Name:"Other",
53+
DisplayName:"",
54+
Description:"",
55+
Icon:"",
56+
})
57+
require.NoError(t,err,"create another organization")
58+
59+
inv,root:=clitest.New(t,"organization","members","add","--organization",otherOrg.ID.String(),user.Username)
60+
//nolint:gocritic // must be an owner
61+
clitest.SetupConfig(t,ownerClient,root)
62+
63+
buf:=new(bytes.Buffer)
64+
inv.Stdout=buf
65+
err=inv.WithContext(ctx).Run()
66+
require.NoError(t,err)
67+
68+
//nolint:gocritic // must be an owner
69+
members,err:=ownerClient.OrganizationMembers(ctx,otherOrg.ID)
70+
require.NoError(t,err)
71+
72+
require.Len(t,members,2)
73+
})
74+
}

‎coderd/apidoc/docs.go

Lines changed: 41 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/apidoc/swagger.json

Lines changed: 37 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/coderd.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -845,11 +845,24 @@ func New(options *Options) *API {
845845
})
846846

847847
r.Route("/{user}",func(r chi.Router) {
848-
r.Use(
849-
httpmw.ExtractOrganizationMemberParam(options.Database),
850-
)
851-
r.Put("/roles",api.putMemberRoles)
852-
r.Post("/workspaces",api.postWorkspacesByOrganization)
848+
r.Group(func(r chi.Router) {
849+
r.Use(
850+
// Adding a member requires "read" permission
851+
// on the site user. So limited to owners and user-admins.
852+
// TODO: Allow org-admins to add users via some new permission? Or give them
853+
// read on site users.
854+
httpmw.ExtractUserParam(options.Database),
855+
)
856+
r.Post("/",api.postOrganizationMember)
857+
})
858+
859+
r.Group(func(r chi.Router) {
860+
r.Use(
861+
httpmw.ExtractOrganizationMemberParam(options.Database),
862+
)
863+
r.Put("/roles",api.putMemberRoles)
864+
r.Post("/workspaces",api.postWorkspacesByOrganization)
865+
})
853866
})
854867
})
855868
})

‎coderd/members.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,59 @@ import (
99

1010
"github.com/coder/coder/v2/coderd/database"
1111
"github.com/coder/coder/v2/coderd/database/db2sdk"
12+
"github.com/coder/coder/v2/coderd/database/dbtime"
1213
"github.com/coder/coder/v2/coderd/httpapi"
1314
"github.com/coder/coder/v2/coderd/httpmw"
1415
"github.com/coder/coder/v2/coderd/rbac"
1516
"github.com/coder/coder/v2/codersdk"
1617
)
1718

19+
// @Summary Add organization member
20+
// @ID add-organization-member
21+
// @Security CoderSessionToken
22+
// @Produce json
23+
// @Tags Members
24+
// @Param organization path string true "Organization ID"
25+
// @Param user path string true "User ID, name, or me"
26+
// @Success 200 {object} codersdk.OrganizationMember
27+
// @Router /organizations/{organization}/members/{user} [post]
28+
func (api*API)postOrganizationMember(rw http.ResponseWriter,r*http.Request) {
29+
var (
30+
ctx=r.Context()
31+
organization=httpmw.OrganizationParam(r)
32+
user=httpmw.UserParam(r)
33+
)
34+
35+
member,err:=api.Database.InsertOrganizationMember(ctx, database.InsertOrganizationMemberParams{
36+
OrganizationID:organization.ID,
37+
UserID:user.ID,
38+
CreatedAt:dbtime.Now(),
39+
UpdatedAt:dbtime.Now(),
40+
Roles: []string{},
41+
})
42+
ifhttpapi.Is404Error(err) {
43+
httpapi.ResourceNotFound(rw)
44+
return
45+
}
46+
iferr!=nil {
47+
httpapi.InternalServerError(rw,err)
48+
return
49+
}
50+
51+
resp,err:=convertOrganizationMembers(ctx,api.Database, []database.OrganizationMember{member})
52+
iferr!=nil {
53+
httpapi.InternalServerError(rw,err)
54+
return
55+
}
56+
57+
iflen(resp)==0 {
58+
httpapi.InternalServerError(rw,xerrors.Errorf("marshal member"))
59+
return
60+
}
61+
62+
httpapi.Write(ctx,rw,http.StatusOK,resp[0])
63+
}
64+
1865
// @Summary List organization members
1966
// @ID list-organization-members
2067
// @Security CoderSessionToken

‎coderd/members_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,65 @@ import (
1313
"github.com/coder/coder/v2/testutil"
1414
)
1515

16+
funcTestAddMember(t*testing.T) {
17+
t.Parallel()
18+
19+
t.Run("OK",func(t*testing.T) {
20+
t.Parallel()
21+
owner:=coderdtest.New(t,nil)
22+
first:=coderdtest.CreateFirstUser(t,owner)
23+
ctx:=testutil.Context(t,testutil.WaitMedium)
24+
org,err:=owner.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{
25+
Name:"other",
26+
DisplayName:"",
27+
Description:"",
28+
Icon:"",
29+
})
30+
require.NoError(t,err)
31+
32+
// Make a user not in the second organization
33+
_,user:=coderdtest.CreateAnotherUser(t,owner,first.OrganizationID)
34+
35+
members,err:=owner.OrganizationMembers(ctx,org.ID)
36+
require.NoError(t,err)
37+
require.Len(t,members,1)// Verify just the 1 member
38+
39+
// Add user to org
40+
_,err=owner.PostOrganizationMember(ctx,org.ID,user.Username)
41+
require.NoError(t,err)
42+
43+
members,err=owner.OrganizationMembers(ctx,org.ID)
44+
require.NoError(t,err)
45+
// Owner + new member
46+
require.Len(t,members,2)
47+
require.ElementsMatch(t,
48+
[]uuid.UUID{first.UserID,user.ID},
49+
db2sdk.List(members,onlyIDs))
50+
})
51+
52+
t.Run("UserNotExists",func(t*testing.T) {
53+
t.Parallel()
54+
owner:=coderdtest.New(t,nil)
55+
_=coderdtest.CreateFirstUser(t,owner)
56+
ctx:=testutil.Context(t,testutil.WaitMedium)
57+
58+
org,err:=owner.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{
59+
Name:"other",
60+
DisplayName:"",
61+
Description:"",
62+
Icon:"",
63+
})
64+
require.NoError(t,err)
65+
66+
// Add user to org
67+
_,err=owner.PostOrganizationMember(ctx,org.ID,uuid.NewString())
68+
require.Error(t,err)
69+
varapiErr*codersdk.Error
70+
require.ErrorAs(t,err,&apiErr)
71+
require.Contains(t,apiErr.Message,"must be an existing")
72+
})
73+
}
74+
1675
funcTestListMembers(t*testing.T) {
1776
t.Parallel()
1877

‎codersdk/users.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,20 @@ func (c *Client) UpdateUserPassword(ctx context.Context, user string, req Update
379379
returnnil
380380
}
381381

382+
// PostOrganizationMember adds a user to an organization
383+
func (c*Client)PostOrganizationMember(ctx context.Context,organizationID uuid.UUID,userstring) (OrganizationMember,error) {
384+
res,err:=c.Request(ctx,http.MethodPost,fmt.Sprintf("/api/v2/organizations/%s/members/%s",organizationID,user),nil)
385+
iferr!=nil {
386+
returnOrganizationMember{},err
387+
}
388+
deferres.Body.Close()
389+
ifres.StatusCode!=http.StatusOK {
390+
returnOrganizationMember{},ReadBodyAsError(res)
391+
}
392+
varmemberOrganizationMember
393+
returnmember,json.NewDecoder(res.Body).Decode(&member)
394+
}
395+
382396
// OrganizationMembers lists all members in an organization
383397
func (c*Client)OrganizationMembers(ctx context.Context,organizationID uuid.UUID) ([]OrganizationMemberWithName,error) {
384398
res,err:=c.Request(ctx,http.MethodGet,fmt.Sprintf("/api/v2/organizations/%s/members/",organizationID),nil)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp