From 18750e308f9327181be2d1b6858b697b8474a67b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Pavl=C3=AD=C4=8Dek?= Date: Tue, 7 Oct 2025 13:13:34 +0200 Subject: [PATCH] feat: add 'vpn clear' and 'vpn key' commands --- src/cliStorage/handler.go | 8 ++--- src/cmd/vpn.go | 4 ++- src/cmd/vpnClear.go | 62 +++++++++++++++++++++++++++++++++ src/cmd/vpnKey.go | 14 ++++++++ src/cmd/vpnKeyList.go.go | 42 ++++++++++++++++++++++ src/cmd/vpnKeyRemove.go.go | 61 ++++++++++++++++++++++++++++++++ src/cmd/vpnUp.go | 6 ++-- src/cmdBuilder/createRunFunc.go | 2 +- src/uxBlock/logs.go | 17 +++++++++ 9 files changed, 207 insertions(+), 9 deletions(-) create mode 100644 src/cmd/vpnClear.go create mode 100644 src/cmd/vpnKey.go create mode 100644 src/cmd/vpnKeyList.go.go create mode 100644 src/cmd/vpnKeyRemove.go.go diff --git a/src/cliStorage/handler.go b/src/cliStorage/handler.go index 147a5fb8..17b34cd5 100644 --- a/src/cliStorage/handler.go +++ b/src/cliStorage/handler.go @@ -12,8 +12,8 @@ type Handler struct { } type Data struct { - Token string - RegionData region.Item - ScopeProjectId uuid.ProjectIdNull - VpnKeys map[uuid.ProjectId]entity.VpnKey + Token string + RegionData region.Item + ScopeProjectId uuid.ProjectIdNull + ProjectVpnKeyRegistry map[uuid.ProjectId]entity.VpnKey } diff --git a/src/cmd/vpn.go b/src/cmd/vpn.go index 5d4589f5..3eb87220 100644 --- a/src/cmd/vpn.go +++ b/src/cmd/vpn.go @@ -11,5 +11,7 @@ func vpnCmd() *cmdBuilder.Cmd { Short(i18n.T(i18n.CmdDescVpn)). HelpFlag(i18n.T(i18n.CmdHelpVpn)). AddChildrenCmd(vpnUpCmd()). - AddChildrenCmd(vpnDownCmd()) + AddChildrenCmd(vpnDownCmd()). + AddChildrenCmd(vpnClearCmd()). + AddChildrenCmd(vpnKeyCmd()) } diff --git a/src/cmd/vpnClear.go b/src/cmd/vpnClear.go new file mode 100644 index 00000000..96829fe7 --- /dev/null +++ b/src/cmd/vpnClear.go @@ -0,0 +1,62 @@ +package cmd + +import ( + "context" + + "github.com/zeropsio/zcli/src/cliStorage" + "github.com/zeropsio/zcli/src/cmdBuilder" + "github.com/zeropsio/zerops-go/dto/input/body" + "github.com/zeropsio/zerops-go/dto/input/path" + "github.com/zeropsio/zerops-go/types" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +func vpnClearCmd() *cmdBuilder.Cmd { + return cmdBuilder.NewCmd(). + Use("clear"). + Short("Disconnects Zerops VPN, removes registered public key from the project via API and deletes locally stored wg key."). + ScopeLevel(cmdBuilder.ScopeProject()). + HelpFlag("Help for the 'vpn clear' command."). + LoggedUserRunFunc(func(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData) error { + project, err := cmdData.Project.Expect("project is null") + if err != nil { + return err + } + + if vpnDownErr := disconnectVpn(ctx, cmdData.UxBlocks); vpnDownErr != nil { + cmdData.UxBlocks.PrintWarningTextf("vpn down: %s", vpnDownErr) + } + + if vpnKey, ok := cmdData.CliStorage.Data().ProjectVpnKeyRegistry[project.Id]; ok { + wgKey, err := wgtypes.ParseKey(vpnKey.Key) + if err != nil { + return err + } + + deleteResponse, err := cmdData.RestApiClient.DeleteProjectVpn( + ctx, + path.ProjectId{Id: project.Id}, + body.PostProjectVpn{PublicKey: types.String(wgKey.PublicKey().String())}, + ) + if err != nil { + return err + } + if err := deleteResponse.Err(); err != nil { + return err + } + + cmdData.UxBlocks.PrintSuccessTextf("Removed registered public key from project via API: %s", wgKey.PublicKey().String()) + + _, err = cmdData.CliStorage.Update(func(data cliStorage.Data) cliStorage.Data { + delete(data.ProjectVpnKeyRegistry, project.Id) + cmdData.UxBlocks.PrintSuccessText("Deleted locally stored wg keys.") + return data + }) + if err != nil { + return err + } + } + + return nil + }) +} diff --git a/src/cmd/vpnKey.go b/src/cmd/vpnKey.go new file mode 100644 index 00000000..8b60c952 --- /dev/null +++ b/src/cmd/vpnKey.go @@ -0,0 +1,14 @@ +package cmd + +import ( + "github.com/zeropsio/zcli/src/cmdBuilder" +) + +func vpnKeyCmd() *cmdBuilder.Cmd { + return cmdBuilder.NewCmd(). + Use("key"). + Short("VPN keys commands group."). + HelpFlag("Help for the 'vpn key' commands."). + AddChildrenCmd(vpnKeyListCmd()). + AddChildrenCmd(vpnKeyRemoveCmd()) +} diff --git a/src/cmd/vpnKeyList.go.go b/src/cmd/vpnKeyList.go.go new file mode 100644 index 00000000..4f4795c7 --- /dev/null +++ b/src/cmd/vpnKeyList.go.go @@ -0,0 +1,42 @@ +package cmd + +import ( + "context" + + "github.com/zeropsio/zcli/src/cmdBuilder" + "github.com/zeropsio/zerops-go/dto/input/path" + "github.com/zeropsio/zerops-go/dto/input/query" +) + +func vpnKeyListCmd() *cmdBuilder.Cmd { + return cmdBuilder.NewCmd(). + Use("list"). + Short("Lists all registered VPN public keys of the project."). + ScopeLevel(cmdBuilder.ScopeProject()). + HelpFlag("Help for the 'vpn key list' command."). + LoggedUserRunFunc(func(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData) error { + project, err := cmdData.Project.Expect("project is null") + if err != nil { + return err + } + + listResponse, err := cmdData.RestApiClient.GetProjectVpnList( + ctx, + path.ProjectId{Id: project.Id}, + query.GetProjectVpn{}, + ) + if err != nil { + return err + } + list, err := listResponse.Output() + if err != nil { + return err + } + + for _, peer := range list.Peers { + cmdData.Stdout.Println(peer.PublicKey) + } + + return nil + }) +} diff --git a/src/cmd/vpnKeyRemove.go.go b/src/cmd/vpnKeyRemove.go.go new file mode 100644 index 00000000..039ed3f0 --- /dev/null +++ b/src/cmd/vpnKeyRemove.go.go @@ -0,0 +1,61 @@ +package cmd + +import ( + "context" + "encoding/base64" + + "github.com/zeropsio/zcli/src/cmdBuilder" + "github.com/zeropsio/zerops-go/dto/input/body" + "github.com/zeropsio/zerops-go/dto/input/path" + "github.com/zeropsio/zerops-go/dto/input/query" + "github.com/zeropsio/zerops-go/types" +) + +func vpnKeyRemoveCmd() *cmdBuilder.Cmd { + return cmdBuilder.NewCmd(). + Use("remove"). + Short("Removes registered VPN public keys from project via API."). + ScopeLevel(cmdBuilder.ScopeProject()). + Arg("public-keys", cmdBuilder.OptionalArg(), cmdBuilder.ArrayArg(), cmdBuilder.OptionalArgLabel("[ public-keys ... ]")). + HelpFlag("Help for the 'vpn key remove' command."). + LoggedUserRunFunc(func(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData) error { + project, err := cmdData.Project.Expect("project is null") + if err != nil { + return err + } + + for _, publicKey := range cmdData.Args["public-keys"] { + getResponse, err := cmdData.RestApiClient.GetProjectVpn( + ctx, + path.ProjectIdBase64PublicKey{ + Id: project.Id, + Base64PublicKey: types.String(base64.URLEncoding.EncodeToString([]byte(publicKey))), + }, + query.GetProjectVpn{}, + ) + if err != nil { + return err + } + if getResponse.Err() != nil { + cmdData.UxBlocks.PrintWarningTextf("Public key not found: %s", publicKey) + continue + } + + deleteResponse, err := cmdData.RestApiClient.DeleteProjectVpn( + ctx, + path.ProjectId{Id: project.Id}, + body.PostProjectVpn{PublicKey: types.String(publicKey)}, + ) + if err != nil { + return err + } + if err := deleteResponse.Err(); err != nil { + return err + } + + cmdData.UxBlocks.PrintSuccessTextf("Removed registered public key from project via API: %s", publicKey) + } + + return nil + }) +} diff --git a/src/cmd/vpnUp.go b/src/cmd/vpnUp.go index 6cae148b..2c72456e 100644 --- a/src/cmd/vpnUp.go +++ b/src/cmd/vpnUp.go @@ -112,10 +112,10 @@ func vpnUpCmd() *cmdBuilder.Cmd { uxBlocks.PrintInfo(styles.InfoWithValueLine(i18n.T(i18n.VpnConfigSaved), filePath)) _, err = cmdData.CliStorage.Update(func(data cliStorage.Data) cliStorage.Data { - if data.VpnKeys == nil { - data.VpnKeys = make(map[uuid.ProjectId]entity.VpnKey) + if data.ProjectVpnKeyRegistry == nil { + data.ProjectVpnKeyRegistry = make(map[uuid.ProjectId]entity.VpnKey) } - data.VpnKeys[project.Id] = entity.VpnKey{ + data.ProjectVpnKeyRegistry[project.Id] = entity.VpnKey{ ProjectId: project.Id, Key: privateKey.String(), CreatedAt: time.Now(), diff --git a/src/cmdBuilder/createRunFunc.go b/src/cmdBuilder/createRunFunc.go index e1222a75..fede7392 100644 --- a/src/cmdBuilder/createRunFunc.go +++ b/src/cmdBuilder/createRunFunc.go @@ -105,7 +105,7 @@ func createCmdRunFunc( cmdData := &LoggedUserCmdData{ GuestCmdData: guestCmdData, - VpnKeys: storedData.VpnKeys, + VpnKeys: storedData.ProjectVpnKeyRegistry, } host := storedData.RegionData.Address diff --git a/src/uxBlock/logs.go b/src/uxBlock/logs.go index 90f94b3f..bf95ee1c 100644 --- a/src/uxBlock/logs.go +++ b/src/uxBlock/logs.go @@ -3,6 +3,7 @@ package uxBlock import ( "bufio" "bytes" + "fmt" "github.com/zeropsio/zcli/src/logger" "github.com/zeropsio/zcli/src/uxBlock/styles" @@ -27,6 +28,10 @@ func (b *Blocks) PrintSuccessText(in string) { } } +func (b *Blocks) PrintSuccessTextf(format string, args ...any) { + b.PrintSuccessText(fmt.Sprintf(format, args...)) +} + func (b *Blocks) PrintInfoText(in string) { scan := bufio.NewScanner(bytes.NewBufferString(in)) for scan.Scan() { @@ -34,6 +39,10 @@ func (b *Blocks) PrintInfoText(in string) { } } +func (b *Blocks) PrintInfoTextf(format string, args ...any) { + b.PrintInfoText(fmt.Sprintf(format, args...)) +} + func (b *Blocks) PrintInfo(line styles.Line) { b.outputLogger.Info(line) b.debugFileLogger.Info(line.DisableStyle()) @@ -46,6 +55,10 @@ func (b *Blocks) PrintWarningText(in string) { } } +func (b *Blocks) PrintWarningTextf(format string, args ...any) { + b.PrintWarningText(fmt.Sprintf(format, args...)) +} + func (b *Blocks) PrintWarning(line styles.Line) { b.outputLogger.Warning(line) b.debugFileLogger.Warning(line.DisableStyle()) @@ -58,6 +71,10 @@ func (b *Blocks) PrintErrorText(in string) { } } +func (b *Blocks) PrintErrorTextf(format string, args ...any) { + b.PrintErrorText(fmt.Sprintf(format, args...)) +} + func (b *Blocks) PrintError(line styles.Line) { b.outputLogger.Error(line) b.debugFileLogger.Error(line.DisableStyle())