195 lines
4.6 KiB
Go
195 lines
4.6 KiB
Go
package ssh
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os/exec"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/containers/common/pkg/config"
|
|
)
|
|
|
|
func nativeConnectionCreate(options ConnectionCreateOptions) error {
|
|
var match bool
|
|
var err error
|
|
if match, err = regexp.MatchString("^[A-Za-z][A-Za-z0-9+.-]*://", options.Path); err != nil {
|
|
return fmt.Errorf("invalid destination: %w", err)
|
|
}
|
|
|
|
if !match {
|
|
options.Path = "ssh://" + options.Path
|
|
}
|
|
|
|
if len(options.Socket) > 0 {
|
|
options.Path += options.Socket
|
|
}
|
|
|
|
dst, uri, err := Validate(options.User, options.Path, options.Port, options.Identity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// test connection
|
|
ssh, err := exec.LookPath("ssh")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if strings.Contains(uri.Host, "/run") {
|
|
uri.Host = strings.Split(uri.Host, "/run")[0]
|
|
}
|
|
conf, err := config.Default()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
args := []string{uri.User.String() + "@" + uri.Hostname()}
|
|
|
|
if len(dst.Identity) > 0 {
|
|
args = append(args, "-i", dst.Identity)
|
|
}
|
|
if len(conf.Engine.SSHConfig) > 0 {
|
|
args = append(args, "-F", conf.Engine.SSHConfig)
|
|
}
|
|
|
|
output := &bytes.Buffer{}
|
|
args = append(args, "podman", "info", "--format", "json")
|
|
info := exec.Command(ssh, args...)
|
|
info.Stdout = output
|
|
err = info.Run()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
remoteInfo := &Info{}
|
|
if err := json.Unmarshal(output.Bytes(), &remoteInfo); err != nil {
|
|
return fmt.Errorf("failed to parse 'podman info' results: %w", err)
|
|
}
|
|
|
|
if remoteInfo.Host.RemoteSocket == nil || len(remoteInfo.Host.RemoteSocket.Path) == 0 {
|
|
return fmt.Errorf("remote podman %q failed to report its UDS socket", uri.Host)
|
|
}
|
|
|
|
// TODO this really should not live here, it must be in podman where we write the other connections as well.
|
|
// This duplicates the code for no reason and I have a really hard time to make any sense of why this code
|
|
// was added in the first place.
|
|
return config.EditConnectionConfig(func(cfg *config.ConnectionsFile) error {
|
|
if cfg.Connection.Connections == nil {
|
|
cfg.Connection.Connections = map[string]config.Destination{
|
|
options.Name: *dst,
|
|
}
|
|
cfg.Connection.Default = options.Name
|
|
} else {
|
|
cfg.Connection.Connections[options.Name] = *dst
|
|
}
|
|
|
|
// Create or update an existing farm with the connection being added
|
|
if options.Farm != "" {
|
|
if len(cfg.Farm.List) == 0 {
|
|
cfg.Farm.Default = options.Farm
|
|
}
|
|
if val, ok := cfg.Farm.List[options.Farm]; ok {
|
|
cfg.Farm.List[options.Farm] = append(val, options.Name)
|
|
} else {
|
|
cfg.Farm.List[options.Farm] = []string{options.Name}
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func nativeConnectionExec(options ConnectionExecOptions, input io.Reader) (*ConnectionExecReport, error) {
|
|
dst, uri, err := Validate(options.User, options.Host, options.Port, options.Identity)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ssh, err := exec.LookPath("ssh")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
output := &bytes.Buffer{}
|
|
errors := &bytes.Buffer{}
|
|
if strings.Contains(uri.Host, "/run") {
|
|
uri.Host = strings.Split(uri.Host, "/run")[0]
|
|
}
|
|
|
|
options.Args = append([]string{uri.User.String() + "@" + uri.Hostname()}, options.Args...)
|
|
conf, err := config.Default()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
args := []string{}
|
|
if len(dst.Identity) > 0 {
|
|
args = append(args, "-i", dst.Identity)
|
|
}
|
|
if len(conf.Engine.SSHConfig) > 0 {
|
|
args = append(args, "-F", conf.Engine.SSHConfig)
|
|
}
|
|
args = append(args, options.Args...)
|
|
info := exec.Command(ssh, args...)
|
|
info.Stdout = output
|
|
info.Stderr = errors
|
|
if input != nil {
|
|
info.Stdin = input
|
|
}
|
|
err = info.Run()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &ConnectionExecReport{Response: output.String()}, nil
|
|
}
|
|
|
|
func nativeConnectionScp(options ConnectionScpOptions) (*ConnectionScpReport, error) {
|
|
host, remotePath, localPath, swap, err := ParseScpArgs(options)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dst, uri, err := Validate(options.User, host, options.Port, options.Identity)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
scp, err := exec.LookPath("scp")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
conf, err := config.Default()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
args := []string{}
|
|
if len(dst.Identity) > 0 {
|
|
args = append(args, "-i", dst.Identity)
|
|
}
|
|
if len(conf.Engine.SSHConfig) > 0 {
|
|
args = append(args, "-F", conf.Engine.SSHConfig)
|
|
}
|
|
|
|
userString := ""
|
|
if !strings.Contains(host, "@") {
|
|
userString = uri.User.String() + "@"
|
|
}
|
|
// meaning, we are copying from a remote host
|
|
if swap {
|
|
args = append(args, userString+host+":"+remotePath, localPath)
|
|
} else {
|
|
args = append(args, localPath, userString+host+":"+remotePath)
|
|
}
|
|
|
|
info := exec.Command(scp, args...)
|
|
err = info.Run()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &ConnectionScpReport{Response: remotePath}, nil
|
|
}
|