* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package clusterctrl
import (
"fmt"
"io"
"net"
"os/exec"
"strings"
"cli/utils"
)
func isLocalIP(ip string) bool {
targetIP := net.ParseIP(ip)
if targetIP == nil {
return false
}
if targetIP.IsLoopback() || targetIP.IsLinkLocalUnicast() || targetIP.IsLinkLocalMulticast() {
return true
}
addrs, err := net.InterfaceAddrs()
if err != nil {
return false
}
for _, addr := range addrs {
ipNet, _, err := net.ParseCIDR(addr.String())
if err != nil {
continue
}
if targetIP.Equal(ipNet) {
return true
}
}
return false
}
type SSHRemoteExecutor struct {
baseRemoteExecutor
sshCmdTemplate string
}
func NewSSHRemoteExecutor(sshCmdTemplate string) RemoteExecutor {
se := &SSHRemoteExecutor{
sshCmdTemplate: sshCmdTemplate,
}
se.Impl = se
return se
}
func (se *SSHRemoteExecutor) prepareCommand(host, command string) *exec.Cmd {
result := se.sshCmdTemplate
result = strings.ReplaceAll(result, utils.PlaceholderTarget, host)
result = strings.ReplaceAll(result, utils.PlaceholderCommand, command)
remoteCommand := strings.Fields(result)
return exec.Command(remoteCommand[0], remoteCommand[1:]...)
}
func (se *SSHRemoteExecutor) RunCommandWithPipe(host string, command string,
env []string,
) (io.ReadCloser, func() error, error) {
commandWithEnv := ""
for _, e := range env {
commandWithEnv += " export " + e + " && "
}
commandWithEnv += command
remoteCommand := se.prepareCommand(host, commandWithEnv)
if isLocalIP(host) {
remoteCommand = exec.Command("/bin/sh", "-c", command)
}
remoteCommand.Env = append(remoteCommand.Env, env...)
outPipe, err := remoteCommand.StdoutPipe()
if err != nil {
return nil, nil, fmt.Errorf("SSH remote executor failed to get stdout pipe: %v", err)
}
remoteCommand.Stderr = remoteCommand.Stdout
if err := remoteCommand.Start(); err != nil {
if closeErr := outPipe.Close(); closeErr != nil {
}
return nil, nil, fmt.Errorf("SSH remote executor failed to start command on %s: %v", host, err)
}
waitFn := func() error {
return remoteCommand.Wait()
}
return outPipe, waitFn, nil
}