a27b4b8c |
package docker
import (
"encoding/json" |
7c57a4cf |
"fmt" |
2333be46 |
"github.com/dotcloud/docker/rcli" |
94b1cf4b |
"github.com/kr/pty" |
a27b4b8c |
"io"
"io/ioutil" |
24dac228 |
"log" |
a27b4b8c |
"os"
"os/exec"
"path" |
931ca464 |
"sort" |
09eacdfa |
"strconv" |
931ca464 |
"strings" |
a27b4b8c |
"syscall" |
24dac228 |
"time" |
a27b4b8c |
)
type Container struct { |
7c57a4cf |
root string
Id string |
8e9bb02c |
Created time.Time
|
a27b4b8c |
Path string
Args []string
|
7c57a4cf |
Config *Config
State State
Image string |
a27b4b8c |
|
09eacdfa |
network *NetworkInterface
NetworkSettings *NetworkSettings |
a27b4b8c |
|
1f9f5eed |
SysInitPath string
ResolvConfPath string
cmd *exec.Cmd
stdout *writeBroadcaster
stderr *writeBroadcaster
stdin io.ReadCloser
stdinPipe io.WriteCloser |
7d889554 |
ptyMaster io.Closer |
5252ab69 |
|
7a565a04 |
runtime *Runtime |
329f4449 |
waitLock chan struct{} |
a27b4b8c |
}
type Config struct { |
c04af2a3 |
Hostname string
User string
Memory int64 // Memory limit (in bytes)
MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap
AttachStdin bool
AttachStdout bool
AttachStderr bool |
2aad4a34 |
PortSpecs []string |
c04af2a3 |
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
OpenStdin bool // Open stdin
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
Env []string
Cmd []string |
7673afc8 |
Dns []string |
c04af2a3 |
Image string // Name of the image as it was passed by the operator (eg. could be symbolic) |
031f91df |
}
|
640efc2e |
func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Config, error) { |
2333be46 |
cmd := rcli.Subcmd(stdout, "run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container")
if len(args) > 0 && args[0] != "--help" {
cmd.SetOutput(ioutil.Discard)
}
|
f65fc1e7 |
flHostname := cmd.String("h", "", "Container host name") |
a6da7f13 |
flUser := cmd.String("u", "", "Username or UID")
flDetach := cmd.Bool("d", false, "Detached mode: leave the container running in the background") |
c04af2a3 |
flAttach := NewAttachOpts()
cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.") |
a6da7f13 |
flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)")
|
640efc2e |
if *flMemory > 0 && !capabilities.MemoryLimit {
fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") |
1967c834 |
*flMemory = 0
}
|
2aad4a34 |
var flPorts ListOpts |
b74d1c92 |
cmd.Var(&flPorts, "p", "Expose a container's port to the host (use 'docker port' to see the actual mapping)") |
dcc9dfb2 |
|
a6da7f13 |
var flEnv ListOpts
cmd.Var(&flEnv, "e", "Set environment variables") |
dcc9dfb2 |
|
7673afc8 |
var flDns ListOpts
cmd.Var(&flDns, "dns", "Set custom dns servers")
|
031f91df |
if err := cmd.Parse(args); err != nil { |
6ce64e84 |
return nil, err |
031f91df |
} |
4f36039e |
if *flDetach && len(flAttach) > 0 { |
c04af2a3 |
return nil, fmt.Errorf("Conflicting options: -a and -d")
}
// If neither -d or -a are set, attach to everything by default |
4f36039e |
if len(flAttach) == 0 && !*flDetach { |
c04af2a3 |
if !*flDetach {
flAttach.Set("stdout")
flAttach.Set("stderr")
if *flStdin {
flAttach.Set("stdin")
}
}
} |
c8ca50b4 |
parsedArgs := cmd.Args()
runCmd := []string{}
image := ""
if len(parsedArgs) >= 1 {
image = cmd.Arg(0)
}
if len(parsedArgs) > 1 {
runCmd = parsedArgs[1:]
} |
031f91df |
config := &Config{ |
15c3096e |
Hostname: *flHostname, |
2aad4a34 |
PortSpecs: flPorts, |
c04af2a3 |
User: *flUser,
Tty: *flTty,
OpenStdin: *flStdin,
Memory: *flMemory,
AttachStdin: flAttach.Get("stdin"),
AttachStdout: flAttach.Get("stdout"),
AttachStderr: flAttach.Get("stderr"),
Env: flEnv,
Cmd: runCmd, |
7673afc8 |
Dns: flDns, |
c04af2a3 |
Image: image, |
031f91df |
} |
640efc2e |
if *flMemory > 0 && !capabilities.SwapLimit {
fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
config.MemorySwap = -1
}
|
aea2675f |
// When allocating stdin in attached mode, close stdin at client disconnect |
c04af2a3 |
if config.OpenStdin && config.AttachStdin { |
aea2675f |
config.StdinOnce = true |
031f91df |
} |
6ce64e84 |
return config, nil |
a27b4b8c |
}
|
09eacdfa |
type NetworkSettings struct { |
c08f5b2b |
IpAddress string
IpPrefixLen int |
09eacdfa |
Gateway string |
d9a9bfc9 |
Bridge string |
09eacdfa |
PortMapping map[string]string |
c08f5b2b |
}
|
931ca464 |
// String returns a human-readable description of the port mapping defined in the settings
func (settings *NetworkSettings) PortMappingHuman() string {
var mapping []string
for private, public := range settings.PortMapping {
mapping = append(mapping, fmt.Sprintf("%s->%s", public, private))
}
sort.Strings(mapping)
return strings.Join(mapping, ", ")
}
|
5d6dd22f |
func (container *Container) Cmd() *exec.Cmd {
return container.cmd
}
|
c7a944ca |
func (container *Container) When() time.Time {
return container.Created
}
|
7c57a4cf |
func (container *Container) FromDisk() error {
data, err := ioutil.ReadFile(container.jsonPath()) |
11b65a00 |
if err != nil {
return err
} |
7c57a4cf |
// Load container settings
if err := json.Unmarshal(data, container); err != nil { |
11b65a00 |
return err
} |
7c57a4cf |
return nil |
11b65a00 |
}
|
7c57a4cf |
func (container *Container) ToDisk() (err error) { |
8e9bb02c |
data, err := json.Marshal(container) |
a27b4b8c |
if err != nil { |
8e9bb02c |
return |
a27b4b8c |
} |
7c57a4cf |
return ioutil.WriteFile(container.jsonPath(), data, 0666) |
a27b4b8c |
}
func (container *Container) generateLXCConfig() error { |
7c57a4cf |
fo, err := os.Create(container.lxcConfigPath()) |
a27b4b8c |
if err != nil {
return err
}
defer fo.Close()
if err := LxcTemplateCompiled.Execute(fo, container); err != nil {
return err
}
return nil
}
|
caea45dd |
func (container *Container) startPty() error { |
7d889554 |
ptyMaster, ptySlave, err := pty.Open() |
caea45dd |
if err != nil {
return err
} |
7d889554 |
container.ptyMaster = ptyMaster
container.cmd.Stdout = ptySlave
container.cmd.Stderr = ptySlave |
caea45dd |
// Copy the PTYs to our broadcasters
go func() { |
a83d87ab |
defer container.stdout.CloseWriters() |
ccac5b13 |
Debugf("[startPty] Begin of stdout pipe") |
7d889554 |
io.Copy(container.stdout, ptyMaster) |
ccac5b13 |
Debugf("[startPty] End of stdout pipe") |
caea45dd |
}()
// stdin
if container.Config.OpenStdin { |
7d889554 |
container.cmd.Stdin = ptySlave |
33a5fe3b |
container.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true} |
caea45dd |
go func() {
defer container.stdin.Close() |
ccac5b13 |
Debugf("[startPty] Begin of stdin pipe") |
7d889554 |
io.Copy(ptyMaster, container.stdin) |
ccac5b13 |
Debugf("[startPty] End of stdin pipe") |
caea45dd |
}()
}
if err := container.cmd.Start(); err != nil {
return err
} |
7d889554 |
ptySlave.Close() |
caea45dd |
return nil
}
func (container *Container) start() error {
container.cmd.Stdout = container.stdout
container.cmd.Stderr = container.stderr
if container.Config.OpenStdin {
stdin, err := container.cmd.StdinPipe()
if err != nil {
return err
}
go func() {
defer stdin.Close() |
ccac5b13 |
Debugf("Begin of stdin pipe [start]") |
caea45dd |
io.Copy(stdin, container.stdin) |
ccac5b13 |
Debugf("End of stdin pipe [start]") |
caea45dd |
}()
}
return container.cmd.Start()
}
|
6882c78c |
func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error {
var cStdout, cStderr io.ReadCloser
|
c808940c |
var nJobs int
errors := make(chan error, 3)
if stdin != nil && container.Config.OpenStdin {
nJobs += 1
if cStdin, err := container.StdinPipe(); err != nil {
errors <- err
} else {
go func() {
Debugf("[start] attach stdin\n") |
ad2bbe23 |
defer Debugf("[end] attach stdin\n")
// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
if cStdout != nil {
defer cStdout.Close()
}
if cStderr != nil {
defer cStderr.Close()
} |
64c1b6d9 |
if container.Config.StdinOnce && !container.Config.Tty { |
c808940c |
defer cStdin.Close()
} |
faa88436 |
if container.Config.Tty {
_, err = CopyEscapable(cStdin, stdin)
} else {
_, err = io.Copy(cStdin, stdin)
} |
c808940c |
if err != nil { |
ad2bbe23 |
Debugf("[error] attach stdin: %s\n", err) |
c808940c |
} |
6882c78c |
// Discard error, expecting pipe error
errors <- nil |
c808940c |
}()
}
}
if stdout != nil {
nJobs += 1
if p, err := container.StdoutPipe(); err != nil {
errors <- err
} else {
cStdout = p
go func() {
Debugf("[start] attach stdout\n")
defer Debugf("[end] attach stdout\n") |
6882c78c |
// If we are in StdinOnce mode, then close stdin
if container.Config.StdinOnce {
if stdin != nil {
defer stdin.Close()
}
if stdinCloser != nil {
defer stdinCloser.Close()
}
} |
c808940c |
_, err := io.Copy(stdout, cStdout)
if err != nil {
Debugf("[error] attach stdout: %s\n", err)
}
errors <- err
}()
}
}
if stderr != nil {
nJobs += 1
if p, err := container.StderrPipe(); err != nil {
errors <- err
} else {
cStderr = p
go func() {
Debugf("[start] attach stderr\n")
defer Debugf("[end] attach stderr\n") |
6882c78c |
// If we are in StdinOnce mode, then close stdin
if container.Config.StdinOnce {
if stdin != nil {
defer stdin.Close()
}
if stdinCloser != nil {
defer stdinCloser.Close()
}
} |
c808940c |
_, err := io.Copy(stderr, cStderr)
if err != nil {
Debugf("[error] attach stderr: %s\n", err)
}
errors <- err
}()
}
}
return Go(func() error {
if cStdout != nil {
defer cStdout.Close()
}
if cStderr != nil {
defer cStderr.Close()
}
// FIXME: how do clean up the stdin goroutine without the unwanted side effect
// of closing the passed stdin? Add an intermediary io.Pipe?
for i := 0; i < nJobs; i += 1 {
Debugf("Waiting for job %d/%d\n", i+1, nJobs)
if err := <-errors; err != nil {
Debugf("Job %d returned error %s. Aborting all jobs\n", i+1, err)
return err
}
Debugf("Job %d completed successfully\n", i+1)
}
Debugf("All jobs completed successfully\n")
return nil
})
}
|
a27b4b8c |
func (container *Container) Start() error { |
7c2b085d |
container.State.lock()
defer container.State.unlock()
|
d949e280 |
if container.State.Running {
return fmt.Errorf("The container %s is already running.", container.Id)
} |
7c57a4cf |
if err := container.EnsureMounted(); err != nil { |
a27b4b8c |
return err
} |
c08f5b2b |
if err := container.allocateNetwork(); err != nil {
return err
} |
1967c834 |
|
f68d107a |
// Make sure the config is compatible with the current kernel
if container.Config.Memory > 0 && !container.runtime.capabilities.MemoryLimit {
log.Printf("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") |
1967c834 |
container.Config.Memory = 0
} |
f68d107a |
if container.Config.Memory > 0 && !container.runtime.capabilities.SwapLimit {
log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
container.Config.MemorySwap = -1
} |
1967c834 |
|
1793538a |
if err := container.generateLXCConfig(); err != nil {
return err
} |
a27b4b8c |
params := []string{ |
78c02daf |
"-n", container.Id, |
7c57a4cf |
"-f", container.lxcConfigPath(), |
a27b4b8c |
"--", |
58a22942 |
"/sbin/init", |
a27b4b8c |
} |
5cecd548 |
// Networking |
c08f5b2b |
params = append(params, "-g", container.network.Gateway.String()) |
5cecd548 |
// User |
6de3e8a2 |
if container.Config.User != "" {
params = append(params, "-u", container.Config.User)
} |
5cecd548 |
|
7b0e96f1 |
if container.Config.Tty {
params = append(params, "-e", "TERM=xterm")
}
// Setup environment
params = append(params,
"-e", "HOME=/",
"-e", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
)
for _, elem := range container.Config.Env {
params = append(params, "-e", elem)
}
|
5cecd548 |
// Program |
6de3e8a2 |
params = append(params, "--", container.Path) |
a27b4b8c |
params = append(params, container.Args...) |
5cecd548 |
|
6d72758f |
container.cmd = exec.Command("lxc-start", params...) |
a27b4b8c |
|
0f7a4534 |
// Setup logging of stdout and stderr to disk
if err := container.runtime.LogToDisk(container.stdout, container.logPath("stdout")); err != nil {
return err
}
if err := container.runtime.LogToDisk(container.stderr, container.logPath("stderr")); err != nil {
return err
}
|
caea45dd |
var err error |
7a50153c |
if container.Config.Tty { |
caea45dd |
err = container.startPty() |
7a50153c |
} else { |
caea45dd |
err = container.start() |
7a50153c |
} |
caea45dd |
if err != nil { |
a27b4b8c |
return err
} |
7c57a4cf |
// FIXME: save state on disk *first*, then converge
// this way disk state is used as a journal, eg. we can restore after crash etc. |
a27b4b8c |
container.State.setRunning(container.cmd.Process.Pid) |
329f4449 |
// Init the lock
container.waitLock = make(chan struct{}) |
7c57a4cf |
container.ToDisk() |
a27b4b8c |
go container.monitor() |
f958bdba |
return nil |
a27b4b8c |
}
func (container *Container) Run() error {
if err := container.Start(); err != nil {
return err
}
container.Wait()
return nil
}
func (container *Container) Output() (output []byte, err error) {
pipe, err := container.StdoutPipe()
if err != nil {
return nil, err
}
defer pipe.Close()
if err := container.Start(); err != nil {
return nil, err
}
output, err = ioutil.ReadAll(pipe)
container.Wait()
return output, err
}
|
7a50153c |
// StdinPipe() returns a pipe connected to the standard input of the container's
// active process.
//
func (container *Container) StdinPipe() (io.WriteCloser, error) {
return container.stdinPipe, nil
}
|
a27b4b8c |
func (container *Container) StdoutPipe() (io.ReadCloser, error) {
reader, writer := io.Pipe()
container.stdout.AddWriter(writer)
return newBufReader(reader), nil
}
func (container *Container) StderrPipe() (io.ReadCloser, error) {
reader, writer := io.Pipe()
container.stderr.AddWriter(writer)
return newBufReader(reader), nil
}
|
c08f5b2b |
func (container *Container) allocateNetwork() error { |
d65983f3 |
iface, err := container.runtime.networkManager.Allocate() |
c08f5b2b |
if err != nil {
return err
} |
09eacdfa |
container.NetworkSettings.PortMapping = make(map[string]string) |
2aad4a34 |
for _, spec := range container.Config.PortSpecs {
if nat, err := iface.AllocatePort(spec); err != nil { |
09eacdfa |
iface.Release()
return err
} else { |
2aad4a34 |
container.NetworkSettings.PortMapping[strconv.Itoa(nat.Backend)] = strconv.Itoa(nat.Frontend) |
09eacdfa |
}
} |
c08f5b2b |
container.network = iface |
d9a9bfc9 |
container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface |
09eacdfa |
container.NetworkSettings.IpAddress = iface.IPNet.IP.String()
container.NetworkSettings.IpPrefixLen, _ = iface.IPNet.Mask.Size()
container.NetworkSettings.Gateway = iface.Gateway.String() |
c08f5b2b |
return nil
}
|
54443c09 |
func (container *Container) releaseNetwork() { |
6f9a67a7 |
container.network.Release() |
c08f5b2b |
container.network = nil |
09eacdfa |
container.NetworkSettings = &NetworkSettings{} |
5d6dd22f |
}
|
a27b4b8c |
func (container *Container) monitor() { |
f0c08b57 |
// Wait for the program to exit |
ccac5b13 |
Debugf("Waiting for process") |
69c2250e |
if err := container.cmd.Wait(); err != nil {
// Discard the error as any signals or non 0 returns will generate an error
Debugf("%s: Process: %s", container.Id, err)
} |
ccac5b13 |
Debugf("Process finished")
|
f958bdba |
exitCode := container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() |
24dac228 |
|
f0c08b57 |
// Cleanup |
6f9a67a7 |
container.releaseNetwork() |
47607494 |
if container.Config.OpenStdin {
if err := container.stdin.Close(); err != nil {
Debugf("%s: Error close stdin: %s", container.Id, err)
}
} |
a83d87ab |
if err := container.stdout.CloseWriters(); err != nil { |
69c2250e |
Debugf("%s: Error close stdout: %s", container.Id, err)
} |
a83d87ab |
if err := container.stderr.CloseWriters(); err != nil { |
69c2250e |
Debugf("%s: Error close stderr: %s", container.Id, err)
} |
5252ab69 |
|
7d889554 |
if container.ptyMaster != nil {
if err := container.ptyMaster.Close(); err != nil {
Debugf("%s: Error closing Pty master: %s", container.Id, err) |
5252ab69 |
}
}
|
7c57a4cf |
if err := container.Unmount(); err != nil { |
78c02daf |
log.Printf("%v: Failed to umount filesystem: %v", container.Id, err) |
24dac228 |
}
|
0da9ccc1 |
// Re-create a brand new stdin pipe once the container exited
if container.Config.OpenStdin {
container.stdin, container.stdinPipe = io.Pipe()
}
|
24dac228 |
// Report status back
container.State.setStopped(exitCode) |
329f4449 |
// Release the lock
close(container.waitLock)
|
69c2250e |
if err := container.ToDisk(); err != nil { |
a52a28b6 |
// FIXME: there is a race condition here which causes this to fail during the unit tests.
// If another goroutine was waiting for Wait() to return before removing the container's root
// from the filesystem... At this point it may already have done so.
// This is because State.setStopped() has already been called, and has caused Wait()
// to return.
// FIXME: why are we serializing running state to disk in the first place?
//log.Printf("%s: Failed to dump configuration to the disk: %s", container.Id, err) |
69c2250e |
} |
a27b4b8c |
}
|
24dac228 |
func (container *Container) kill() error { |
7c2b085d |
if !container.State.Running || container.cmd == nil { |
b32436cd |
return nil
} |
e68c04b7 |
|
bb22cd49 |
// Sending SIGKILL to the process via lxc |
e68c04b7 |
output, err := exec.Command("lxc-kill", "-n", container.Id, "9").CombinedOutput()
if err != nil { |
bb22cd49 |
log.Printf("error killing container %s (%s, %s)", container.Id, output, err) |
a27b4b8c |
} |
e68c04b7 |
// 2. Wait for the process to die, in last resort, try to kill the process directly
if err := container.WaitTimeout(10 * time.Second); err != nil { |
bb22cd49 |
log.Printf("Container %s failed to exit within 10 seconds of lxc SIGKILL - trying direct SIGKILL", container.Id) |
e68c04b7 |
if err := container.cmd.Process.Kill(); err != nil {
return err
}
}
|
24dac228 |
// Wait for the container to be actually stopped |
f958bdba |
container.Wait() |
24dac228 |
return nil
}
func (container *Container) Kill() error { |
7c2b085d |
container.State.lock()
defer container.State.unlock() |
313d13ea |
if !container.State.Running {
return nil
}
if container.State.Ghost { |
b1fbebb4 |
return fmt.Errorf("Can't kill ghost container") |
313d13ea |
} |
24dac228 |
return container.kill()
}
|
1615bb08 |
func (container *Container) Stop(seconds int) error { |
7c2b085d |
container.State.lock()
defer container.State.unlock() |
24dac228 |
if !container.State.Running { |
a27b4b8c |
return nil
} |
313d13ea |
if container.State.Ghost { |
ffe16e32 |
return fmt.Errorf("Can't stop ghost container") |
313d13ea |
} |
24dac228 |
// 1. Send a SIGTERM |
6d72758f |
if output, err := exec.Command("lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil { |
c298a91f |
log.Print(string(output))
log.Print("Failed to send SIGTERM to the process, force killing") |
7c2b085d |
if err := container.kill(); err != nil { |
f4e25694 |
return err
} |
24dac228 |
}
// 2. Wait for the process to exit on its own |
1615bb08 |
if err := container.WaitTimeout(time.Duration(seconds) * time.Second); err != nil {
log.Printf("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.Id, seconds) |
0d9e5436 |
if err := container.kill(); err != nil { |
f4e25694 |
return err
} |
24dac228 |
} |
a27b4b8c |
return nil
}
|
1615bb08 |
func (container *Container) Restart(seconds int) error {
if err := container.Stop(seconds); err != nil { |
f0c08b57 |
return err
}
if err := container.Start(); err != nil {
return err
}
return nil
}
|
ebaa50c4 |
// Wait blocks until the container stops running, then returns its exit code.
func (container *Container) Wait() int { |
329f4449 |
<-container.waitLock |
ebaa50c4 |
return container.State.ExitCode |
a27b4b8c |
} |
24dac228 |
|
ef711962 |
func (container *Container) ExportRw() (Archive, error) {
return Tar(container.rwPath(), Uncompressed) |
7c57a4cf |
}
|
ef711962 |
func (container *Container) Export() (Archive, error) { |
7c57a4cf |
if err := container.EnsureMounted(); err != nil {
return nil, err
} |
ef711962 |
return Tar(container.RootfsPath(), Uncompressed) |
7c57a4cf |
}
|
24dac228 |
func (container *Container) WaitTimeout(timeout time.Duration) error {
done := make(chan bool)
go func() {
container.Wait()
done <- true
}()
select {
case <-time.After(timeout): |
d17f78c3 |
return fmt.Errorf("Timed Out") |
24dac228 |
case <-done:
return nil
} |
22f1cc95 |
panic("unreachable") |
24dac228 |
} |
7c57a4cf |
func (container *Container) EnsureMounted() error {
if mounted, err := container.Mounted(); err != nil {
return err
} else if mounted {
return nil
}
return container.Mount()
}
func (container *Container) Mount() error {
image, err := container.GetImage()
if err != nil {
return err
}
return image.Mount(container.RootfsPath(), container.rwPath())
}
|
ef711962 |
func (container *Container) Changes() ([]Change, error) { |
7c57a4cf |
image, err := container.GetImage()
if err != nil {
return nil, err
}
return image.Changes(container.rwPath())
}
|
ef711962 |
func (container *Container) GetImage() (*Image, error) { |
7c57a4cf |
if container.runtime == nil {
return nil, fmt.Errorf("Can't get image of unregistered container")
}
return container.runtime.graph.Get(container.Image)
}
func (container *Container) Mounted() (bool, error) { |
ef711962 |
return Mounted(container.RootfsPath()) |
7c57a4cf |
}
func (container *Container) Unmount() error { |
ef711962 |
return Unmount(container.RootfsPath()) |
7c57a4cf |
}
|
0b9a3c86 |
// ShortId returns a shorthand version of the container's id for convenience.
// A collision with other container shorthands is very unlikely, but possible.
// In case of a collision a lookup with Runtime.Get() will fail, and the caller
// will need to use a langer prefix, or the full-length container Id.
func (container *Container) ShortId() string { |
1632566e |
return TruncateId(container.Id) |
0b9a3c86 |
}
|
7c57a4cf |
func (container *Container) logPath(name string) string {
return path.Join(container.root, fmt.Sprintf("%s-%s.log", container.Id, name))
}
func (container *Container) ReadLog(name string) (io.Reader, error) {
return os.Open(container.logPath(name))
}
func (container *Container) jsonPath() string {
return path.Join(container.root, "config.json")
}
func (container *Container) lxcConfigPath() string {
return path.Join(container.root, "config.lxc")
}
// This method must be exported to be used from the lxc template
func (container *Container) RootfsPath() string {
return path.Join(container.root, "rootfs")
}
func (container *Container) rwPath() string {
return path.Join(container.root, "rw")
}
func validateId(id string) error {
if id == "" {
return fmt.Errorf("Invalid empty id")
}
return nil
} |