Merge pull request #24240 from zackattackz/scp-opts

scp: add option types
This commit is contained in:
openshift-merge-bot[bot] 2024-10-15 21:54:38 +00:00 committed by GitHub
commit a38eaa5b31
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 253 additions and 129 deletions

View File

@ -7,6 +7,7 @@ import (
"github.com/containers/common/pkg/ssh"
"github.com/containers/podman/v5/cmd/podman/common"
"github.com/containers/podman/v5/cmd/podman/registry"
"github.com/containers/podman/v5/pkg/domain/entities"
"github.com/spf13/cobra"
)
@ -73,7 +74,11 @@ func scp(cmd *cobra.Command, args []string) (finalErr error) {
}
sshEngine := ssh.DefineMode(sshType)
err = registry.ImageEngine().Scp(registry.Context(), src, dst, parentFlags, quiet, sshEngine)
scpOpts := entities.ImageScpOptions{}
scpOpts.ParentFlags = parentFlags
scpOpts.Quiet = quiet
scpOpts.SSHMode = sshEngine
_, err = registry.ImageEngine().Scp(registry.Context(), src, dst, scpOpts)
if err != nil {
return err
}

View File

@ -713,18 +713,21 @@ func ImageScp(w http.ResponseWriter, r *http.Request) {
sourceArg := utils.GetName(r)
rep, source, dest, _, err := domainUtils.ExecuteTransfer(sourceArg, query.Destination, []string{}, query.Quiet, ssh.GolangMode)
opts := entities.ScpExecuteTransferOptions{}
opts.Quiet = query.Quiet
opts.SSHMode = ssh.GolangMode
report, err := domainUtils.ExecuteTransfer(sourceArg, query.Destination, opts)
if err != nil {
utils.Error(w, http.StatusInternalServerError, err)
return
}
if source != nil || dest != nil {
if report.Source != nil || report.Dest != nil {
utils.Error(w, http.StatusBadRequest, fmt.Errorf("cannot use the user transfer function on the remote client: %w", define.ErrInvalidArg))
return
}
utils.WriteResponse(w, http.StatusOK, &reports.ScpReport{Id: rep.Names[0]})
utils.WriteResponse(w, http.StatusOK, &reports.ScpReport{Id: report.LoadReport.Names[0]})
}
// Resolve the passed (short) name to one more candidates it may resolve to.

View File

@ -5,7 +5,6 @@ import (
"github.com/containers/common/libimage/define"
"github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/ssh"
"github.com/containers/podman/v5/pkg/domain/entities/reports"
)
@ -24,7 +23,7 @@ type ImageEngine interface { //nolint:interfacebloat
Push(ctx context.Context, source string, destination string, opts ImagePushOptions) (*ImagePushReport, error)
Remove(ctx context.Context, images []string, opts ImageRemoveOptions) (*ImageRemoveReport, []error)
Save(ctx context.Context, nameOrID string, tags []string, options ImageSaveOptions) error
Scp(ctx context.Context, src, dst string, parentFlags []string, quiet bool, sshMode ssh.EngineMode) error
Scp(ctx context.Context, src, dst string, opts ImageScpOptions) (*ImageScpReport, error)
Search(ctx context.Context, term string, opts ImageSearchOptions) ([]ImageSearchReport, error)
SetTrust(ctx context.Context, args []string, options SetTrustOptions) error
ShowTrust(ctx context.Context, args []string, options ShowTrustOptions) (*ShowTrustReport, error)

View File

@ -305,22 +305,14 @@ type ImageSaveOptions struct {
SignaturePolicy string
}
// ImageScpOptions provide options for securely copying images to and from a remote host
// ImageScpOptions provides options for ImageEngine.Scp()
type ImageScpOptions struct {
// Remote determines if this entity is operating on a remote machine
Remote bool `json:"remote,omitempty"`
// File is the input/output file for the save and load Operation
File string `json:"file,omitempty"`
// Quiet Determines if the save and load operation will be done quietly
Quiet bool `json:"quiet,omitempty"`
// Image is the image the user is providing to save and load
Image string `json:"image,omitempty"`
// User is used in conjunction with Transfer to determine if a valid user was given to save from/load into
User string `json:"user,omitempty"`
// Tag is the name to be used for the image on the destination
Tag string `json:"tag,omitempty"`
ScpExecuteTransferOptions
}
// ImageScpReport provides results from ImageEngine.Scp()
type ImageScpReport struct{}
// ImageScpConnections provides the ssh related information used in remote image transfer
type ImageScpConnections struct {
// Connections holds the raw string values for connections (ssh or unix)

View File

@ -0,0 +1,97 @@
package entities
import (
"net/url"
"github.com/containers/common/pkg/ssh"
)
// ScpTransferImageOptions provide options for securely copying images to and from a remote host
type ScpTransferImageOptions struct {
// Remote determines if this entity is operating on a remote machine
Remote bool `json:"remote,omitempty"`
// File is the input/output file for the save and load Operation
File string `json:"file,omitempty"`
// Quiet Determines if the save and load operation will be done quietly
Quiet bool `json:"quiet,omitempty"`
// Image is the image the user is providing to save and load
Image string `json:"image,omitempty"`
// User is used in conjunction with Transfer to determine if a valid user was given to save from/load into
User string `json:"user,omitempty"`
// Tag is the name to be used for the image on the destination
Tag string `json:"tag,omitempty"`
}
type ScpLoadReport = ImageLoadReport
type ScpExecuteTransferOptions struct {
// ParentFlags are the arguments to apply to the parent podman command when called via ssh
ParentFlags []string
// Quiet Determines if the save and load operation will be done quietly
Quiet bool
// SSHMode is the specified ssh.EngineMode which should be used
SSHMode ssh.EngineMode
}
type ScpExecuteTransferReport struct {
// LoadReport provides results from calling podman load
LoadReport *ScpLoadReport
// Source contains data relating to the source of the image to transfer
Source *ScpTransferImageOptions
// Dest contains data relating to the destination of the image to transfer
Dest *ScpTransferImageOptions
// ParentFlags are the arguments to apply to the parent podman command when called via ssh
ParentFlags []string
}
type ScpTransferOptions struct {
// ParentFlags are the arguments to apply to the parent podman command when called.
ParentFlags []string
}
type ScpTransferReport struct{}
type ScpLoadToRemoteOptions struct {
// Dest contains data relating to the destination of the image to transfer
Dest ScpTransferImageOptions
// LocalFile is a path to a local file containing saved image data to transfer
LocalFile string
// Tag is the name of the tag to be given to the loaded image (unused)
Tag string
// URL points to the remote location for loading to
URL *url.URL
// Iden is a path to an optional identity file with ssh key
Iden string
// SSHMode is the specified ssh.EngineMode which should be used
SSHMode ssh.EngineMode
}
type ScpLoadToRemoteReport struct {
// Response contains any additional information from the executed load command
Response string
// ID is the identifier of the loaded image
ID string
}
type ScpSaveToRemoteOptions struct {
Image string
// LocalFile is a path to a local file to copy the saved image to
LocalFile string
// Tag is the name of the tag to be given to the saved image (unused)
Tag string
// URL points to the remote location for saving from
URL *url.URL
// Iden is a path to an optional identity file with ssh key
Iden string
// SSHMode is the specified ssh.EngineMode which should be used
SSHMode ssh.EngineMode
}
type ScpSaveToRemoteReport struct{}
type ScpCreateCommandsOptions struct {
// ParentFlags are the arguments to apply to the parent podman command when called via ssh
ParentFlags []string
// Podman is the path to the local podman executable
Podman string
}

View File

@ -23,7 +23,6 @@ import (
"github.com/containers/common/libimage"
"github.com/containers/common/libimage/filter"
"github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/ssh"
"github.com/containers/image/v5/docker"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/manifest"
@ -772,36 +771,39 @@ func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entitie
return nil, nil
}
func (ir *ImageEngine) Scp(ctx context.Context, src, dst string, parentFlags []string, quiet bool, sshMode ssh.EngineMode) error {
rep, source, dest, flags, err := domainUtils.ExecuteTransfer(src, dst, parentFlags, quiet, sshMode)
func (ir *ImageEngine) Scp(ctx context.Context, src, dst string, opts entities.ImageScpOptions) (*entities.ImageScpReport, error) {
report, err := domainUtils.ExecuteTransfer(src, dst, opts.ScpExecuteTransferOptions)
if err != nil {
return err
return nil, err
}
if (rep == nil && err == nil) && (source != nil && dest != nil) { // we need to execute the transfer
err := Transfer(ctx, *source, *dest, flags)
if (report.LoadReport == nil && err == nil) && (report.Source != nil && report.Dest != nil) { // we need to execute the transfer
transferOpts := entities.ScpTransferOptions{}
transferOpts.ParentFlags = report.ParentFlags
_, err := Transfer(ctx, *report.Source, *report.Dest, transferOpts)
if err != nil {
return err
return nil, err
}
}
return nil
return &entities.ImageScpReport{}, nil
}
func Transfer(ctx context.Context, source entities.ImageScpOptions, dest entities.ImageScpOptions, parentFlags []string) error {
func Transfer(ctx context.Context, source entities.ScpTransferImageOptions, dest entities.ScpTransferImageOptions, opts entities.ScpTransferOptions) (*entities.ScpTransferReport, error) {
if source.User == "" {
return fmt.Errorf("you must define a user when transferring from root to rootless storage: %w", define.ErrInvalidArg)
return nil, fmt.Errorf("you must define a user when transferring from root to rootless storage: %w", define.ErrInvalidArg)
}
podman, err := os.Executable()
if err != nil {
return err
return nil, err
}
rep := entities.ScpTransferReport{}
if rootless.IsRootless() && (len(dest.User) == 0 || dest.User == "root") { // if we are rootless and do not have a destination user we can just use sudo
return transferRootless(source, dest, podman, parentFlags)
return &rep, transferRootless(source, dest, podman, opts.ParentFlags)
}
return transferRootful(source, dest, podman, parentFlags)
return &rep, transferRootful(source, dest, podman, opts.ParentFlags)
}
// TransferRootless creates new podman processes using exec.Command and sudo, transferring images between the given source and destination users
func transferRootless(source entities.ImageScpOptions, dest entities.ImageScpOptions, podman string, parentFlags []string) error {
func transferRootless(source entities.ScpTransferImageOptions, dest entities.ScpTransferImageOptions, podman string, parentFlags []string) error {
var cmdSave *exec.Cmd
saveCommand, loadCommand := parentFlags, parentFlags
saveCommand = append(saveCommand, []string{"save"}...)
@ -842,7 +844,7 @@ func transferRootless(source entities.ImageScpOptions, dest entities.ImageScpOpt
}
// transferRootful creates new podman processes using exec.Command and a new uid/gid alongside a cleared environment
func transferRootful(source entities.ImageScpOptions, dest entities.ImageScpOptions, podman string, parentFlags []string) error {
func transferRootful(source entities.ScpTransferImageOptions, dest entities.ScpTransferImageOptions, podman string, parentFlags []string) error {
basicCommand := make([]string, 0, len(parentFlags)+1)
basicCommand = append(basicCommand, podman)
basicCommand = append(basicCommand, parentFlags...)

View File

@ -12,7 +12,6 @@ import (
bdefine "github.com/containers/buildah/define"
"github.com/containers/common/libimage/filter"
"github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/ssh"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/types"
"github.com/containers/podman/v5/libpod/define"
@ -415,22 +414,22 @@ func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entitie
return nil, errors.New("not implemented yet")
}
func (ir *ImageEngine) Scp(ctx context.Context, src, dst string, parentFlags []string, quiet bool, sshMode ssh.EngineMode) error {
func (ir *ImageEngine) Scp(ctx context.Context, src, dst string, opts entities.ImageScpOptions) (*entities.ImageScpReport, error) {
options := new(images.ScpOptions)
var destination *string
if len(dst) > 1 {
destination = &dst
}
options.Quiet = &quiet
options.Quiet = &opts.Quiet
options.Destination = destination
rep, err := images.Scp(ir.ClientCtx, &src, destination, *options)
if err != nil {
return err
return nil, err
}
fmt.Println("Loaded Image(s):", rep.Id)
return nil
return &entities.ImageScpReport{}, nil
}

View File

@ -17,23 +17,23 @@ import (
"github.com/sirupsen/logrus"
)
func ExecuteTransfer(src, dst string, parentFlags []string, quiet bool, sshMode ssh.EngineMode) (*entities.ImageLoadReport, *entities.ImageScpOptions, *entities.ImageScpOptions, []string, error) {
source := entities.ImageScpOptions{}
dest := entities.ImageScpOptions{}
func ExecuteTransfer(src, dst string, opts entities.ScpExecuteTransferOptions) (*entities.ScpExecuteTransferReport, error) {
source := entities.ScpTransferImageOptions{}
dest := entities.ScpTransferImageOptions{}
sshInfo := entities.ImageScpConnections{}
report := entities.ImageLoadReport{Names: []string{}}
loadReport := entities.ScpLoadReport{Names: []string{}}
podman, err := os.Executable()
if err != nil {
return nil, nil, nil, nil, err
return nil, err
}
f, err := os.CreateTemp("", "podman") // open temp file for load/save output
if err != nil {
return nil, nil, nil, nil, err
return nil, err
}
locations := []*entities.ImageScpOptions{}
locations := []*entities.ScpTransferImageOptions{}
cliConnections := []string{}
args := []string{src}
if len(dst) > 0 {
@ -42,7 +42,7 @@ func ExecuteTransfer(src, dst string, parentFlags []string, quiet bool, sshMode
for _, arg := range args {
loc, connect, err := ParseImageSCPArg(arg)
if err != nil {
return nil, nil, nil, nil, err
return nil, err
}
locations = append(locations, loc)
cliConnections = append(cliConnections, connect...)
@ -51,19 +51,19 @@ func ExecuteTransfer(src, dst string, parentFlags []string, quiet bool, sshMode
switch {
case len(locations) > 1:
if err = ValidateSCPArgs(locations); err != nil {
return nil, nil, nil, nil, err
return nil, err
}
dest = *locations[1]
case len(locations) == 1:
switch {
case len(locations[0].Image) == 0:
return nil, nil, nil, nil, fmt.Errorf("no source image specified: %w", define.ErrInvalidArg)
return nil, fmt.Errorf("no source image specified: %w", define.ErrInvalidArg)
case len(locations[0].Image) > 0 && !locations[0].Remote && len(locations[0].User) == 0: // if we have podman image scp $IMAGE
return nil, nil, nil, nil, fmt.Errorf("must specify a destination: %w", define.ErrInvalidArg)
return nil, fmt.Errorf("must specify a destination: %w", define.ErrInvalidArg)
}
}
source.Quiet = quiet
source.Quiet = opts.Quiet
source.File = f.Name() // after parsing the arguments, set the file for the save/load
dest.File = source.File
defer os.Remove(source.File)
@ -81,59 +81,83 @@ func ExecuteTransfer(src, dst string, parentFlags []string, quiet bool, sshMode
cfg, err := config.Default()
if err != nil {
return nil, nil, nil, nil, err
return nil, err
}
err = GetServiceInformation(&sshInfo, cliConnections, cfg)
if err != nil {
return nil, nil, nil, nil, err
return nil, err
}
saveCmd, loadCmd := CreateCommands(source, dest, parentFlags, podman)
createCommandOpts := entities.ScpCreateCommandsOptions{}
createCommandOpts.ParentFlags = opts.ParentFlags
createCommandOpts.Podman = podman
saveCmd, loadCmd := CreateCommands(source, dest, createCommandOpts)
switch {
case source.Remote: // if we want to load FROM the remote, dest can either be local or remote in this case
err = SaveToRemote(source.Image, source.File, "", sshInfo.URI[0], sshInfo.Identities[0], sshMode)
saveToRemoteOpts := entities.ScpSaveToRemoteOptions{}
saveToRemoteOpts.Image = source.Image
saveToRemoteOpts.LocalFile = source.File
saveToRemoteOpts.Tag = ""
saveToRemoteOpts.URL = sshInfo.URI[0]
saveToRemoteOpts.Iden = sshInfo.Identities[0]
saveToRemoteOpts.SSHMode = opts.SSHMode
_, err = SaveToRemote(saveToRemoteOpts)
if err != nil {
return nil, nil, nil, nil, err
return nil, err
}
if dest.Remote { // we want to load remote -> remote, both source and dest are remote
rep, id, err := LoadToRemote(dest, dest.File, "", sshInfo.URI[1], sshInfo.Identities[1], sshMode)
loadToRemoteOpts := entities.ScpLoadToRemoteOptions{}
loadToRemoteOpts.Dest = dest
loadToRemoteOpts.LocalFile = dest.File
loadToRemoteOpts.Tag = ""
loadToRemoteOpts.URL = sshInfo.URI[1]
loadToRemoteOpts.Iden = sshInfo.Identities[1]
loadToRemoteOpts.SSHMode = opts.SSHMode
loadToRemoteRep, err := LoadToRemote(loadToRemoteOpts)
if err != nil {
return nil, nil, nil, nil, err
return nil, err
}
if len(rep) > 0 {
fmt.Println(rep)
if len(loadToRemoteRep.Response) > 0 {
fmt.Println(loadToRemoteRep.Response)
}
if len(id) > 0 {
report.Names = append(report.Names, id)
if len(loadToRemoteRep.ID) > 0 {
loadReport.Names = append(loadReport.Names, loadToRemoteRep.ID)
}
break
}
id, err := ExecPodman(dest, podman, loadCmd)
if err != nil {
return nil, nil, nil, nil, err
return nil, err
}
if len(id) > 0 {
report.Names = append(report.Names, id)
loadReport.Names = append(loadReport.Names, id)
}
case dest.Remote: // remote host load, implies source is local
_, err = ExecPodman(dest, podman, saveCmd)
if err != nil {
return nil, nil, nil, nil, err
return nil, err
}
rep, id, err := LoadToRemote(dest, source.File, "", sshInfo.URI[0], sshInfo.Identities[0], sshMode)
loadToRemoteOpts := entities.ScpLoadToRemoteOptions{}
loadToRemoteOpts.Dest = dest
loadToRemoteOpts.LocalFile = source.File
loadToRemoteOpts.Tag = ""
loadToRemoteOpts.URL = sshInfo.URI[0]
loadToRemoteOpts.Iden = sshInfo.Identities[0]
loadToRemoteOpts.SSHMode = opts.SSHMode
loadToRemoteRep, err := LoadToRemote(loadToRemoteOpts)
if err != nil {
return nil, nil, nil, nil, err
return nil, err
}
if len(rep) > 0 {
fmt.Println(rep)
if len(loadToRemoteRep.Response) > 0 {
fmt.Println(loadToRemoteRep.Response)
}
if len(id) > 0 {
report.Names = append(report.Names, id)
if len(loadToRemoteRep.ID) > 0 {
loadReport.Names = append(loadReport.Names, loadToRemoteRep.ID)
}
if err = os.Remove(source.File); err != nil {
return nil, nil, nil, nil, err
return nil, err
}
default: // else native load, both source and dest are local and transferring between users
if source.User == "" { // source user has to be set, destination does not
@ -141,15 +165,21 @@ func ExecuteTransfer(src, dst string, parentFlags []string, quiet bool, sshMode
if source.User == "" {
u, err := user.Current()
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("could not obtain user, make sure the environmental variable $USER is set: %w", err)
return nil, fmt.Errorf("could not obtain user, make sure the environmental variable $USER is set: %w", err)
}
source.User = u.Username
}
}
return nil, &source, &dest, parentFlags, nil // transfer needs to be done in ABI due to cross issues
rep := entities.ScpExecuteTransferReport{}
rep.Source = &source
rep.Dest = &dest
rep.ParentFlags = opts.ParentFlags
return &rep, nil // transfer needs to be done in ABI due to cross issues
}
return &report, nil, nil, nil, nil
rep := entities.ScpExecuteTransferReport{}
rep.LoadReport = &loadReport
return &rep, nil
}
// CreateSCPCommand takes an existing command, appends the given arguments and returns a configured podman command for image scp
@ -162,7 +192,7 @@ func CreateSCPCommand(cmd *exec.Cmd, command []string) *exec.Cmd {
}
// ScpTag is a helper function for native podman to tag an image after a local load from image SCP
func ScpTag(cmd *exec.Cmd, podman string, dest entities.ImageScpOptions) error {
func ScpTag(cmd *exec.Cmd, podman string, dest entities.ScpTransferImageOptions) error {
cmd.Stdout = nil
out, err := cmd.Output() // this function captures the output temporarily in order to execute the next command
if err != nil {
@ -201,91 +231,88 @@ func LoginUser(user string) (*exec.Cmd, error) {
return cmd, err
}
// loadToRemote takes image and remote connection information. it connects to the specified client
// LoadToRemote takes image and remote connection information. it connects to the specified client
// and copies the saved image dir over to the remote host and then loads it onto the machine
// returns a string containing output or an error
func LoadToRemote(dest entities.ImageScpOptions, localFile string, tag string, url *url.URL, iden string, sshEngine ssh.EngineMode) (string, string, error) {
// returns a report containing ssh response string and the id of the loaded image, or an error
func LoadToRemote(opts entities.ScpLoadToRemoteOptions) (*entities.ScpLoadToRemoteReport, error) {
port := 0
urlPort := url.Port()
urlPort := opts.URL.Port()
if urlPort != "" {
var err error
port, err = strconv.Atoi(url.Port())
port, err = strconv.Atoi(opts.URL.Port())
if err != nil {
return "", "", err
return nil, err
}
}
input, err := os.Open(localFile)
input, err := os.Open(opts.LocalFile)
if err != nil {
return "", "", err
return nil, err
}
defer input.Close()
out, err := ssh.ExecWithInput(&ssh.ConnectionExecOptions{Host: url.String(), Identity: iden, Port: port, User: url.User, Args: []string{"podman", "image", "load"}}, sshEngine, input)
out, err := ssh.ExecWithInput(&ssh.ConnectionExecOptions{Host: opts.URL.String(), Identity: opts.Iden, Port: port, User: opts.URL.User, Args: []string{"podman", "image", "load"}}, opts.SSHMode, input)
if err != nil {
return "", "", err
return nil, err
}
if tag != "" {
return "", "", fmt.Errorf("renaming of an image is currently not supported: %w", define.ErrInvalidArg)
if opts.Tag != "" {
return nil, fmt.Errorf("renaming of an image is currently not supported: %w", define.ErrInvalidArg)
}
rep := strings.TrimSuffix(out, "\n")
outArr := strings.Split(rep, " ")
id := outArr[len(outArr)-1]
if len(dest.Tag) > 0 { // tag the remote image using the output ID
_, err := ssh.Exec(&ssh.ConnectionExecOptions{Host: url.String(), Identity: iden, Port: port, User: url.User, Args: []string{"podman", "image", "tag", id, dest.Tag}}, sshEngine)
if len(opts.Dest.Tag) > 0 { // tag the remote image using the output ID
_, err := ssh.Exec(&ssh.ConnectionExecOptions{Host: opts.URL.String(), Identity: opts.Iden, Port: port, User: opts.URL.User, Args: []string{"podman", "image", "tag", id, opts.Dest.Tag}}, opts.SSHMode)
if err != nil {
return "", "", err
}
if err != nil {
return "", "", err
return nil, err
}
}
return rep, id, nil
return &entities.ScpLoadToRemoteReport{Response: rep, ID: id}, nil
}
// saveToRemote takes image information and remote connection information. it connects to the specified client
// SaveToRemote takes image information and remote connection information. it connects to the specified client
// and saves the specified image on the remote machine and then copies it to the specified local location
// returns an error if one occurs.
func SaveToRemote(image, localFile string, tag string, uri *url.URL, iden string, sshEngine ssh.EngineMode) error {
if tag != "" {
return fmt.Errorf("renaming of an image is currently not supported: %w", define.ErrInvalidArg)
func SaveToRemote(opts entities.ScpSaveToRemoteOptions) (*entities.ScpSaveToRemoteReport, error) {
if opts.Tag != "" {
return nil, fmt.Errorf("renaming of an image is currently not supported: %w", define.ErrInvalidArg)
}
port := 0
urlPort := uri.Port()
urlPort := opts.URL.Port()
if urlPort != "" {
var err error
port, err = strconv.Atoi(uri.Port())
port, err = strconv.Atoi(opts.URL.Port())
if err != nil {
return err
return nil, err
}
}
remoteFile, err := ssh.Exec(&ssh.ConnectionExecOptions{Host: uri.String(), Identity: iden, Port: port, User: uri.User, Args: []string{"mktemp"}}, sshEngine)
remoteFile, err := ssh.Exec(&ssh.ConnectionExecOptions{Host: opts.URL.String(), Identity: opts.Iden, Port: port, User: opts.URL.User, Args: []string{"mktemp"}}, opts.SSHMode)
if err != nil {
return err
return nil, err
}
_, err = ssh.Exec(&ssh.ConnectionExecOptions{Host: uri.String(), Identity: iden, Port: port, User: uri.User, Args: []string{"podman", "image", "save", image, "--format", "oci-archive", "--output", remoteFile}}, sshEngine)
_, err = ssh.Exec(&ssh.ConnectionExecOptions{Host: opts.URL.String(), Identity: opts.Iden, Port: port, User: opts.URL.User, Args: []string{"podman", "image", "save", opts.Image, "--format", "oci-archive", "--output", remoteFile}}, opts.SSHMode)
if err != nil {
return err
return nil, err
}
opts := ssh.ConnectionScpOptions{User: uri.User, Identity: iden, Port: port, Source: "ssh://" + uri.User.String() + "@" + uri.Hostname() + ":" + remoteFile, Destination: localFile}
scpRep, err := ssh.Scp(&opts, sshEngine)
scpConnOpts := ssh.ConnectionScpOptions{User: opts.URL.User, Identity: opts.Iden, Port: port, Source: "ssh://" + opts.URL.User.String() + "@" + opts.URL.Hostname() + ":" + remoteFile, Destination: opts.LocalFile}
scpRep, err := ssh.Scp(&scpConnOpts, opts.SSHMode)
if err != nil {
return err
return nil, err
}
_, err = ssh.Exec(&ssh.ConnectionExecOptions{Host: uri.String(), Identity: iden, Port: port, User: uri.User, Args: []string{"rm", scpRep}}, sshEngine)
_, err = ssh.Exec(&ssh.ConnectionExecOptions{Host: opts.URL.String(), Identity: opts.Iden, Port: port, User: opts.URL.User, Args: []string{"rm", scpRep}}, opts.SSHMode)
if err != nil {
logrus.Errorf("Removing file on endpoint: %v", err)
}
return nil
return &entities.ScpSaveToRemoteReport{}, nil
}
// execPodman executes the podman save/load command given the podman binary
func ExecPodman(dest entities.ImageScpOptions, podman string, command []string) (string, error) {
func ExecPodman(dest entities.ScpTransferImageOptions, podman string, command []string) (string, error) {
cmd := exec.Command(podman)
CreateSCPCommand(cmd, command[1:])
logrus.Debugf("Executing podman command: %q", cmd)
@ -304,27 +331,27 @@ func ExecPodman(dest entities.ImageScpOptions, podman string, command []string)
return "", cmd.Run()
}
// createCommands forms the podman save and load commands used by SCP
func CreateCommands(source entities.ImageScpOptions, dest entities.ImageScpOptions, parentFlags []string, podman string) ([]string, []string) {
// CreateCommands forms the podman save and load commands used by SCP
func CreateCommands(source entities.ScpTransferImageOptions, dest entities.ScpTransferImageOptions, opts entities.ScpCreateCommandsOptions) ([]string, []string) {
var parentString string
quiet := ""
if source.Quiet {
quiet = "-q "
}
if len(parentFlags) > 0 {
parentString = strings.Join(parentFlags, " ") + " " // if there are parent args, an extra space needs to be added
if len(opts.ParentFlags) > 0 {
parentString = strings.Join(opts.ParentFlags, " ") + " " // if there are parent args, an extra space needs to be added
} else {
parentString = strings.Join(parentFlags, " ")
parentString = strings.Join(opts.ParentFlags, " ")
}
loadCmd := strings.Split(fmt.Sprintf("%s %sload %s--input %s", podman, parentString, quiet, dest.File), " ")
saveCmd := strings.Split(fmt.Sprintf("%s %vsave %s--output %s %s", podman, parentString, quiet, source.File, source.Image), " ")
loadCmd := strings.Split(fmt.Sprintf("%s %sload %s--input %s", opts.Podman, parentString, quiet, dest.File), " ")
saveCmd := strings.Split(fmt.Sprintf("%s %vsave %s--output %s %s", opts.Podman, parentString, quiet, source.File, source.Image), " ")
return saveCmd, loadCmd
}
// parseImageSCPArg returns the valid connection, and source/destination data based off of the information provided by the user
// arg is a string containing one of the cli arguments returned is a filled out source/destination options structs as well as a connections array and an error if applicable
func ParseImageSCPArg(arg string) (*entities.ImageScpOptions, []string, error) {
location := entities.ImageScpOptions{}
func ParseImageSCPArg(arg string) (*entities.ScpTransferImageOptions, []string, error) {
location := entities.ScpTransferImageOptions{}
var err error
cliConnections := []string{}
@ -349,7 +376,7 @@ func ParseImageSCPArg(arg string) (*entities.ImageScpOptions, []string, error) {
return &location, cliConnections, nil
}
func ValidateImagePortion(location entities.ImageScpOptions, arg string) (entities.ImageScpOptions, error) {
func ValidateImagePortion(location entities.ScpTransferImageOptions, arg string) (entities.ScpTransferImageOptions, error) {
if RemoteArgLength(arg, 1) > 0 {
before := strings.Split(arg, "::")[1]
name := ValidateImageName(before)
@ -376,7 +403,7 @@ func ValidateImageName(input string) string {
}
// validateSCPArgs takes the array of source and destination options and checks for common errors
func ValidateSCPArgs(locations []*entities.ImageScpOptions) error {
func ValidateSCPArgs(locations []*entities.ScpTransferImageOptions) error {
if len(locations) > 2 {
return fmt.Errorf("cannot specify more than two arguments: %w", define.ErrInvalidArg)
}

View File

@ -10,7 +10,7 @@ import (
func TestValidateSCPArgs(t *testing.T) {
type args struct {
locations []*entities.ImageScpOptions
locations []*entities.ScpTransferImageOptions
}
tests := []struct {
name string
@ -20,7 +20,7 @@ func TestValidateSCPArgs(t *testing.T) {
{
name: "test args length more than 2",
args: args{
locations: []*entities.ImageScpOptions{
locations: []*entities.ScpTransferImageOptions{
{
Image: "source image one",
},
@ -40,7 +40,7 @@ func TestValidateSCPArgs(t *testing.T) {
{
name: "test source image is empty",
args: args{
locations: []*entities.ImageScpOptions{
locations: []*entities.ScpTransferImageOptions{
{
Image: "",
},
@ -54,7 +54,7 @@ func TestValidateSCPArgs(t *testing.T) {
{
name: "test target image is empty",
args: args{
locations: []*entities.ImageScpOptions{
locations: []*entities.ScpTransferImageOptions{
{
Image: "source image",
},

View File

@ -78,8 +78,8 @@ func TestToURLValues(t *testing.T) {
func TestParseSCPArgs(t *testing.T) {
args := []string{"alpine", "root@localhost::"}
var source *entities.ImageScpOptions
var dest *entities.ImageScpOptions
var source *entities.ScpTransferImageOptions
var dest *entities.ScpTransferImageOptions
var err error
source, _, err = ParseImageSCPArg(args[0])
assert.Nil(t, err)