Skip to content

Commit 64fd1f9

Browse files
authored
restructure command/api to use stable IDs (#2261)
1 parent 08bd4b9 commit 64fd1f9

29 files changed

+1947
-3658
lines changed

CHANGELOG.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,21 @@ This will also affect the way you [reference users in policies](https://github.c
8080
- Having usernames in magic DNS is no longer possible.
8181
- Remove versions older than 1.56 [#2149](https://github.com/juanfont/headscale/pull/2149)
8282
- Clean up old code required by old versions
83+
- User gRPC/API [#2261](https://github.com/juanfont/headscale/pull/2261):
84+
- If you depend on a Headscale Web UI, you should wait with this update until the UI have been updated to match the new API.
85+
- `GET /api/v1/user/{name}` and `GetUser` have been removed in favour of `ListUsers` with and ID parameter
86+
- `RenameUser` and `DeleteUser` now requires and ID instead of a name.
8387

8488
### Changes
8589

86-
- Improved compatibilty of built-in DERP server with clients connecting over WebSocket.
90+
- Improved compatibilty of built-in DERP server with clients connecting over WebSocket [#2132](https://github.com/juanfont/headscale/pull/2132)
8791
- Allow nodes to use SSH agent forwarding [#2145](https://github.com/juanfont/headscale/pull/2145)
8892
- Fixed processing of fields in post request in MoveNode rpc [#2179](https://github.com/juanfont/headscale/pull/2179)
8993
- Added conversion of 'Hostname' to 'givenName' in a node with FQDN rules applied [#2198](https://github.com/juanfont/headscale/pull/2198)
9094
- Fixed updating of hostname and givenName when it is updated in HostInfo [#2199](https://github.com/juanfont/headscale/pull/2199)
9195
- Fixed missing `stable-debug` container tag [#2232](https://github.com/juanfont/headscale/pr/2232)
92-
- Loosened up `server_url` and `base_domain` check. It was overly strict in some cases.
96+
- Loosened up `server_url` and `base_domain` check. It was overly strict in some cases. [#2248](https://github.com/juanfont/headscale/pull/2248)
97+
- CLI for managing users now accepts `--identifier` in addition to `--name`, usage of `--identifier` is recommended [#2261](https://github.com/juanfont/headscale/pull/2261)
9398

9499
## 0.23.0 (2024-09-18)
95100

Makefile

+8-2
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,16 @@ test_integration:
3838
lint:
3939
golangci-lint run --fix --timeout 10m
4040

41-
fmt:
41+
fmt: fmt-go fmt-prettier fmt-proto
42+
43+
fmt-prettier:
4244
prettier --write '**/**.{ts,js,md,yaml,yml,sass,css,scss,html}'
45+
46+
fmt-go:
4347
golines --max-len=88 --base-formatter=gofumpt -w $(GO_SOURCES)
44-
clang-format -style="{BasedOnStyle: Google, IndentWidth: 4, AlignConsecutiveDeclarations: true, AlignConsecutiveAssignments: true, ColumnLimit: 0}" -i $(PROTO_SOURCES)
48+
49+
fmt-proto:
50+
clang-format -i $(PROTO_SOURCES)
4551

4652
proto-lint:
4753
cd proto/ && go run github.com/bufbuild/buf/cmd/buf lint

cmd/headscale/cli/users.go

+100-30
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,43 @@ import (
1212
"google.golang.org/grpc/status"
1313
)
1414

15+
func usernameAndIDFlag(cmd *cobra.Command) {
16+
cmd.Flags().Int64P("identifier", "i", -1, "User identifier (ID)")
17+
cmd.Flags().StringP("name", "n", "", "Username")
18+
}
19+
20+
// usernameAndIDFromFlag returns the username and ID from the flags of the command.
21+
// If both are empty, it will exit the program with an error.
22+
func usernameAndIDFromFlag(cmd *cobra.Command) (uint64, string) {
23+
username, _ := cmd.Flags().GetString("name")
24+
identifier, _ := cmd.Flags().GetInt64("identifier")
25+
if username == "" && identifier < 0 {
26+
err := errors.New("--name or --identifier flag is required")
27+
ErrorOutput(
28+
err,
29+
fmt.Sprintf(
30+
"Cannot rename user: %s",
31+
status.Convert(err).Message(),
32+
),
33+
"",
34+
)
35+
}
36+
37+
return uint64(identifier), username
38+
}
39+
1540
func init() {
1641
rootCmd.AddCommand(userCmd)
1742
userCmd.AddCommand(createUserCmd)
1843
userCmd.AddCommand(listUsersCmd)
44+
usernameAndIDFlag(listUsersCmd)
45+
listUsersCmd.Flags().StringP("email", "e", "", "Email")
1946
userCmd.AddCommand(destroyUserCmd)
47+
usernameAndIDFlag(destroyUserCmd)
2048
userCmd.AddCommand(renameUserCmd)
49+
usernameAndIDFlag(renameUserCmd)
50+
renameUserCmd.Flags().StringP("new-name", "r", "", "New username")
51+
renameNodeCmd.MarkFlagRequired("new-name")
2152
}
2253

2354
var errMissingParameter = errors.New("missing parameters")
@@ -70,30 +101,23 @@ var createUserCmd = &cobra.Command{
70101
}
71102

72103
var destroyUserCmd = &cobra.Command{
73-
Use: "destroy NAME",
104+
Use: "destroy --identifier ID or --name NAME",
74105
Short: "Destroys a user",
75106
Aliases: []string{"delete"},
76-
Args: func(cmd *cobra.Command, args []string) error {
77-
if len(args) < 1 {
78-
return errMissingParameter
79-
}
80-
81-
return nil
82-
},
83107
Run: func(cmd *cobra.Command, args []string) {
84108
output, _ := cmd.Flags().GetString("output")
85109

86-
userName := args[0]
87-
88-
request := &v1.GetUserRequest{
89-
Name: userName,
110+
id, username := usernameAndIDFromFlag(cmd)
111+
request := &v1.ListUsersRequest{
112+
Name: username,
113+
Id: id,
90114
}
91115

92116
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
93117
defer cancel()
94118
defer conn.Close()
95119

96-
_, err := client.GetUser(ctx, request)
120+
users, err := client.ListUsers(ctx, request)
97121
if err != nil {
98122
ErrorOutput(
99123
err,
@@ -102,13 +126,24 @@ var destroyUserCmd = &cobra.Command{
102126
)
103127
}
104128

129+
if len(users.GetUsers()) != 1 {
130+
err := fmt.Errorf("Unable to determine user to delete, query returned multiple users, use ID")
131+
ErrorOutput(
132+
err,
133+
fmt.Sprintf("Error: %s", status.Convert(err).Message()),
134+
output,
135+
)
136+
}
137+
138+
user := users.GetUsers()[0]
139+
105140
confirm := false
106141
force, _ := cmd.Flags().GetBool("force")
107142
if !force {
108143
prompt := &survey.Confirm{
109144
Message: fmt.Sprintf(
110-
"Do you want to remove the user '%s' and any associated preauthkeys?",
111-
userName,
145+
"Do you want to remove the user %q (%d) and any associated preauthkeys?",
146+
user.GetName(), user.GetId(),
112147
),
113148
}
114149
err := survey.AskOne(prompt, &confirm)
@@ -118,7 +153,7 @@ var destroyUserCmd = &cobra.Command{
118153
}
119154

120155
if confirm || force {
121-
request := &v1.DeleteUserRequest{Name: userName}
156+
request := &v1.DeleteUserRequest{Id: user.GetId()}
122157

123158
response, err := client.DeleteUser(ctx, request)
124159
if err != nil {
@@ -151,6 +186,23 @@ var listUsersCmd = &cobra.Command{
151186

152187
request := &v1.ListUsersRequest{}
153188

189+
id, _ := cmd.Flags().GetInt64("identifier")
190+
username, _ := cmd.Flags().GetString("name")
191+
email, _ := cmd.Flags().GetString("email")
192+
193+
// filter by one param at most
194+
switch {
195+
case id > 0:
196+
request.Id = uint64(id)
197+
break
198+
case username != "":
199+
request.Name = username
200+
break
201+
case email != "":
202+
request.Email = email
203+
break
204+
}
205+
154206
response, err := client.ListUsers(ctx, request)
155207
if err != nil {
156208
ErrorOutput(
@@ -169,7 +221,7 @@ var listUsersCmd = &cobra.Command{
169221
tableData = append(
170222
tableData,
171223
[]string{
172-
user.GetId(),
224+
fmt.Sprintf("%d", user.GetId()),
173225
user.GetDisplayName(),
174226
user.GetName(),
175227
user.GetEmail(),
@@ -189,30 +241,48 @@ var listUsersCmd = &cobra.Command{
189241
}
190242

191243
var renameUserCmd = &cobra.Command{
192-
Use: "rename OLD_NAME NEW_NAME",
244+
Use: "rename",
193245
Short: "Renames a user",
194246
Aliases: []string{"mv"},
195-
Args: func(cmd *cobra.Command, args []string) error {
196-
expectedArguments := 2
197-
if len(args) < expectedArguments {
198-
return errMissingParameter
199-
}
200-
201-
return nil
202-
},
203247
Run: func(cmd *cobra.Command, args []string) {
204248
output, _ := cmd.Flags().GetString("output")
205249

206250
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
207251
defer cancel()
208252
defer conn.Close()
209253

210-
request := &v1.RenameUserRequest{
211-
OldName: args[0],
212-
NewName: args[1],
254+
id, username := usernameAndIDFromFlag(cmd)
255+
listReq := &v1.ListUsersRequest{
256+
Name: username,
257+
Id: id,
258+
}
259+
260+
users, err := client.ListUsers(ctx, listReq)
261+
if err != nil {
262+
ErrorOutput(
263+
err,
264+
fmt.Sprintf("Error: %s", status.Convert(err).Message()),
265+
output,
266+
)
267+
}
268+
269+
if len(users.GetUsers()) != 1 {
270+
err := fmt.Errorf("Unable to determine user to delete, query returned multiple users, use ID")
271+
ErrorOutput(
272+
err,
273+
fmt.Sprintf("Error: %s", status.Convert(err).Message()),
274+
output,
275+
)
276+
}
277+
278+
newName, _ := cmd.Flags().GetString("new-name")
279+
280+
renameReq := &v1.RenameUserRequest{
281+
OldId: id,
282+
NewName: newName,
213283
}
214284

215-
response, err := client.RenameUser(ctx, request)
285+
response, err := client.RenameUser(ctx, renameReq)
216286
if err != nil {
217287
ErrorOutput(
218288
err,

flake.nix

+21-4
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,38 @@
4141

4242
protoc-gen-grpc-gateway = buildGo rec {
4343
pname = "grpc-gateway";
44-
version = "2.22.0";
44+
version = "2.24.0";
4545

4646
src = pkgs.fetchFromGitHub {
4747
owner = "grpc-ecosystem";
4848
repo = "grpc-gateway";
4949
rev = "v${version}";
50-
sha256 = "sha256-I1w3gfV06J8xG1xJ+XuMIGkV2/Ofszo7SCC+z4Xb6l4=";
50+
sha256 = "sha256-lUEoqXJF1k4/il9bdDTinkUV5L869njZNYqObG/mHyA=";
5151
};
5252

53-
vendorHash = "sha256-S4hcD5/BSGxM2qdJHMxOkxsJ5+Ks6m4lKHSS9+yZ17c=";
53+
vendorHash = "sha256-Ttt7bPKU+TMKRg5550BS6fsPwYp0QJqcZ7NLrhttSdw=";
5454

5555
nativeBuildInputs = [pkgs.installShellFiles];
5656

5757
subPackages = ["protoc-gen-grpc-gateway" "protoc-gen-openapiv2"];
5858
};
5959

60+
protobuf-language-server = buildGo rec {
61+
pname = "protobuf-language-server";
62+
version = "2546944";
63+
64+
src = pkgs.fetchFromGitHub {
65+
owner = "lasorda";
66+
repo = "protobuf-language-server";
67+
rev = "${version}";
68+
sha256 = "sha256-Cbr3ktT86RnwUntOiDKRpNTClhdyrKLTQG2ZEd6fKDc=";
69+
};
70+
71+
vendorHash = "sha256-PfT90dhfzJZabzLTb1D69JCO+kOh2khrlpF5mCDeypk=";
72+
73+
subPackages = ["."];
74+
};
75+
6076
# Upstream does not override buildGoModule properly,
6177
# importing a specific module, so comment out for now.
6278
# golangci-lint = prev.golangci-lint.override {
@@ -115,6 +131,7 @@
115131
protoc-gen-grpc-gateway
116132
buf
117133
clang-tools # clang-format
134+
protobuf-language-server
118135
];
119136

120137
# Add entry to build a docker image with headscale
@@ -191,7 +208,7 @@
191208
${pkgs.golangci-lint}/bin/golangci-lint run --fix --timeout 10m
192209
${pkgs.nodePackages.prettier}/bin/prettier --write '**/**.{ts,js,md,yaml,yml,sass,css,scss,html}'
193210
${pkgs.golines}/bin/golines --max-len=88 --base-formatter=gofumpt -w ${./.}
194-
${pkgs.clang-tools}/bin/clang-format -style="{BasedOnStyle: Google, IndentWidth: 4, AlignConsecutiveDeclarations: true, AlignConsecutiveAssignments: true, ColumnLimit: 0}" -i ${./.}
211+
${pkgs.clang-tools}/bin/clang-format -i ${./.}
195212
'';
196213
};
197214
});

0 commit comments

Comments
 (0)