Skip to content

Commit a71aa20

Browse files
authored
feat: filter users by github user id in the users list CLI command (coder#17029)
Add the `--github-user-id` option to `coder users list`, which makes the command only return users with a matching GitHub user id. This will enable https://github.com/coder/start-workspace-action to find a Coder user that corresponds to a GitHub user requesting to start a workspace.
1 parent 69ba27e commit a71aa20

File tree

11 files changed

+124
-30
lines changed

11 files changed

+124
-30
lines changed

cli/testdata/coder_users_list_--help.golden

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ OPTIONS:
99
-c, --column [id|username|email|created at|updated at|status] (default: username,email,created at,status)
1010
Columns to display in table output.
1111

12+
--github-user-id int
13+
Filter users by their GitHub user ID.
14+
1215
-o, --output table|json (default: table)
1316
Output format.
1417

cli/userlist.go

+17-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ func (r *RootCmd) userList() *serpent.Command {
1919
cliui.JSONFormat(),
2020
)
2121
client := new(codersdk.Client)
22+
var githubUserID int64
2223

2324
cmd := &serpent.Command{
2425
Use: "list",
@@ -27,8 +28,23 @@ func (r *RootCmd) userList() *serpent.Command {
2728
serpent.RequireNArgs(0),
2829
r.InitClient(client),
2930
),
31+
Options: serpent.OptionSet{
32+
{
33+
Name: "github-user-id",
34+
Description: "Filter users by their GitHub user ID.",
35+
Default: "",
36+
Flag: "github-user-id",
37+
Required: false,
38+
Value: serpent.Int64Of(&githubUserID),
39+
},
40+
},
3041
Handler: func(inv *serpent.Invocation) error {
31-
res, err := client.Users(inv.Context(), codersdk.UsersRequest{})
42+
req := codersdk.UsersRequest{}
43+
if githubUserID != 0 {
44+
req.Search = fmt.Sprintf("github_com_user_id:%d", githubUserID)
45+
}
46+
47+
res, err := client.Users(inv.Context(), req)
3248
if err != nil {
3349
return err
3450
}

coderd/database/dbmem/dbmem.go

+10
Original file line numberDiff line numberDiff line change
@@ -6578,6 +6578,16 @@ func (q *FakeQuerier) GetUsers(_ context.Context, params database.GetUsersParams
65786578
users = usersFilteredByLastSeen
65796579
}
65806580

6581+
if params.GithubComUserID != 0 {
6582+
usersFilteredByGithubComUserID := make([]database.User, 0, len(users))
6583+
for i, user := range users {
6584+
if user.GithubComUserID.Int64 == params.GithubComUserID {
6585+
usersFilteredByGithubComUserID = append(usersFilteredByGithubComUserID, users[i])
6586+
}
6587+
}
6588+
users = usersFilteredByGithubComUserID
6589+
}
6590+
65816591
beforePageCount := len(users)
65826592

65836593
if params.OffsetOpt > 0 {

coderd/database/modelqueries.go

+1
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,7 @@ func (q *sqlQuerier) GetAuthorizedUsers(ctx context.Context, arg GetUsersParams,
393393
arg.LastSeenAfter,
394394
arg.CreatedBefore,
395395
arg.CreatedAfter,
396+
arg.GithubComUserID,
396397
arg.OffsetOpt,
397398
arg.LimitOpt,
398399
)

coderd/database/queries.sql.go

+19-12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/users.sql

+5
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,11 @@ WHERE
223223
created_at >= @created_after
224224
ELSE true
225225
END
226+
AND CASE
227+
WHEN @github_com_user_id :: bigint != 0 THEN
228+
github_com_user_id = @github_com_user_id
229+
ELSE true
230+
END
226231
-- End of filters
227232

228233
-- Authorize Filter clause will be injected below in GetAuthorizedUsers

coderd/httpapi/queryparams.go

+14
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,20 @@ func (p *QueryParamParser) Int(vals url.Values, def int, queryParam string) int
8282
return v
8383
}
8484

85+
func (p *QueryParamParser) Int64(vals url.Values, def int64, queryParam string) int64 {
86+
v, err := parseQueryParam(p, vals, func(v string) (int64, error) {
87+
return strconv.ParseInt(v, 10, 64)
88+
}, def, queryParam)
89+
if err != nil {
90+
p.Errors = append(p.Errors, codersdk.ValidationError{
91+
Field: queryParam,
92+
Detail: fmt.Sprintf("Query param %q must be a valid 64-bit integer: %s", queryParam, err.Error()),
93+
})
94+
return 0
95+
}
96+
return v
97+
}
98+
8599
// PositiveInt32 function checks if the given value is 32-bit and positive.
86100
//
87101
// We can't use `uint32` as the value must be within the range <0,2147483647>

coderd/searchquery/search.go

+8-7
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,14 @@ func Users(query string) (database.GetUsersParams, []codersdk.ValidationError) {
8080

8181
parser := httpapi.NewQueryParamParser()
8282
filter := database.GetUsersParams{
83-
Search: parser.String(values, "", "search"),
84-
Status: httpapi.ParseCustomList(parser, values, []database.UserStatus{}, "status", httpapi.ParseEnum[database.UserStatus]),
85-
RbacRole: parser.Strings(values, []string{}, "role"),
86-
LastSeenAfter: parser.Time3339Nano(values, time.Time{}, "last_seen_after"),
87-
LastSeenBefore: parser.Time3339Nano(values, time.Time{}, "last_seen_before"),
88-
CreatedAfter: parser.Time3339Nano(values, time.Time{}, "created_after"),
89-
CreatedBefore: parser.Time3339Nano(values, time.Time{}, "created_before"),
83+
Search: parser.String(values, "", "search"),
84+
Status: httpapi.ParseCustomList(parser, values, []database.UserStatus{}, "status", httpapi.ParseEnum[database.UserStatus]),
85+
RbacRole: parser.Strings(values, []string{}, "role"),
86+
LastSeenAfter: parser.Time3339Nano(values, time.Time{}, "last_seen_after"),
87+
LastSeenBefore: parser.Time3339Nano(values, time.Time{}, "last_seen_before"),
88+
CreatedAfter: parser.Time3339Nano(values, time.Time{}, "created_after"),
89+
CreatedBefore: parser.Time3339Nano(values, time.Time{}, "created_before"),
90+
GithubComUserID: parser.Int64(values, 0, "github_com_user_id"),
9091
}
9192
parser.ErrorExcessParams(values)
9293
return filter, parser.Errors

coderd/users.go

+11-10
Original file line numberDiff line numberDiff line change
@@ -297,16 +297,17 @@ func (api *API) GetUsers(rw http.ResponseWriter, r *http.Request) ([]database.Us
297297
}
298298

299299
userRows, err := api.Database.GetUsers(ctx, database.GetUsersParams{
300-
AfterID: paginationParams.AfterID,
301-
Search: params.Search,
302-
Status: params.Status,
303-
RbacRole: params.RbacRole,
304-
LastSeenBefore: params.LastSeenBefore,
305-
LastSeenAfter: params.LastSeenAfter,
306-
CreatedAfter: params.CreatedAfter,
307-
CreatedBefore: params.CreatedBefore,
308-
OffsetOpt: int32(paginationParams.Offset),
309-
LimitOpt: int32(paginationParams.Limit),
300+
AfterID: paginationParams.AfterID,
301+
Search: params.Search,
302+
Status: params.Status,
303+
RbacRole: params.RbacRole,
304+
LastSeenBefore: params.LastSeenBefore,
305+
LastSeenAfter: params.LastSeenAfter,
306+
CreatedAfter: params.CreatedAfter,
307+
CreatedBefore: params.CreatedBefore,
308+
GithubComUserID: params.GithubComUserID,
309+
OffsetOpt: int32(paginationParams.Offset),
310+
LimitOpt: int32(paginationParams.Limit),
310311
})
311312
if err != nil {
312313
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{

coderd/users_test.go

+28
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package coderd_test
22

33
import (
44
"context"
5+
"database/sql"
56
"fmt"
67
"net/http"
78
"slices"
@@ -1873,6 +1874,33 @@ func TestGetUsers(t *testing.T) {
18731874
require.NoError(t, err)
18741875
require.ElementsMatch(t, active, res.Users)
18751876
})
1877+
t.Run("GithubComUserID", func(t *testing.T) {
1878+
t.Parallel()
1879+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
1880+
defer cancel()
1881+
1882+
client, db := coderdtest.NewWithDatabase(t, nil)
1883+
first := coderdtest.CreateFirstUser(t, client)
1884+
_ = dbgen.User(t, db, database.User{
1885+
Email: "test2@coder.com",
1886+
Username: "test2",
1887+
})
1888+
// nolint:gocritic // Unit test
1889+
err := db.UpdateUserGithubComUserID(dbauthz.AsSystemRestricted(ctx), database.UpdateUserGithubComUserIDParams{
1890+
ID: first.UserID,
1891+
GithubComUserID: sql.NullInt64{
1892+
Int64: 123,
1893+
Valid: true,
1894+
},
1895+
})
1896+
require.NoError(t, err)
1897+
res, err := client.Users(ctx, codersdk.UsersRequest{
1898+
SearchQuery: "github_com_user_id:123",
1899+
})
1900+
require.NoError(t, err)
1901+
require.Len(t, res.Users, 1)
1902+
require.Equal(t, res.Users[0].ID, first.UserID)
1903+
})
18761904
}
18771905

18781906
func TestGetUsersPagination(t *testing.T) {

docs/reference/cli/users_list.md

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)