Skip to content

Commit

Permalink
feat(climc,host): support container copy and copy-from (#21822)
Browse files Browse the repository at this point in the history
  • Loading branch information
zexi authored Dec 18, 2024
1 parent 4132f24 commit c2bc6b1
Show file tree
Hide file tree
Showing 7 changed files with 512 additions and 12 deletions.
51 changes: 51 additions & 0 deletions cmd/climc/shell/compute/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"io/ioutil"
"os"
"os/exec"
"strings"

"github.com/ghodss/yaml"

Expand All @@ -30,6 +31,7 @@ import (
"yunion.io/x/onecloud/pkg/mcclient"
modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
options "yunion.io/x/onecloud/pkg/mcclient/options/compute"
"yunion.io/x/onecloud/pkg/util/pod/stream/cp"
)

func init() {
Expand Down Expand Up @@ -133,4 +135,53 @@ func init() {
}
return nil
})

R(new(options.ContainerCopyOptions), "container-cp", "Container copy", func(s *mcclient.ClientSession, opts *options.ContainerCopyOptions) error {
parts := strings.Split(opts.CONTAINER_ID_FILE, ":")
if len(parts) != 2 {
return fmt.Errorf("invalid container id: %s", opts.CONTAINER_ID_FILE)
}
if opts.RawFile {
fr, err := os.Open(opts.SRC_FILE)
if err != nil {
return errors.Wrapf(err, "open file: %v", opts.SRC_FILE)
}
defer fr.Close()
if err := modules.Containers.CopyTo(s, parts[0], parts[1], fr); err != nil {
return errors.Wrapf(err, "copy file to container")
}
return nil
} else {
return cp.NewCopy().CopyToContainer(s, opts.SRC_FILE, cp.ContainerFileOpt{
ContainerId: parts[0],
File: parts[1],
})
}
})

R(new(options.ContainerCopyOptions), "container-cp-from", "Container copy", func(s *mcclient.ClientSession, opts *options.ContainerCopyOptions) error {
parts := strings.Split(opts.SRC_FILE, ":")
if len(parts) != 2 {
return fmt.Errorf("invalid container id: %s", opts.CONTAINER_ID_FILE)
}
ctrId := parts[0]
ctrFile := parts[1]
destFile := opts.CONTAINER_ID_FILE
if opts.RawFile {
fw, err := os.Create(destFile)
if err != nil {
return errors.Wrapf(err, "open file: %v", destFile)
}
defer fw.Close()
if err := modules.Containers.CopyFrom(s, ctrId, ctrFile, fw); err != nil {
return errors.Wrap(err, "copy from")
}
return nil
} else {
return cp.NewCopy().CopyFromContainer(s, cp.ContainerFileOpt{
ContainerId: ctrId,
File: ctrFile,
}, destFile)
}
})
}
3 changes: 3 additions & 0 deletions pkg/apis/compute/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ type ContainerExecInfoOutput struct {
type ContainerExecInput struct {
Command []string `json:"command"`
Tty bool `json:"tty"`
SetIO bool `json:"set_io"`
Stdin bool `json:"stdin"`
Stdout bool `json:"stdout"`
}

type ContainerExecSyncInput struct {
Expand Down
10 changes: 9 additions & 1 deletion pkg/hostman/guestman/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -2085,13 +2085,21 @@ func (s *sPodGuestInstance) ExecContainer(ctx context.Context, userCred mcclient
if err != nil {
return nil, errors.Wrap(err, "get container cri id")
}
stderr := true
if input.Tty {
stderr = false
}
req := &runtimeapi.ExecRequest{
ContainerId: criId,
Cmd: input.Command,
Tty: input.Tty,
Stdin: true,
Stdout: true,
//Stderr: true,
Stderr: stderr,
}
if input.SetIO {
req.Stdin = input.Stdin
req.Stdout = input.Stdout
}
resp, err := rCli.Exec(ctx, req)
if err != nil {
Expand Down
132 changes: 121 additions & 11 deletions pkg/mcclient/modules/compute/mod_containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import (
"io"
"net/url"
"os"
"path"
"time"

"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/httputils"

Expand All @@ -36,18 +38,32 @@ import (
"yunion.io/x/onecloud/pkg/util/pod/term"
)

var (
Containers ContainerManager
)

func init() {
Containers = ContainerManager{
modules.NewComputeManager("container", "containers",
[]string{"ID", "Name", "Guest_ID", "Status", "Started_At", "Last_Finished_At", "Restart_Count", "Spec"},
[]string{}),
}
modules.RegisterCompute(&Containers)
}

type ContainerManager struct {
modulebase.ResourceManager
}

func (man ContainerManager) SetupTTY(in io.Reader, out io.Writer, errOut io.Writer, raw bool) term.TTY {
/*t := term.TTY{
t := term.TTY{
Out: out,
}
if in == nil {
t.In = nil
t.Raw = false
return t
}*/
}
return term.TTY{
In: in,
Out: out,
Expand Down Expand Up @@ -97,6 +113,54 @@ func (man ContainerManager) Exec(s *mcclient.ClientSession, id string, opt *api.
return t.Safe(fn)
}

type ContainerExecInput struct {
Command []string
Tty bool
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
}

func (man ContainerManager) ExecV2(s *mcclient.ClientSession, id string, opt *ContainerExecInput) error {
info, err := man.GetSpecific(s, id, "exec-info", nil)
if err != nil {
return errors.Wrap(err, "get exec info")
}
infoOut := new(api.ContainerExecInfoOutput)
info.Unmarshal(infoOut)
apiInput := &api.ContainerExecInput{
Command: opt.Command,
Tty: opt.Tty,
SetIO: true,
Stdin: opt.Stdin != nil,
Stdout: opt.Stdout != nil,
}
urlLoc := fmt.Sprintf("%s/pods/%s/containers/%s/exec?%s", infoOut.HostUri, infoOut.PodId, infoOut.ContainerId, jsonutils.Marshal(apiInput).QueryString())
url, err := url.Parse(urlLoc)
if err != nil {
return errors.Wrapf(err, "parse url: %s", urlLoc)
}
exec, err := remotecommand.NewSPDYExecutor("POST", url)
if err != nil {
return errors.Wrap(err, "NewSPDYExecutor")
}
headers := mcclient.GetTokenHeaders(s.GetToken())

t := man.SetupTTY(opt.Stdin, opt.Stdout, opt.Stderr, true)
sizeQueue := t.MonitorSize(t.GetSize())
fn := func() error {
return exec.Stream(remotecommand.StreamOptions{
Stdin: opt.Stdin,
Stdout: opt.Stdout,
Stderr: opt.Stderr,
Tty: opt.Tty,
TerminalSizeQueue: sizeQueue,
Header: headers,
})
}
return t.Safe(fn)
}

func (man ContainerManager) Log(s *mcclient.ClientSession, id string, opt *api.PodLogOptions) (io.ReadCloser, error) {
info, err := man.GetSpecific(s, id, "exec-info", nil)
if err != nil {
Expand Down Expand Up @@ -142,15 +206,61 @@ func (man ContainerManager) LogToWriter(s *mcclient.ClientSession, id string, op
return nil
}

var (
Containers ContainerManager
)
func (man ContainerManager) EnsureDir(s *mcclient.ClientSession, ctrId string, dirName string) error {
opt := &ContainerExecInput{
Command: []string{"mkdir", "-p", dirName},
Tty: false,
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
return man.ExecV2(s, ctrId, opt)
}

func init() {
Containers = ContainerManager{
modules.NewComputeManager("container", "containers",
[]string{"ID", "Name", "Guest_ID", "Status", "Started_At", "Last_Finished_At", "Restart_Count", "Spec"},
[]string{}),
func (man ContainerManager) CopyTo(s *mcclient.ClientSession, ctrId string, destPath string, in io.Reader) error {
destDir := path.Dir(destPath)
if err := man.EnsureDir(s, ctrId, destDir); err != nil {
return errors.Wrapf(err, "ensure dir %s", destDir)
}
modules.RegisterCompute(&Containers)

reader, writer := io.Pipe()
go func() {
defer writer.Close()
written, err := io.Copy(writer, in)
if err != nil {
log.Errorf("copy reader to writer, written %d, error: %v", written, err)
}
}()

ctrCmd := []string{"sh", "-c", fmt.Sprintf("cat - > %s", destPath)}
opt := &ContainerExecInput{
Command: ctrCmd,
Tty: false,
Stdin: reader,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
return man.ExecV2(s, ctrId, opt)
}

func (man ContainerManager) CopyFrom(s *mcclient.ClientSession, ctrId string, ctrFile string, out io.Writer) error {
reader, outStream := io.Pipe()
opts := &ContainerExecInput{
Command: []string{"cat", ctrFile},
Tty: false,
Stdin: nil,
Stdout: outStream,
Stderr: os.Stderr,
}
go func() {
defer outStream.Close()
if err := man.ExecV2(s, ctrId, opts); err != nil {
log.Errorf("compute.Containers.ExecV2: %v", err)
}
}()
written, err := io.Copy(out, reader)
if err != nil {
return errors.Wrapf(err, "copy from reader written: %d", written)
}
return nil
}
6 changes: 6 additions & 0 deletions pkg/mcclient/options/compute/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,3 +497,9 @@ func (o *ContainerRemoveVolumeMountPostOverlayOptions) Params() (jsonutils.JSONO
}
return params, nil
}

type ContainerCopyOptions struct {
SRC_FILE string
CONTAINER_ID_FILE string
RawFile bool
}
Loading

0 comments on commit c2bc6b1

Please sign in to comment.