Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

restructure command/api to use stable IDs #2261

Merged
merged 10 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,21 @@ This will also affect the way you [reference users in policies](https://github.c
- Having usernames in magic DNS is no longer possible.
- Remove versions older than 1.56 [#2149](https://github.com/juanfont/headscale/pull/2149)
- Clean up old code required by old versions
- User gRPC/API [#2261](https://github.com/juanfont/headscale/pull/2261):
- 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.
- `GET /api/v1/user/{name}` and `GetUser` have been removed in favour of `ListUsers` with and ID parameter
- `RenameUser` and `DeleteUser` now requires and ID instead of a name.

### Changes

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

## 0.23.0 (2024-09-18)

Expand Down
10 changes: 8 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,16 @@ test_integration:
lint:
golangci-lint run --fix --timeout 10m

fmt:
fmt: fmt-go fmt-prettier fmt-proto

fmt-prettier:
prettier --write '**/**.{ts,js,md,yaml,yml,sass,css,scss,html}'

fmt-go:
golines --max-len=88 --base-formatter=gofumpt -w $(GO_SOURCES)
clang-format -style="{BasedOnStyle: Google, IndentWidth: 4, AlignConsecutiveDeclarations: true, AlignConsecutiveAssignments: true, ColumnLimit: 0}" -i $(PROTO_SOURCES)

fmt-proto:
clang-format -i $(PROTO_SOURCES)

proto-lint:
cd proto/ && go run github.com/bufbuild/buf/cmd/buf lint
Expand Down
130 changes: 100 additions & 30 deletions cmd/headscale/cli/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,43 @@ import (
"google.golang.org/grpc/status"
)

func usernameAndIDFlag(cmd *cobra.Command) {
cmd.Flags().Int64P("identifier", "i", -1, "User identifier (ID)")
cmd.Flags().StringP("name", "n", "", "Username")
}

// usernameAndIDFromFlag returns the username and ID from the flags of the command.
// If both are empty, it will exit the program with an error.
func usernameAndIDFromFlag(cmd *cobra.Command) (uint64, string) {
username, _ := cmd.Flags().GetString("name")
identifier, _ := cmd.Flags().GetInt64("identifier")
if username == "" && identifier < 0 {
err := errors.New("--name or --identifier flag is required")
ErrorOutput(
err,
fmt.Sprintf(
"Cannot rename user: %s",
status.Convert(err).Message(),
),
"",
)
}

return uint64(identifier), username
}

func init() {
rootCmd.AddCommand(userCmd)
userCmd.AddCommand(createUserCmd)
userCmd.AddCommand(listUsersCmd)
usernameAndIDFlag(listUsersCmd)
listUsersCmd.Flags().StringP("email", "e", "", "Email")
userCmd.AddCommand(destroyUserCmd)
usernameAndIDFlag(destroyUserCmd)
userCmd.AddCommand(renameUserCmd)
usernameAndIDFlag(renameUserCmd)
renameUserCmd.Flags().StringP("new-name", "r", "", "New username")
renameNodeCmd.MarkFlagRequired("new-name")
}

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

var destroyUserCmd = &cobra.Command{
Use: "destroy NAME",
Use: "destroy --identifier ID or --name NAME",
Short: "Destroys a user",
Aliases: []string{"delete"},
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errMissingParameter
}

return nil
},
Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output")

userName := args[0]

request := &v1.GetUserRequest{
Name: userName,
id, username := usernameAndIDFromFlag(cmd)
request := &v1.ListUsersRequest{
Name: username,
Id: id,
}

ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()

_, err := client.GetUser(ctx, request)
users, err := client.ListUsers(ctx, request)
if err != nil {
ErrorOutput(
err,
Expand All @@ -102,13 +126,24 @@ var destroyUserCmd = &cobra.Command{
)
}

if len(users.GetUsers()) != 1 {
err := fmt.Errorf("Unable to determine user to delete, query returned multiple users, use ID")
ErrorOutput(
err,
fmt.Sprintf("Error: %s", status.Convert(err).Message()),
output,
)
}

user := users.GetUsers()[0]

confirm := false
force, _ := cmd.Flags().GetBool("force")
if !force {
prompt := &survey.Confirm{
Message: fmt.Sprintf(
"Do you want to remove the user '%s' and any associated preauthkeys?",
userName,
"Do you want to remove the user %q (%d) and any associated preauthkeys?",
user.GetName(), user.GetId(),
),
}
err := survey.AskOne(prompt, &confirm)
Expand All @@ -118,7 +153,7 @@ var destroyUserCmd = &cobra.Command{
}

if confirm || force {
request := &v1.DeleteUserRequest{Name: userName}
request := &v1.DeleteUserRequest{Id: user.GetId()}

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

request := &v1.ListUsersRequest{}

id, _ := cmd.Flags().GetInt64("identifier")
username, _ := cmd.Flags().GetString("name")
email, _ := cmd.Flags().GetString("email")

// filter by one param at most
switch {
case id > 0:
request.Id = uint64(id)
break
case username != "":
request.Name = username
break
case email != "":
request.Email = email
break
}

response, err := client.ListUsers(ctx, request)
if err != nil {
ErrorOutput(
Expand All @@ -169,7 +221,7 @@ var listUsersCmd = &cobra.Command{
tableData = append(
tableData,
[]string{
user.GetId(),
fmt.Sprintf("%d", user.GetId()),
user.GetDisplayName(),
user.GetName(),
user.GetEmail(),
Expand All @@ -189,30 +241,48 @@ var listUsersCmd = &cobra.Command{
}

var renameUserCmd = &cobra.Command{
Use: "rename OLD_NAME NEW_NAME",
Use: "rename",
Short: "Renames a user",
Aliases: []string{"mv"},
Args: func(cmd *cobra.Command, args []string) error {
expectedArguments := 2
if len(args) < expectedArguments {
return errMissingParameter
}

return nil
},
Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output")

ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()

request := &v1.RenameUserRequest{
OldName: args[0],
NewName: args[1],
id, username := usernameAndIDFromFlag(cmd)
listReq := &v1.ListUsersRequest{
Name: username,
Id: id,
}

users, err := client.ListUsers(ctx, listReq)
if err != nil {
ErrorOutput(
err,
fmt.Sprintf("Error: %s", status.Convert(err).Message()),
output,
)
}

if len(users.GetUsers()) != 1 {
err := fmt.Errorf("Unable to determine user to delete, query returned multiple users, use ID")
ErrorOutput(
err,
fmt.Sprintf("Error: %s", status.Convert(err).Message()),
output,
)
}

newName, _ := cmd.Flags().GetString("new-name")

renameReq := &v1.RenameUserRequest{
OldId: id,
NewName: newName,
}

response, err := client.RenameUser(ctx, request)
response, err := client.RenameUser(ctx, renameReq)
if err != nil {
ErrorOutput(
err,
Expand Down
25 changes: 21 additions & 4 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,38 @@

protoc-gen-grpc-gateway = buildGo rec {
pname = "grpc-gateway";
version = "2.22.0";
version = "2.24.0";

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

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

nativeBuildInputs = [pkgs.installShellFiles];

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

protobuf-language-server = buildGo rec {
pname = "protobuf-language-server";
version = "2546944";

src = pkgs.fetchFromGitHub {
owner = "lasorda";
repo = "protobuf-language-server";
rev = "${version}";
sha256 = "sha256-Cbr3ktT86RnwUntOiDKRpNTClhdyrKLTQG2ZEd6fKDc=";
};

vendorHash = "sha256-PfT90dhfzJZabzLTb1D69JCO+kOh2khrlpF5mCDeypk=";

subPackages = ["."];
};

# Upstream does not override buildGoModule properly,
# importing a specific module, so comment out for now.
# golangci-lint = prev.golangci-lint.override {
Expand Down Expand Up @@ -115,6 +131,7 @@
protoc-gen-grpc-gateway
buf
clang-tools # clang-format
protobuf-language-server
];

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