Skip to content

Commit

Permalink
feat: vpn update (#126)
Browse files Browse the repository at this point in the history
* feat: vpn update

* update

* update imports

---------

Co-authored-by: [email protected] <[email protected]>
  • Loading branch information
jan-hajek and jan-hajek authored Mar 16, 2024
1 parent daac125 commit 3753fef
Show file tree
Hide file tree
Showing 11 changed files with 259 additions and 63 deletions.
14 changes: 11 additions & 3 deletions src/cmd/projectDelete.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ package cmd
import (
"context"

"github.com/pkg/errors"

"github.com/zeropsio/zcli/src/cmd/scope"
"github.com/zeropsio/zcli/src/cmdBuilder"
"github.com/zeropsio/zcli/src/i18n"
"github.com/zeropsio/zcli/src/uxHelpers"
"github.com/zeropsio/zerops-go/dto/input/path"

"github.com/zeropsio/zcli/src/i18n"
)

func projectDeleteCmd() *cmdBuilder.Cmd {
Expand All @@ -21,10 +22,17 @@ func projectDeleteCmd() *cmdBuilder.Cmd {
HelpFlag(i18n.T(i18n.ProjectDeleteHelp)).
LoggedUserRunFunc(func(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData) error {
if !cmdData.Params.GetBool("confirm") {
err := uxHelpers.YesNoPromptDestructive(ctx, cmdData.UxBlocks, i18n.T(i18n.ProjectDeleteConfirm, cmdData.Project.Name))
confirmed, err := uxHelpers.YesNoPrompt(
ctx,
cmdData.UxBlocks,
i18n.T(i18n.ProjectDeleteConfirm, cmdData.Project.Name),
)
if err != nil {
return err
}
if !confirmed {
return errors.New(i18n.T(i18n.DestructiveOperationConfirmationFailed))
}
}

deleteProjectResponse, err := cmdData.RestApiClient.DeleteProject(
Expand Down
12 changes: 12 additions & 0 deletions src/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ package cmd
import (
"context"
"fmt"
"time"

"github.com/zeropsio/zcli/src/cmdBuilder"
"github.com/zeropsio/zcli/src/constants"
"github.com/zeropsio/zcli/src/entity/repository"
"github.com/zeropsio/zcli/src/errorsx"
"github.com/zeropsio/zcli/src/i18n"
"github.com/zeropsio/zcli/src/nettools"
"github.com/zeropsio/zcli/src/uxBlock"
"github.com/zeropsio/zcli/src/uxBlock/styles"
)
Expand All @@ -21,6 +23,7 @@ func rootCmd() *cmdBuilder.Cmd {
return cmdBuilder.NewCmd().
Use("zcli").
SetHelpTemplate(getRootTemplate()).
SilenceError(true).
AddChildrenCmd(loginCmd()).
AddChildrenCmd(versionCmd()).
AddChildrenCmd(scopeCmd()).
Expand Down Expand Up @@ -73,6 +76,15 @@ func rootCmd() *cmdBuilder.Cmd {
body.AddStringsRow(i18n.T(i18n.ScopedProject), fmt.Sprintf("%s [%s]", project.Name.String(), project.ID.Native()))
}

ctx, cancel := context.WithTimeout(ctx, time.Second*5)
defer cancel()

if err := nettools.Ping(ctx, vpnCheckAddress); err != nil {
body.AddStringsRow(i18n.T(i18n.StatusInfoVpnStatus), i18n.T(i18n.VpnCheckingConnectionIsNotActive))
} else {
body.AddStringsRow(i18n.T(i18n.StatusInfoVpnStatus), i18n.T(i18n.VpnCheckingConnectionIsActive))
}

cmdData.UxBlocks.Table(body)

return nil
Expand Down
11 changes: 10 additions & 1 deletion src/cmd/serviceDelete.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package cmd
import (
"context"

"github.com/pkg/errors"

"github.com/zeropsio/zcli/src/cmd/scope"
"github.com/zeropsio/zcli/src/cmdBuilder"
"github.com/zeropsio/zcli/src/i18n"
Expand All @@ -20,10 +22,17 @@ func serviceDeleteCmd() *cmdBuilder.Cmd {
HelpFlag(i18n.T(i18n.ServiceDeleteHelp)).
LoggedUserRunFunc(func(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData) error {
if !cmdData.Params.GetBool("confirm") {
err := uxHelpers.YesNoPromptDestructive(ctx, cmdData.UxBlocks, i18n.T(i18n.ServiceDeleteConfirm, cmdData.Service.Name))
confirmed, err := uxHelpers.YesNoPrompt(
ctx,
cmdData.UxBlocks,
i18n.T(i18n.ServiceDeleteConfirm, cmdData.Service.Name),
)
if err != nil {
return err
}
if !confirmed {
return errors.New(i18n.T(i18n.DestructiveOperationConfirmationFailed))
}
}

deleteServiceResponse, err := cmdData.RestApiClient.DeleteServiceStack(
Expand Down
60 changes: 33 additions & 27 deletions src/cmd/vpnDown.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import (
"os"
"os/exec"

"github.com/pkg/errors"

"github.com/zeropsio/zcli/src/cmdBuilder"
"github.com/zeropsio/zcli/src/cmdRunner"
"github.com/zeropsio/zcli/src/constants"
"github.com/zeropsio/zcli/src/i18n"
"github.com/zeropsio/zcli/src/uxBlock"
"github.com/zeropsio/zcli/src/uxBlock/styles"
)

Expand All @@ -18,32 +21,35 @@ func vpnDownCmd() *cmdBuilder.Cmd {
Short(i18n.T(i18n.CmdVpnDown)).
HelpFlag(i18n.T(i18n.VpnDownHelp)).
LoggedUserRunFunc(func(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData) error {
uxBlocks := cmdData.UxBlocks

filePath, err := constants.WgConfigFilePath()
if err != nil {
return err
}

// create empty file if not exists, only thing wg-quick needs is a proper file name
f, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
return err
}
defer f.Close()

// TODO - janhajek check if vpn is connected
// TODO - janhajek get somehow a meaningful output
// TODO - janhajek check if wg-quick is installed
// TODO - janhajek a configurable path to wg-quick
c := exec.CommandContext(ctx, "wg-quick", "down", filePath)
_, err = cmdRunner.Run(c)
if err != nil {
return err
}

uxBlocks.PrintInfo(styles.InfoLine(i18n.T(i18n.VpnDown)))

return nil
return disconnectVpn(ctx, cmdData.UxBlocks)
})
}

func disconnectVpn(ctx context.Context, uxBlocks uxBlock.UxBlocks) error {
_, err := exec.LookPath("wg-quick")
if err != nil {
return errors.New(i18n.T(i18n.VpnWgQuickIsNotInstalled))
}

filePath, err := constants.WgConfigFilePath()
if err != nil {
return err
}

// create empty file if not exists, only thing wg-quick needs is a proper file name
f, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
return err
}
defer f.Close()

c := exec.CommandContext(ctx, "wg-quick", "down", filePath)
_, err = cmdRunner.Run(c)
if err != nil {
return err
}

uxBlocks.PrintInfo(styles.InfoLine(i18n.T(i18n.VpnDown)))

return nil
}
102 changes: 92 additions & 10 deletions src/cmd/vpnUp.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,70 @@ import (
"text/template"
"time"

"github.com/pkg/errors"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"

"github.com/zeropsio/zcli/src/cliStorage"
"github.com/zeropsio/zcli/src/cmd/scope"
"github.com/zeropsio/zcli/src/cmdBuilder"
"github.com/zeropsio/zcli/src/cmdRunner"
"github.com/zeropsio/zcli/src/constants"
"github.com/zeropsio/zcli/src/entity"
"github.com/zeropsio/zcli/src/i18n"
"github.com/zeropsio/zcli/src/nettools"
"github.com/zeropsio/zcli/src/uxBlock"
"github.com/zeropsio/zcli/src/uxBlock/styles"
"github.com/zeropsio/zcli/src/uxHelpers"
"github.com/zeropsio/zerops-go/dto/input/body"
"github.com/zeropsio/zerops-go/dto/input/path"
"github.com/zeropsio/zerops-go/types"
"github.com/zeropsio/zerops-go/types/uuid"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"

"github.com/zeropsio/zcli/src/cmdBuilder"
"github.com/zeropsio/zcli/src/i18n"
"github.com/zeropsio/zcli/src/uxBlock/styles"
)

const vpnCheckAddress = "logger.core.zerops"

func vpnUpCmd() *cmdBuilder.Cmd {
return cmdBuilder.NewCmd().
Use("up").
Short(i18n.T(i18n.CmdVpnUp)).
ScopeLevel(scope.Project).
Arg(scope.ProjectArgName, cmdBuilder.OptionalArg()).
BoolFlag("auto-disconnect", false, i18n.T(i18n.VpnAutoDisconnectFlag)).
HelpFlag(i18n.T(i18n.VpnUpHelp)).
LoggedUserRunFunc(func(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData) error {
uxBlocks := cmdData.UxBlocks

_, err := exec.LookPath("wg-quick")
if err != nil {
return errors.New(i18n.T(i18n.VpnWgQuickIsNotInstalled))
}

if !isVpnDisconnect(ctx, uxBlocks) {
if cmdData.Params.GetBool("auto-disconnect") {
err := disconnectVpn(ctx, uxBlocks)
if err != nil {
return err
}
} else {
confirmed, err := uxHelpers.YesNoPrompt(
ctx,
cmdData.UxBlocks,
i18n.T(i18n.VpnDisconnectionPrompt),
)
if err != nil {
return err
}
if !confirmed {
return errors.New(i18n.T(i18n.VpnDisconnectionPromptNo))
}

err = disconnectVpn(ctx, uxBlocks)
if err != nil {
return err
}
}
}

privateKey, err := getOrCreatePrivateVpnKey(cmdData)
if err != nil {
return err
Expand Down Expand Up @@ -103,17 +141,15 @@ func vpnUpCmd() *cmdBuilder.Cmd {
return err
}

// TODO - janhajek check if vpn is disconnected
// TODO - janhajek get somehow a meaningful output
// TODO - janhajek check if wg-quick is installed
// TODO - janhajek a configurable path to wg-quick
c := exec.CommandContext(ctx, "wg-quick", "up", filePath)
_, err = cmdRunner.Run(c)
if err != nil {
return err
}

// TODO - janhajek ping {{.Ipv4NetworkGateway}}
if !isVpnConnect(ctx, uxBlocks) {
uxBlocks.PrintWarning(styles.WarningLine(i18n.T(i18n.VpnPingFailed)))
}

uxBlocks.PrintInfo(styles.InfoLine(i18n.T(i18n.VpnUp)))

Expand Down Expand Up @@ -143,6 +179,52 @@ func getOrCreatePrivateVpnKey(cmdData *cmdBuilder.LoggedUserCmdData) (wgtypes.Ke
return vpnKey, nil
}

func isVpnConnect(ctx context.Context, uxBlocks uxBlock.UxBlocks) bool {
p := []uxHelpers.Process{
{
F: func(ctx context.Context) error {
ctx, cancel := context.WithTimeout(ctx, time.Second*5)
defer cancel()

return nettools.Ping(ctx, vpnCheckAddress)
},
RunningMessage: i18n.T(i18n.VpnCheckingConnection),
ErrorMessageMessage: i18n.T(i18n.VpnCheckingConnectionIsNotActive),
SuccessMessage: i18n.T(i18n.VpnCheckingConnectionIsActive),
},
}

err := uxHelpers.ProcessCheckWithSpinner(ctx, uxBlocks, p)

return err == nil
}

func isVpnDisconnect(ctx context.Context, uxBlocks uxBlock.UxBlocks) bool {
p := []uxHelpers.Process{
{
F: func(ctx context.Context) error {
ctx, cancel := context.WithTimeout(ctx, time.Second*5)
defer cancel()

err := nettools.Ping(ctx, vpnCheckAddress)
if err != nil {
//nolint:nilerr // Why: error is good in this case
return nil
} else {
return errors.New("vpn is connected")
}
},
RunningMessage: i18n.T(i18n.VpnCheckingConnection),
ErrorMessageMessage: i18n.T(i18n.VpnCheckingConnectionIsActive),
SuccessMessage: i18n.T(i18n.VpnCheckingConnectionIsNotActive),
},
}

err := uxHelpers.ProcessCheckWithSpinner(ctx, uxBlocks, p)

return err == nil
}

var vpnTmpl = `
[Interface]
PrivateKey = {{.PrivateKey}}
Expand Down
5 changes: 3 additions & 2 deletions src/cmdBuilder/buildCobraCmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ func buildCobraCmd(
cliStorage *cliStorage.Handler,
) (*cobra.Command, error) {
cobraCmd := &cobra.Command{
Short: cmd.short,
SilenceUsage: cmd.silenceUsage,
Short: cmd.short,
SilenceUsage: cmd.silenceUsage,
SilenceErrors: cmd.silenceError,
}

if cmd.helpTemplate != "" {
Expand Down
6 changes: 6 additions & 0 deletions src/cmdBuilder/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type Cmd struct {
loggedUserRunFunc loggedUserRunFunc
guestRunFunc guestRunFunc
silenceUsage bool
silenceError bool

scopeLevel ScopeLevel
args []cmdArg
Expand Down Expand Up @@ -90,6 +91,11 @@ func (cmd *Cmd) SilenceUsage(silenceUsage bool) *Cmd {
return cmd
}

func (cmd *Cmd) SilenceError(silenceError bool) *Cmd {
cmd.silenceError = silenceError
return cmd
}

func (cmd *Cmd) ScopeLevel(scopeLevel ScopeLevel) *Cmd {
cmd.scopeLevel = scopeLevel
return cmd
Expand Down
19 changes: 14 additions & 5 deletions src/i18n/en.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ var en = map[string]string{
ConfirmFlag: "If set, zCLI will not ask for confirmation of destructive operations.",
ServiceIdFlag: "If you have access to more than one service, you must specify the service ID for which the\ncommand is to be executed.",
ProjectIdFlag: "If you have access to more than one project, you must specify the project ID for which the\ncommand is to be executed.",
VpnAutoDisconnectFlag: "If set, zCLI will automatically disconnect from the VPN if it is already connected.",

// process
ProcessInvalidState: "last command has finished with error, identifier for communication with our support: %s",
Expand Down Expand Up @@ -159,16 +160,24 @@ var en = map[string]string{
StatusInfoLogFilePath: "Zerops CLI log file path",
StatusInfoWgConfigFilePath: "Zerops CLI wg config file path",
StatusInfoLoggedUser: "Logged user",
StatusInfoVpnStatus: "VPN status",

// debug logs
DebugLogsNotFound: "Debug logs not found",

// vpn
VpnUp: "VPN connected",
VpnDown: "VPN disconnected",
VpnConfigSaved: "VPN config saved",
VpnPrivateKeyCorrupted: "VPN private key corrupted, a new one will be created",
VpnPrivateKeyCreated: "VPN private key created",
VpnUp: "VPN connected",
VpnDown: "VPN disconnected",
VpnConfigSaved: "VPN config saved",
VpnPrivateKeyCorrupted: "VPN private key corrupted, a new one will be created",
VpnPrivateKeyCreated: "VPN private key created",
VpnWgQuickIsNotInstalled: "wg-quick is not installed, please install it and try again",
VpnDisconnectionPrompt: "VPN is active, do you want to disconnect?",
VpnDisconnectionPromptNo: "VPN is active, you can disconnect using the 'zcli vpn down' command",
VpnPingFailed: "VPN ping failed, this could indicate a problem with the VPN connection",
VpnCheckingConnection: "Checking VPN connection",
VpnCheckingConnectionIsActive: "VPN connection is active",
VpnCheckingConnectionIsNotActive: "VPN connection is not active",

////////////
// global //
Expand Down
Loading

0 comments on commit 3753fef

Please sign in to comment.