f555e7a9f2
* I had to contextualise Communicator.Start and RemoteCmd.StartWithUi NOTE: Communicator.Start starts a RemoteCmd but RemoteCmd.StartWithUi will run the cmd and wait for a return, so I renamed StartWithUi to RunWithUi so that the intent is clearer. Ideally in the future RunWithUi will be named back to StartWithUi and the exit status or wait funcs of the command will allow to wait for a return. If you do so please read carrefully https://golang.org/pkg/os/exec/#Cmd.Stdout to avoid a deadlock * cmd.ExitStatus to cmd.ExitStatus() is now blocking to avoid race conditions * also had to simplify StartWithUi
182 lines
4.5 KiB
Go
182 lines
4.5 KiB
Go
package lxc
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/hashicorp/packer/packer"
|
|
"github.com/hashicorp/packer/packer/tmp"
|
|
)
|
|
|
|
type LxcAttachCommunicator struct {
|
|
RootFs string
|
|
ContainerName string
|
|
AttachOptions []string
|
|
CmdWrapper CommandWrapper
|
|
}
|
|
|
|
func (c *LxcAttachCommunicator) Start(ctx context.Context, cmd *packer.RemoteCmd) error {
|
|
localCmd, err := c.Execute(cmd.Command)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
localCmd.Stdin = cmd.Stdin
|
|
localCmd.Stdout = cmd.Stdout
|
|
localCmd.Stderr = cmd.Stderr
|
|
if err := localCmd.Start(); err != nil {
|
|
return err
|
|
}
|
|
|
|
go func() {
|
|
exitStatus := 0
|
|
if err := localCmd.Wait(); err != nil {
|
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
exitStatus = 1
|
|
|
|
// There is no process-independent way to get the REAL
|
|
// exit status so we just try to go deeper.
|
|
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
|
exitStatus = status.ExitStatus()
|
|
}
|
|
}
|
|
}
|
|
|
|
log.Printf(
|
|
"lxc-attach execution exited with '%d': '%s'",
|
|
exitStatus, cmd.Command)
|
|
cmd.SetExited(exitStatus)
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *LxcAttachCommunicator) Upload(dst string, r io.Reader, fi *os.FileInfo) error {
|
|
log.Printf("Uploading to rootfs: %s", dst)
|
|
tf, err := tmp.File("packer-lxc-attach")
|
|
if err != nil {
|
|
return fmt.Errorf("Error uploading file to rootfs: %s", err)
|
|
}
|
|
defer os.Remove(tf.Name())
|
|
io.Copy(tf, r)
|
|
|
|
attachCommand := []string{"cat", "%s", " | ", "lxc-attach"}
|
|
attachCommand = append(attachCommand, c.AttachOptions...)
|
|
attachCommand = append(attachCommand, []string{"--name", "%s", "--", "/bin/sh -c \"/bin/cat > %s\""}...)
|
|
|
|
cpCmd, err := c.CmdWrapper(fmt.Sprintf(strings.Join(attachCommand, " "), tf.Name(), c.ContainerName, dst))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if fi != nil {
|
|
tfDir := filepath.Dir(tf.Name())
|
|
// rename tempfile to match original file name. This makes sure that if file is being
|
|
// moved into a directory, the filename is preserved instead of a temp name.
|
|
adjustedTempName := filepath.Join(tfDir, (*fi).Name())
|
|
mvCmd, err := c.CmdWrapper(fmt.Sprintf("mv %s %s", tf.Name(), adjustedTempName))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer os.Remove(adjustedTempName)
|
|
ShellCommand(mvCmd).Run()
|
|
// change cpCmd to use new file name as source
|
|
cpCmd, err = c.CmdWrapper(fmt.Sprintf(strings.Join(attachCommand, " "), adjustedTempName, c.ContainerName, dst))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
log.Printf("Running copy command: %s", dst)
|
|
|
|
return ShellCommand(cpCmd).Run()
|
|
}
|
|
|
|
func (c *LxcAttachCommunicator) UploadDir(dst string, src string, exclude []string) error {
|
|
// TODO: remove any file copied if it appears in `exclude`
|
|
dest := filepath.Join(c.RootFs, dst)
|
|
log.Printf("Uploading directory '%s' to rootfs '%s'", src, dest)
|
|
cpCmd, err := c.CmdWrapper(fmt.Sprintf("cp -R %s/. %s", src, dest))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return ShellCommand(cpCmd).Run()
|
|
}
|
|
|
|
func (c *LxcAttachCommunicator) Download(src string, w io.Writer) error {
|
|
src = filepath.Join(c.RootFs, src)
|
|
log.Printf("Downloading from rootfs dir: %s", src)
|
|
f, err := os.Open(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
if _, err := io.Copy(w, f); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *LxcAttachCommunicator) DownloadDir(src string, dst string, exclude []string) error {
|
|
return fmt.Errorf("DownloadDir is not implemented for lxc")
|
|
}
|
|
|
|
func (c *LxcAttachCommunicator) Execute(commandString string) (*exec.Cmd, error) {
|
|
log.Printf("Executing with lxc-attach in container: %s %s %s", c.ContainerName, c.RootFs, commandString)
|
|
|
|
attachCommand := []string{"lxc-attach"}
|
|
attachCommand = append(attachCommand, c.AttachOptions...)
|
|
attachCommand = append(attachCommand, []string{"--name", "%s", "--", "/bin/sh -c \"%s\""}...)
|
|
|
|
command, err := c.CmdWrapper(
|
|
fmt.Sprintf(strings.Join(attachCommand, " "), c.ContainerName, commandString))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
localCmd := ShellCommand(command)
|
|
log.Printf("Executing lxc-attach: %s %#v", localCmd.Path, localCmd.Args)
|
|
|
|
return localCmd, nil
|
|
}
|
|
|
|
func (c *LxcAttachCommunicator) CheckInit() (string, error) {
|
|
log.Printf("Debug runlevel exec")
|
|
localCmd, err := c.Execute("/sbin/runlevel")
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
pr, _ := localCmd.StdoutPipe()
|
|
if err = localCmd.Start(); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
output, err := ioutil.ReadAll(pr)
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
err = localCmd.Wait()
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return strings.TrimSpace(string(output)), nil
|
|
}
|