wsh connreinstall (#317)

This commit is contained in:
Mike Sawka 2024-09-03 22:15:02 -07:00 committed by GitHub
parent fa69406550
commit afd83f0f6f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 166 additions and 30 deletions

View File

@ -0,0 +1,38 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"github.com/spf13/cobra"
"github.com/wavetermdev/thenextwave/pkg/remote"
"github.com/wavetermdev/thenextwave/pkg/wshrpc"
"github.com/wavetermdev/thenextwave/pkg/wshrpc/wshclient"
)
var connReinstallCmd = &cobra.Command{
Use: "connreinstall",
Short: "reinstall wsh on a connection",
Args: cobra.ExactArgs(1),
Run: connReinstallRun,
PreRunE: preRunSetupRpcClient,
}
func init() {
rootCmd.AddCommand(connReinstallCmd)
}
func connReinstallRun(cmd *cobra.Command, args []string) {
connName := args[0]
_, err := remote.ParseOpts(connName)
if err != nil {
WriteStderr("[error] cannot parse connection name: %v\n", err)
return
}
err = wshclient.ConnReinstallWshCommand(RpcClient, connName, &wshrpc.RpcOpts{Timeout: 60000})
if err != nil {
WriteStderr("[error] getting metadata: %v\n", err)
return
}
WriteStdout("wsh reinstalled on connection %q\n", connName)
}

View File

@ -17,6 +17,26 @@ class WshServerType {
return WOS.wshServerRpcHelper_call("authenticate", data, opts);
}
// command "conndisconnect" [call]
ConnDisconnectCommand(data: string, opts?: RpcOpts): Promise<void> {
return WOS.wshServerRpcHelper_call("conndisconnect", data, opts);
}
// command "connensure" [call]
ConnEnsureCommand(data: string, opts?: RpcOpts): Promise<void> {
return WOS.wshServerRpcHelper_call("connensure", data, opts);
}
// command "connforceconnect" [call]
ConnForceConnectCommand(data: string, opts?: RpcOpts): Promise<void> {
return WOS.wshServerRpcHelper_call("connforceconnect", data, opts);
}
// command "connreinstallwsh" [call]
ConnReinstallWshCommand(data: string, opts?: RpcOpts): Promise<void> {
return WOS.wshServerRpcHelper_call("connreinstallwsh", data, opts);
}
// command "controllerinput" [call]
ControllerInputCommand(data: CommandBlockInputData, opts?: RpcOpts): Promise<void> {
return WOS.wshServerRpcHelper_call("controllerinput", data, opts);

View File

@ -154,6 +154,7 @@ declare global {
status: string;
connection: string;
connected: boolean;
hasconnected: boolean;
error?: string;
};

View File

@ -53,6 +53,7 @@ type SSHConn struct {
ConnController *ssh.Session
Error string
HasWaiter *atomic.Bool
LastConnectTime int64
}
func GetAllConnStatus() []wshrpc.ConnStatus {
@ -70,10 +71,11 @@ func (conn *SSHConn) DeriveConnStatus() wshrpc.ConnStatus {
conn.Lock.Lock()
defer conn.Lock.Unlock()
return wshrpc.ConnStatus{
Status: conn.Status,
Connected: conn.Status == Status_Connected,
Connection: conn.Opts.String(),
Error: conn.Error,
Status: conn.Status,
Connected: conn.Status == Status_Connected,
Connection: conn.Opts.String(),
HasConnected: (conn.LastConnectTime > 0),
Error: conn.Error,
}
}
@ -243,7 +245,15 @@ func (conn *SSHConn) StartConnServer() error {
return nil
}
func (conn *SSHConn) checkAndInstallWsh(ctx context.Context, clientDisplayName string) error {
type WshInstallOpts struct {
Force bool
NoUserPrompt bool
}
func (conn *SSHConn) CheckAndInstallWsh(ctx context.Context, clientDisplayName string, opts *WshInstallOpts) error {
if opts == nil {
opts = &WshInstallOpts{}
}
client := conn.GetClient()
if client == nil {
return fmt.Errorf("client is nil")
@ -251,12 +261,15 @@ func (conn *SSHConn) checkAndInstallWsh(ctx context.Context, clientDisplayName s
// check that correct wsh extensions are installed
expectedVersion := fmt.Sprintf("wsh v%s", wavebase.WaveVersion)
clientVersion, err := remote.GetWshVersion(client)
if err == nil && clientVersion == expectedVersion {
if err == nil && clientVersion == expectedVersion && !opts.Force {
return nil
}
var queryText string
var title string
if err != nil {
if opts.Force {
queryText = fmt.Sprintf("ReInstalling Wave Shell Extensions (%s) on `%s`\n", wavebase.WaveVersion, clientDisplayName)
title = "Install Wave Shell Extensions"
} else if err != nil {
queryText = fmt.Sprintf("Wave requires Wave Shell Extensions to be \n"+
"installed on `%s` \n"+
"to ensure a seamless experience. \n\n"+
@ -269,16 +282,18 @@ func (conn *SSHConn) checkAndInstallWsh(ctx context.Context, clientDisplayName s
"Would you like to update?", clientDisplayName, clientVersion, expectedVersion)
title = "Update Wave Shell Extensions"
}
request := &userinput.UserInputRequest{
ResponseType: "confirm",
QueryText: queryText,
Title: title,
Markdown: true,
CheckBoxMsg: "Don't show me this again",
}
response, err := userinput.GetUserInput(ctx, request)
if err != nil || !response.Confirm {
return err
if !opts.NoUserPrompt {
request := &userinput.UserInputRequest{
ResponseType: "confirm",
QueryText: queryText,
Title: title,
Markdown: true,
CheckBoxMsg: "Don't show me this again",
}
response, err := userinput.GetUserInput(ctx, request)
if err != nil || !response.Confirm {
return err
}
}
log.Printf("attempting to install wsh to `%s`", clientDisplayName)
clientOs, err := remote.GetClientOs(client)
@ -337,6 +352,7 @@ func (conn *SSHConn) Connect(ctx context.Context) error {
conn.close_nolock()
} else {
conn.Status = Status_Connected
conn.LastConnectTime = time.Now().UnixMilli()
}
})
conn.FireConnChangeEvent()
@ -363,7 +379,7 @@ func (conn *SSHConn) connectInternal(ctx context.Context) error {
if err != nil {
return err
}
installErr := conn.checkAndInstallWsh(ctx, clientDisplayName)
installErr := conn.CheckAndInstallWsh(ctx, clientDisplayName, nil)
if installErr != nil {
return fmt.Errorf("conncontroller %s wsh install error: %v", conn.GetName(), installErr)
}
@ -385,14 +401,13 @@ func (conn *SSHConn) waitForDisconnect() {
}
err := client.Wait()
conn.WithLock(func() {
if err != nil {
if conn.Status != Status_Disconnected {
// don't set the error if our status is disconnected (because this error was caused by an explicit close)
conn.Status = Status_Error
conn.Error = err.Error()
}
} else {
// not sure if this is possible, because I think Wait() always returns an error (although that's not in the docs)
// disconnects happen for a variety of reasons (like network, etc. and are typically transient)
// so we just set the status to "disconnected" here (not error)
// don't overwrite any existing error (or error status)
if err != nil && conn.Error == "" {
conn.Error = err.Error()
}
if conn.Status != Status_Error {
conn.Status = Status_Disconnected
}
conn.close_nolock()

View File

@ -24,6 +24,30 @@ func AuthenticateCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) (
return resp, err
}
// command "conndisconnect", wshserver.ConnDisconnectCommand
func ConnDisconnectCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "conndisconnect", data, opts)
return err
}
// command "connensure", wshserver.ConnEnsureCommand
func ConnEnsureCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "connensure", data, opts)
return err
}
// command "connforceconnect", wshserver.ConnForceConnectCommand
func ConnForceConnectCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "connforceconnect", data, opts)
return err
}
// command "connreinstallwsh", wshserver.ConnReinstallWshCommand
func ConnReinstallWshCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "connreinstallwsh", data, opts)
return err
}
// command "controllerinput", wshserver.ControllerInputCommand
func ControllerInputCommand(w *wshutil.WshRpc, data wshrpc.CommandBlockInputData, opts *wshrpc.RpcOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "controllerinput", data, opts)

View File

@ -63,6 +63,11 @@ const (
Command_RemoteWriteFile = "remotewritefile"
Command_RemoteFileDelete = "remotefiledelete"
Command_RemoteFileJoiin = "remotefilejoin"
Command_ConnEnsure = "connensure"
Command_ConnReinstallWsh = "connreinstallwsh"
Command_ConnForceConnect = "connforceconnect"
Command_ConnDisconnect = "conndisconnect"
)
type RespOrErrorUnion[T any] struct {
@ -98,6 +103,12 @@ type WshRpcInterface interface {
TestCommand(ctx context.Context, data string) error
SetConfigCommand(ctx context.Context, data wconfig.MetaSettingsType) error
// connection functions
ConnEnsureCommand(ctx context.Context, connName string) error
ConnReinstallWshCommand(ctx context.Context, connName string) error
ConnForceConnectCommand(ctx context.Context, connName string) error
ConnDisconnectCommand(ctx context.Context, connName string) error
// eventrecv is special, it's handled internally by WshRpc with EventListener
EventRecvCommand(ctx context.Context, data WaveEvent) error
@ -344,8 +355,9 @@ type TimeSeriesData struct {
}
type ConnStatus struct {
Status string `json:"status"`
Connection string `json:"connection"`
Connected bool `json:"connected"`
Error string `json:"error,omitempty"`
Status string `json:"status"`
Connection string `json:"connection"`
Connected bool `json:"connected"`
HasConnected bool `json:"hasconnected"` // true if it has *ever* connected successfully
Error string `json:"error,omitempty"`
}

View File

@ -20,6 +20,8 @@ import (
"github.com/wavetermdev/thenextwave/pkg/blockcontroller"
"github.com/wavetermdev/thenextwave/pkg/eventbus"
"github.com/wavetermdev/thenextwave/pkg/filestore"
"github.com/wavetermdev/thenextwave/pkg/remote"
"github.com/wavetermdev/thenextwave/pkg/remote/conncontroller"
"github.com/wavetermdev/thenextwave/pkg/waveai"
"github.com/wavetermdev/thenextwave/pkg/waveobj"
"github.com/wavetermdev/thenextwave/pkg/wconfig"
@ -467,3 +469,27 @@ func (ws *WshServer) SetConfigCommand(ctx context.Context, data wconfig.MetaSett
log.Printf("SETCONFIG: %v\n", data)
return wconfig.SetBaseConfigValue(data.MetaMapType)
}
func (ws *WshServer) ConnEnsureCommand(ctx context.Context, connName string) error {
return nil
}
func (ws *WshServer) ConnDisconnectCommand(ctx context.Context, connName string) error {
return nil
}
func (ws *WshServer) ConnForceConnectCommand(ctx context.Context, connName string) error {
return nil
}
func (ws *WshServer) ConnReinstallWshCommand(ctx context.Context, connName string) error {
connOpts, err := remote.ParseOpts(connName)
if err != nil {
return fmt.Errorf("error parsing connection name: %w", err)
}
conn := conncontroller.GetConn(ctx, connOpts, false)
if conn == nil {
return fmt.Errorf("connection not found: %s", connName)
}
return conn.CheckAndInstallWsh(ctx, connName, &conncontroller.WshInstallOpts{Force: true, NoUserPrompt: true})
}