зеркало из https://github.com/microsoft/docker.git
Merge branch 'master' into bump_v1.3.0
This commit is contained in:
Коммит
87bd2da5d8
|
@ -0,0 +1,14 @@
|
|||
image: dockercore/docker
|
||||
env:
|
||||
- AUTO_GOPATH=1
|
||||
- DOCKER_GRAPHDRIVER=vfs
|
||||
- DOCKER_EXECDRIVER=native
|
||||
script:
|
||||
# Setup the DockerInDocker environment.
|
||||
- hack/dind
|
||||
# Tests relying on StartWithBusybox make Drone time out.
|
||||
- rm integration-cli/docker_cli_daemon_test.go
|
||||
- rm integration-cli/docker_cli_exec_test.go
|
||||
# Validate and test.
|
||||
- hack/make.sh validate-dco validate-gofmt
|
||||
- hack/make.sh binary cross test-unit test-integration-cli test-integration
|
11
.travis.yml
11
.travis.yml
|
@ -17,6 +17,15 @@ sudo: false
|
|||
install:
|
||||
- export DOCKER_BUILDTAGS='exclude_graphdriver_btrfs exclude_graphdriver_devicemapper' # btrfs and devicemapper fail to compile thanks to a couple missing headers (which we can't install thanks to "sudo: false")
|
||||
- export AUTO_GOPATH=1
|
||||
# some of Docker's unit tests don't work inside Travis (yet!), so we purge those test files for now
|
||||
- rm -f daemon/graphdriver/btrfs/*_test.go # fails to compile (missing header)
|
||||
- rm -f daemon/graphdriver/devmapper/*_test.go # fails to compile (missing header)
|
||||
- rm -f daemon/execdriver/lxc/*_test.go # fails to run (missing "lxc-start")
|
||||
- rm -f daemon/graphdriver/aufs/*_test.go # fails to run ("backing file system is unsupported for this graph driver")
|
||||
- rm -f daemon/graphdriver/vfs/*_test.go # fails to run (not root, which these tests assume "/var/tmp/... no owned by uid 0")
|
||||
- rm -f daemon/networkdriver/bridge/*_test.go # fails to run ("Failed to initialize network driver")
|
||||
- rm -f graph/*_test.go # fails to run ("mkdir /tmp/docker-test.../vfs/dir/foo/etc/postgres: permission denied")
|
||||
- rm -f pkg/mount/*_test.go # fails to run ("permission denied")
|
||||
|
||||
before_script:
|
||||
- env | sort
|
||||
|
@ -24,7 +33,7 @@ before_script:
|
|||
script:
|
||||
- hack/make.sh validate-dco
|
||||
- hack/make.sh validate-gofmt
|
||||
- ./hack/make.sh dynbinary
|
||||
- DOCKER_CLIENTONLY=1 ./hack/make.sh dynbinary
|
||||
- ./hack/make.sh dynbinary dyntest-unit
|
||||
|
||||
# vim:set sw=2 ts=2:
|
||||
|
|
|
@ -59,7 +59,7 @@ RUN cd /usr/local/lvm2 && ./configure --enable-static_link && make device-mapper
|
|||
# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
|
||||
|
||||
# Install Go
|
||||
RUN curl -sSL https://golang.org/dl/go1.3.1.src.tar.gz | tar -v -C /usr/local -xz
|
||||
RUN curl -sSL https://golang.org/dl/go1.3.3.src.tar.gz | tar -v -C /usr/local -xz
|
||||
ENV PATH /usr/local/go/bin:$PATH
|
||||
ENV GOPATH /go:/go/src/github.com/docker/docker/vendor
|
||||
ENV PATH /go/bin:$PATH
|
||||
|
@ -89,8 +89,11 @@ RUN mkdir -p /go/src/github.com/cpuguy83 \
|
|||
# Get the "busybox" image source so we can build locally instead of pulling
|
||||
RUN git clone -b buildroot-2014.02 https://github.com/jpetazzo/docker-busybox.git /docker-busybox
|
||||
|
||||
# Get the "cirros" image source so we can import it instead of fetching it during tests
|
||||
RUN curl -sSL -o /cirros.tar.gz https://github.com/ewindisch/docker-cirros/raw/1cded459668e8b9dbf4ef976c94c05add9bbd8e9/cirros-0.3.0-x86_64-lxc.tar.gz
|
||||
|
||||
# Setup s3cmd config
|
||||
RUN /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY' > /.s3cfg
|
||||
RUN /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY' > $HOME/.s3cfg
|
||||
|
||||
# Set user.email so crosbymichael's in-container merge commits go smoothly
|
||||
RUN git config --global user.email 'docker-dummy@example.com'
|
||||
|
|
2
Makefile
2
Makefile
|
@ -12,7 +12,7 @@ DOCKER_IMAGE := docker$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
|||
DOCKER_DOCS_IMAGE := docker-docs$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
||||
DOCKER_MOUNT := $(if $(BINDDIR),-v "$(CURDIR)/$(BINDDIR):/go/src/github.com/docker/docker/$(BINDDIR)")
|
||||
|
||||
DOCKER_RUN_DOCKER := docker run --rm -it --privileged -e TESTFLAGS -e TESTDIRS -e DOCKER_GRAPHDRIVER -e DOCKER_EXECDRIVER $(DOCKER_MOUNT) "$(DOCKER_IMAGE)"
|
||||
DOCKER_RUN_DOCKER := docker run --rm -it --privileged -e TIMEOUT -e BUILDFLAGS -e TESTFLAGS -e TESTDIRS -e DOCKER_GRAPHDRIVER -e DOCKER_EXECDRIVER $(DOCKER_MOUNT) "$(DOCKER_IMAGE)"
|
||||
# to allow `make DOCSDIR=docs docs-shell`
|
||||
DOCKER_RUN_DOCS := docker run --rm -it $(if $(DOCSDIR),-v $(CURDIR)/$(DOCSDIR):/$(DOCSDIR)) -e AWS_S3_BUCKET
|
||||
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
1.2.0
|
||||
1.2.0-dev
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/libtrust"
|
||||
)
|
||||
|
||||
type DockerCli struct {
|
||||
|
@ -22,10 +23,17 @@ type DockerCli struct {
|
|||
in io.ReadCloser
|
||||
out io.Writer
|
||||
err io.Writer
|
||||
isTerminal bool
|
||||
terminalFd uintptr
|
||||
key libtrust.PrivateKey
|
||||
tlsConfig *tls.Config
|
||||
scheme string
|
||||
// inFd holds file descriptor of the client's STDIN, if it's a valid file
|
||||
inFd uintptr
|
||||
// outFd holds file descriptor of the client's STDOUT, if it's a valid file
|
||||
outFd uintptr
|
||||
// isTerminalIn describes if client's STDIN is a TTY
|
||||
isTerminalIn bool
|
||||
// isTerminalOut describes if client's STDOUT is a TTY
|
||||
isTerminalOut bool
|
||||
}
|
||||
|
||||
var funcMap = template.FuncMap{
|
||||
|
@ -35,11 +43,15 @@ var funcMap = template.FuncMap{
|
|||
},
|
||||
}
|
||||
|
||||
func (cli *DockerCli) getMethod(name string) (func(...string) error, bool) {
|
||||
if len(name) == 0 {
|
||||
return nil, false
|
||||
func (cli *DockerCli) getMethod(args ...string) (func(...string) error, bool) {
|
||||
camelArgs := make([]string, len(args))
|
||||
for i, s := range args {
|
||||
if len(s) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
|
||||
}
|
||||
methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:])
|
||||
methodName := "Cmd" + strings.Join(camelArgs, "")
|
||||
method := reflect.ValueOf(cli).MethodByName(methodName)
|
||||
if !method.IsValid() {
|
||||
return nil, false
|
||||
|
@ -49,6 +61,12 @@ func (cli *DockerCli) getMethod(name string) (func(...string) error, bool) {
|
|||
|
||||
// Cmd executes the specified command
|
||||
func (cli *DockerCli) Cmd(args ...string) error {
|
||||
if len(args) > 1 {
|
||||
method, exists := cli.getMethod(args[:2]...)
|
||||
if exists {
|
||||
return method(args[2:]...)
|
||||
}
|
||||
}
|
||||
if len(args) > 0 {
|
||||
method, exists := cli.getMethod(args[0])
|
||||
if !exists {
|
||||
|
@ -63,7 +81,11 @@ func (cli *DockerCli) Cmd(args ...string) error {
|
|||
func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet {
|
||||
flags := flag.NewFlagSet(name, flag.ContinueOnError)
|
||||
flags.Usage = func() {
|
||||
fmt.Fprintf(cli.err, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description)
|
||||
options := ""
|
||||
if flags.FlagCountUndeprecated() > 0 {
|
||||
options = "[OPTIONS] "
|
||||
}
|
||||
fmt.Fprintf(cli.err, "\nUsage: docker %s %s%s\n\n%s\n\n", name, options, signature, description)
|
||||
flags.PrintDefaults()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
@ -78,11 +100,13 @@ func (cli *DockerCli) LoadConfigFile() (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string, tlsConfig *tls.Config) *DockerCli {
|
||||
func NewDockerCli(in io.ReadCloser, out, err io.Writer, key libtrust.PrivateKey, proto, addr string, tlsConfig *tls.Config) *DockerCli {
|
||||
var (
|
||||
isTerminal = false
|
||||
terminalFd uintptr
|
||||
scheme = "http"
|
||||
inFd uintptr
|
||||
outFd uintptr
|
||||
isTerminalIn = false
|
||||
isTerminalOut = false
|
||||
scheme = "http"
|
||||
)
|
||||
|
||||
if tlsConfig != nil {
|
||||
|
@ -90,24 +114,35 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string, tlsC
|
|||
}
|
||||
|
||||
if in != nil {
|
||||
if file, ok := in.(*os.File); ok {
|
||||
inFd = file.Fd()
|
||||
isTerminalIn = term.IsTerminal(inFd)
|
||||
}
|
||||
}
|
||||
|
||||
if out != nil {
|
||||
if file, ok := out.(*os.File); ok {
|
||||
terminalFd = file.Fd()
|
||||
isTerminal = term.IsTerminal(terminalFd)
|
||||
outFd = file.Fd()
|
||||
isTerminalOut = term.IsTerminal(outFd)
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = out
|
||||
}
|
||||
|
||||
return &DockerCli{
|
||||
proto: proto,
|
||||
addr: addr,
|
||||
in: in,
|
||||
out: out,
|
||||
err: err,
|
||||
isTerminal: isTerminal,
|
||||
terminalFd: terminalFd,
|
||||
tlsConfig: tlsConfig,
|
||||
scheme: scheme,
|
||||
proto: proto,
|
||||
addr: addr,
|
||||
in: in,
|
||||
out: out,
|
||||
err: err,
|
||||
key: key,
|
||||
inFd: inFd,
|
||||
outFd: outFd,
|
||||
isTerminalIn: isTerminalIn,
|
||||
isTerminalOut: isTerminalOut,
|
||||
tlsConfig: tlsConfig,
|
||||
scheme: scheme,
|
||||
}
|
||||
}
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -14,8 +14,9 @@ import (
|
|||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/dockerversion"
|
||||
"github.com/docker/docker/pkg/log"
|
||||
"github.com/docker/docker/pkg/promise"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/docker/utils"
|
||||
)
|
||||
|
||||
func (cli *DockerCli) dial() (net.Conn, error) {
|
||||
|
@ -25,14 +26,18 @@ func (cli *DockerCli) dial() (net.Conn, error) {
|
|||
return net.Dial(cli.proto, cli.addr)
|
||||
}
|
||||
|
||||
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer) error {
|
||||
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer, data interface{}) error {
|
||||
defer func() {
|
||||
if started != nil {
|
||||
close(started)
|
||||
}
|
||||
}()
|
||||
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), nil)
|
||||
params, err := cli.encodeData(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -64,20 +69,20 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
|||
|
||||
var oldState *term.State
|
||||
|
||||
if in != nil && setRawTerminal && cli.isTerminal && os.Getenv("NORAW") == "" {
|
||||
oldState, err = term.SetRawTerminal(cli.terminalFd)
|
||||
if in != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" {
|
||||
oldState, err = term.SetRawTerminal(cli.inFd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer term.RestoreTerminal(cli.terminalFd, oldState)
|
||||
defer term.RestoreTerminal(cli.inFd, oldState)
|
||||
}
|
||||
|
||||
if stdout != nil || stderr != nil {
|
||||
receiveStdout = utils.Go(func() (err error) {
|
||||
receiveStdout = promise.Go(func() (err error) {
|
||||
defer func() {
|
||||
if in != nil {
|
||||
if setRawTerminal && cli.isTerminal {
|
||||
term.RestoreTerminal(cli.terminalFd, oldState)
|
||||
if setRawTerminal && cli.isTerminalIn {
|
||||
term.RestoreTerminal(cli.inFd, oldState)
|
||||
}
|
||||
// For some reason this Close call blocks on darwin..
|
||||
// As the client exists right after, simply discard the close
|
||||
|
@ -92,14 +97,14 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
|||
if setRawTerminal && stdout != nil {
|
||||
_, err = io.Copy(stdout, br)
|
||||
} else {
|
||||
_, err = utils.StdCopy(stdout, stderr, br)
|
||||
_, err = stdcopy.StdCopy(stdout, stderr, br)
|
||||
}
|
||||
log.Debugf("[hijack] End of stdout")
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
sendStdin := utils.Go(func() error {
|
||||
sendStdin := promise.Go(func() error {
|
||||
if in != nil {
|
||||
io.Copy(rwc, in)
|
||||
log.Debugf("[hijack] End of stdin")
|
||||
|
@ -124,7 +129,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
|||
}
|
||||
}
|
||||
|
||||
if !cli.isTerminal {
|
||||
if !cli.isTerminalIn {
|
||||
if err := <-sendStdin; err != nil {
|
||||
log.Debugf("Error sendStdin: %s", err)
|
||||
return err
|
||||
|
|
|
@ -16,11 +16,13 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/dockerversion"
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/pkg/log"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/docker/utils"
|
||||
|
@ -34,30 +36,46 @@ func (cli *DockerCli) HTTPClient() *http.Client {
|
|||
tr := &http.Transport{
|
||||
TLSClientConfig: cli.tlsConfig,
|
||||
Dial: func(network, addr string) (net.Conn, error) {
|
||||
return net.Dial(cli.proto, cli.addr)
|
||||
// Why 32? See issue 8035
|
||||
return net.DialTimeout(cli.proto, cli.addr, 32*time.Second)
|
||||
},
|
||||
}
|
||||
if cli.proto == "unix" {
|
||||
// XXX workaround for net/http Transport which caches connections, but is
|
||||
// intended for tcp connections, not unix sockets.
|
||||
tr.DisableKeepAlives = true
|
||||
|
||||
// no need in compressing for local communications
|
||||
tr.DisableCompression = true
|
||||
}
|
||||
return &http.Client{Transport: tr}
|
||||
}
|
||||
|
||||
func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) {
|
||||
func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) {
|
||||
params := bytes.NewBuffer(nil)
|
||||
if data != nil {
|
||||
if env, ok := data.(engine.Env); ok {
|
||||
if err := env.Encode(params); err != nil {
|
||||
return nil, -1, err
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
buf, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
return nil, err
|
||||
}
|
||||
if _, err := params.Write(buf); err != nil {
|
||||
return nil, -1, err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) {
|
||||
params, err := cli.encodeData(data)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
|
@ -108,6 +126,7 @@ func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo b
|
|||
}
|
||||
return nil, resp.StatusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
|
||||
}
|
||||
|
||||
return resp.Body, resp.StatusCode, nil
|
||||
}
|
||||
|
||||
|
@ -120,7 +139,7 @@ func (cli *DockerCli) streamHelper(method, path string, setRawTerminal bool, in
|
|||
in = bytes.NewReader([]byte{})
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("http://v%s%s", api.APIVERSION, path), in)
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -157,14 +176,14 @@ func (cli *DockerCli) streamHelper(method, path string, setRawTerminal bool, in
|
|||
}
|
||||
|
||||
if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") {
|
||||
return utils.DisplayJSONMessagesStream(resp.Body, stdout, cli.terminalFd, cli.isTerminal)
|
||||
return utils.DisplayJSONMessagesStream(resp.Body, stdout, cli.outFd, cli.isTerminalOut)
|
||||
}
|
||||
if stdout != nil || stderr != nil {
|
||||
// When TTY is ON, use regular copy
|
||||
if setRawTerminal {
|
||||
_, err = io.Copy(stdout, resp.Body)
|
||||
} else {
|
||||
_, err = utils.StdCopy(stdout, stderr, resp.Body)
|
||||
_, err = stdcopy.StdCopy(stdout, stderr, resp.Body)
|
||||
}
|
||||
log.Debugf("[stream] End of stdout")
|
||||
return err
|
||||
|
@ -172,7 +191,7 @@ func (cli *DockerCli) streamHelper(method, path string, setRawTerminal bool, in
|
|||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) resizeTty(id string) {
|
||||
func (cli *DockerCli) resizeTty(id string, isExec bool) {
|
||||
height, width := cli.getTtySize()
|
||||
if height == 0 && width == 0 {
|
||||
return
|
||||
|
@ -180,7 +199,15 @@ func (cli *DockerCli) resizeTty(id string) {
|
|||
v := url.Values{}
|
||||
v.Set("h", strconv.Itoa(height))
|
||||
v.Set("w", strconv.Itoa(width))
|
||||
if _, _, err := readBody(cli.call("POST", "/containers/"+id+"/resize?"+v.Encode(), nil, false)); err != nil {
|
||||
|
||||
path := ""
|
||||
if !isExec {
|
||||
path = "/containers/" + id + "/resize?"
|
||||
} else {
|
||||
path = "/exec/" + id + "/resize?"
|
||||
}
|
||||
|
||||
if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, false)); err != nil {
|
||||
log.Debugf("Error resize: %s", err)
|
||||
}
|
||||
}
|
||||
|
@ -219,24 +246,24 @@ func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
|
|||
return state.GetBool("Running"), state.GetInt("ExitCode"), nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) monitorTtySize(id string) error {
|
||||
cli.resizeTty(id)
|
||||
func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
|
||||
cli.resizeTty(id, isExec)
|
||||
|
||||
sigchan := make(chan os.Signal, 1)
|
||||
gosignal.Notify(sigchan, syscall.SIGWINCH)
|
||||
go func() {
|
||||
for _ = range sigchan {
|
||||
cli.resizeTty(id)
|
||||
cli.resizeTty(id, isExec)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) getTtySize() (int, int) {
|
||||
if !cli.isTerminal {
|
||||
if !cli.isTerminalOut {
|
||||
return 0, 0
|
||||
}
|
||||
ws, err := term.GetWinsize(cli.terminalFd)
|
||||
ws, err := term.GetWinsize(cli.outFd)
|
||||
if err != nil {
|
||||
log.Debugf("Error getting size: %s", err)
|
||||
if ws == nil {
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
APIVERSION version.Version = "1.14"
|
||||
APIVERSION version.Version = "1.15"
|
||||
DEFAULTHTTPHOST = "127.0.0.1"
|
||||
DEFAULTUNIXSOCKET = "/var/run/docker.sock"
|
||||
)
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
Victor Vieux <vieux@docker.com> (@vieux)
|
||||
# off the grid until september
|
||||
# Johan Euphrosine <proppy@google.com> (@proppy)
|
||||
Johan Euphrosine <proppy@google.com> (@proppy)
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/docker/docker/pkg/listenbuffer"
|
||||
"github.com/docker/docker/pkg/log"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
"github.com/docker/docker/pkg/systemd"
|
||||
"github.com/docker/docker/pkg/version"
|
||||
"github.com/docker/docker/registry"
|
||||
|
@ -50,6 +51,24 @@ func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
|||
return conn, conn, nil
|
||||
}
|
||||
|
||||
// Check to make sure request's Content-Type is application/json
|
||||
func checkForJson(r *http.Request) error {
|
||||
ct := r.Header.Get("Content-Type")
|
||||
|
||||
// No Content-Type header is ok as long as there's no Body
|
||||
if ct == "" {
|
||||
if r.Body == nil || r.ContentLength == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise it better be json
|
||||
if api.MatchesContentType(ct, "application/json") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Content-Type specified (%s) must be 'application/json'", ct)
|
||||
}
|
||||
|
||||
//If we don't do this, POST method without Content-type (even with empty body) will fail
|
||||
func parseForm(r *http.Request) error {
|
||||
if r == nil {
|
||||
|
@ -397,8 +416,8 @@ func getContainersLogs(eng *engine.Engine, version version.Version, w http.Respo
|
|||
outStream = utils.NewWriteFlusher(w)
|
||||
|
||||
if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version.GreaterThanOrEqualTo("1.6") {
|
||||
errStream = utils.NewStdWriter(outStream, utils.Stderr)
|
||||
outStream = utils.NewStdWriter(outStream, utils.Stdout)
|
||||
errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
|
||||
outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
|
||||
} else {
|
||||
errStream = outStream
|
||||
}
|
||||
|
@ -438,6 +457,11 @@ func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWrit
|
|||
job = eng.Job("commit", r.Form.Get("container"))
|
||||
stdoutBuffer = bytes.NewBuffer(nil)
|
||||
)
|
||||
|
||||
if err := checkForJson(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := config.Decode(r.Body); err != nil {
|
||||
log.Errorf("%s", err)
|
||||
}
|
||||
|
@ -611,10 +635,18 @@ func getImagesGet(eng *engine.Engine, version version.Version, w http.ResponseWr
|
|||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
if version.GreaterThan("1.0") {
|
||||
w.Header().Set("Content-Type", "application/x-tar")
|
||||
}
|
||||
job := eng.Job("image_export", vars["name"])
|
||||
var job *engine.Job
|
||||
if name, ok := vars["name"]; ok {
|
||||
job = eng.Job("image_export", name)
|
||||
} else {
|
||||
job = eng.Job("image_export", r.Form["names"]...)
|
||||
}
|
||||
job.Stdout.Add(w)
|
||||
return job.Run()
|
||||
}
|
||||
|
@ -636,6 +668,11 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re
|
|||
stdoutBuffer = bytes.NewBuffer(nil)
|
||||
warnings = bytes.NewBuffer(nil)
|
||||
)
|
||||
|
||||
if err := checkForJson(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := job.DecodeEnv(r.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -653,6 +690,7 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re
|
|||
}
|
||||
out.Set("Id", engine.Tail(stdoutBuffer, 1))
|
||||
out.SetList("Warnings", outWarnings)
|
||||
|
||||
return writeJSON(w, http.StatusCreated, out)
|
||||
}
|
||||
|
||||
|
@ -679,7 +717,7 @@ func deleteContainers(eng *engine.Engine, version version.Version, w http.Respon
|
|||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
job := eng.Job("delete", vars["name"])
|
||||
job := eng.Job("rm", vars["name"])
|
||||
|
||||
job.Setenv("forceRemove", r.Form.Get("force"))
|
||||
|
||||
|
@ -716,10 +754,15 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res
|
|||
job = eng.Job("start", name)
|
||||
)
|
||||
|
||||
// If contentLength is -1, we can assumed chunked encoding
|
||||
// or more technically that the length is unknown
|
||||
// http://golang.org/src/pkg/net/http/request.go#L139
|
||||
// net/http otherwise seems to swallow any headers related to chunked encoding
|
||||
// including r.TransferEncoding
|
||||
// allow a nil body for backwards compatibility
|
||||
if r.Body != nil && r.ContentLength > 0 {
|
||||
if !api.MatchesContentType(r.Header.Get("Content-Type"), "application/json") {
|
||||
return fmt.Errorf("Content-Type of application/json is required")
|
||||
if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) {
|
||||
if err := checkForJson(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := job.DecodeEnv(r.Body); err != nil {
|
||||
|
@ -832,8 +875,8 @@ func postContainersAttach(eng *engine.Engine, version version.Version, w http.Re
|
|||
fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
|
||||
|
||||
if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version.GreaterThanOrEqualTo("1.6") {
|
||||
errStream = utils.NewStdWriter(outStream, utils.Stderr)
|
||||
outStream = utils.NewStdWriter(outStream, utils.Stdout)
|
||||
errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
|
||||
outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
|
||||
} else {
|
||||
errStream = outStream
|
||||
}
|
||||
|
@ -984,12 +1027,12 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp
|
|||
|
||||
var copyData engine.Env
|
||||
|
||||
if contentType := r.Header.Get("Content-Type"); api.MatchesContentType(contentType, "application/json") {
|
||||
if err := copyData.Decode(r.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Content-Type not supported: %s", contentType)
|
||||
if err := checkForJson(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := copyData.Decode(r.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if copyData.Get("Resource") == "" {
|
||||
|
@ -1004,6 +1047,7 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp
|
|||
|
||||
job := eng.Job("container_copy", vars["name"], copyData.Get("Resource"))
|
||||
job.Stdout.Add(w)
|
||||
w.Header().Set("Content-Type", "application/x-tar")
|
||||
if err := job.Run(); err != nil {
|
||||
log.Errorf("%s", err.Error())
|
||||
if strings.Contains(err.Error(), "No such container") {
|
||||
|
@ -1015,6 +1059,107 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp
|
|||
return nil
|
||||
}
|
||||
|
||||
func postContainerExecCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
out engine.Env
|
||||
name = vars["name"]
|
||||
job = eng.Job("execCreate", name)
|
||||
stdoutBuffer = bytes.NewBuffer(nil)
|
||||
)
|
||||
|
||||
if err := job.DecodeEnv(r.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
job.Stdout.Add(stdoutBuffer)
|
||||
// Register an instance of Exec in container.
|
||||
if err := job.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error setting up exec command in container %s: %s\n", name, err)
|
||||
return err
|
||||
}
|
||||
// Return the ID
|
||||
out.Set("Id", engine.Tail(stdoutBuffer, 1))
|
||||
|
||||
return writeJSON(w, http.StatusCreated, out)
|
||||
}
|
||||
|
||||
// TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start.
|
||||
func postContainerExecStart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
name = vars["name"]
|
||||
job = eng.Job("execStart", name)
|
||||
errOut io.Writer = os.Stderr
|
||||
)
|
||||
|
||||
if err := job.DecodeEnv(r.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
if !job.GetenvBool("Detach") {
|
||||
// Setting up the streaming http interface.
|
||||
inStream, outStream, err := hijackServer(w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if tcpc, ok := inStream.(*net.TCPConn); ok {
|
||||
tcpc.CloseWrite()
|
||||
} else {
|
||||
inStream.Close()
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
if tcpc, ok := outStream.(*net.TCPConn); ok {
|
||||
tcpc.CloseWrite()
|
||||
} else if closer, ok := outStream.(io.Closer); ok {
|
||||
closer.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
var errStream io.Writer
|
||||
|
||||
fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
|
||||
if !job.GetenvBool("Tty") && version.GreaterThanOrEqualTo("1.6") {
|
||||
errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
|
||||
outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
|
||||
} else {
|
||||
errStream = outStream
|
||||
}
|
||||
job.Stdin.Add(inStream)
|
||||
job.Stdout.Add(outStream)
|
||||
job.Stderr.Set(errStream)
|
||||
errOut = outStream
|
||||
}
|
||||
// Now run the user process in container.
|
||||
job.SetCloseIO(false)
|
||||
if err := job.Run(); err != nil {
|
||||
fmt.Fprintf(errOut, "Error starting exec command in container %s: %s\n", name, err)
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func postContainerExecResize(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
if err := eng.Job("execResize", vars["name"], r.Form.Get("h"), r.Form.Get("w")).Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func optionsHandler(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return nil
|
||||
|
@ -1105,6 +1250,7 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
|
|||
"/images/json": getImagesJSON,
|
||||
"/images/viz": getImagesViz,
|
||||
"/images/search": getImagesSearch,
|
||||
"/images/get": getImagesGet,
|
||||
"/images/{name:.*}/get": getImagesGet,
|
||||
"/images/{name:.*}/history": getImagesHistory,
|
||||
"/images/{name:.*}/json": getImagesByName,
|
||||
|
@ -1136,6 +1282,9 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
|
|||
"/containers/{name:.*}/resize": postContainersResize,
|
||||
"/containers/{name:.*}/attach": postContainersAttach,
|
||||
"/containers/{name:.*}/copy": postContainersCopy,
|
||||
"/containers/{name:.*}/exec": postContainerExecCreate,
|
||||
"/exec/{name:.*}/start": postContainerExecStart,
|
||||
"/exec/{name:.*}/resize": postContainerExecResize,
|
||||
},
|
||||
"DELETE": {
|
||||
"/containers/{name:.*}": deleteContainers,
|
||||
|
@ -1209,7 +1358,7 @@ func ServeFd(addr string, handle http.Handler) error {
|
|||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < len(ls); i += 1 {
|
||||
for i := 0; i < len(ls); i++ {
|
||||
err := <-chErrors
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1322,6 +1471,7 @@ func ListenAndServe(proto, addr string, job *engine.Job) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if err := os.Chmod(addr, 0660); err != nil {
|
||||
return err
|
||||
|
@ -1357,7 +1507,7 @@ func ServeApi(job *engine.Job) engine.Status {
|
|||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < len(protoAddrs); i += 1 {
|
||||
for i := 0; i < len(protoAddrs); i++ {
|
||||
err := <-chErrors
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
|
|
|
@ -455,7 +455,7 @@ func TestDeleteContainers(t *testing.T) {
|
|||
eng := engine.New()
|
||||
name := "foo"
|
||||
var called bool
|
||||
eng.Register("delete", func(job *engine.Job) engine.Status {
|
||||
eng.Register("rm", func(job *engine.Job) engine.Status {
|
||||
called = true
|
||||
if len(job.Args) == 0 {
|
||||
t.Fatalf("Job arguments is empty")
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
This code provides helper functions for dealing with archive files.
|
||||
|
||||
**TODO**: Move this to either `pkg` or (if not possible) to `utils`.
|
|
@ -1,4 +0,0 @@
|
|||
package archive
|
||||
|
||||
const twBufSize = 32 * 1024
|
||||
const trBufSize = 32 * 1024
|
|
@ -0,0 +1,2 @@
|
|||
Tibor Vass <teabee89@gmail.com> (@tiborvass)
|
||||
Erik Hollensbe <github@hollensbe.org> (@erikh)
|
|
@ -0,0 +1,352 @@
|
|||
package builder
|
||||
|
||||
// This file contains the dispatchers for each command. Note that
|
||||
// `nullDispatch` is not actually a command, but support for commands we parse
|
||||
// but do nothing with.
|
||||
//
|
||||
// See evaluator.go for a higher level discussion of the whole evaluator
|
||||
// package.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/nat"
|
||||
"github.com/docker/docker/pkg/log"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/runconfig"
|
||||
)
|
||||
|
||||
// dispatch with no layer / parsing. This is effectively not a command.
|
||||
func nullDispatch(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ENV foo bar
|
||||
//
|
||||
// Sets the environment variable foo to bar, also makes interpolation
|
||||
// in the dockerfile available from the next statement on via ${foo}.
|
||||
//
|
||||
func env(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) != 2 {
|
||||
return fmt.Errorf("ENV accepts two arguments")
|
||||
}
|
||||
|
||||
fullEnv := fmt.Sprintf("%s=%s", args[0], args[1])
|
||||
|
||||
for i, envVar := range b.Config.Env {
|
||||
envParts := strings.SplitN(envVar, "=", 2)
|
||||
if args[0] == envParts[0] {
|
||||
b.Config.Env[i] = fullEnv
|
||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("ENV %s", fullEnv))
|
||||
}
|
||||
}
|
||||
b.Config.Env = append(b.Config.Env, fullEnv)
|
||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("ENV %s", fullEnv))
|
||||
}
|
||||
|
||||
// MAINTAINER some text <maybe@an.email.address>
|
||||
//
|
||||
// Sets the maintainer metadata.
|
||||
func maintainer(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("MAINTAINER requires only one argument")
|
||||
}
|
||||
|
||||
b.maintainer = args[0]
|
||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("MAINTAINER %s", b.maintainer))
|
||||
}
|
||||
|
||||
// ADD foo /path
|
||||
//
|
||||
// Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling
|
||||
// exist here. If you do not wish to have this automatic handling, use COPY.
|
||||
//
|
||||
func add(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) < 2 {
|
||||
return fmt.Errorf("ADD requires at least two arguments")
|
||||
}
|
||||
|
||||
return b.runContextCommand(args, true, true, "ADD")
|
||||
}
|
||||
|
||||
// COPY foo /path
|
||||
//
|
||||
// Same as 'ADD' but without the tar and remote url handling.
|
||||
//
|
||||
func dispatchCopy(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) < 2 {
|
||||
return fmt.Errorf("COPY requires at least two arguments")
|
||||
}
|
||||
|
||||
return b.runContextCommand(args, false, false, "COPY")
|
||||
}
|
||||
|
||||
// FROM imagename
|
||||
//
|
||||
// This sets the image the dockerfile will build on top of.
|
||||
//
|
||||
func from(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("FROM requires one argument")
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
|
||||
image, err := b.Daemon.Repositories().LookupImage(name)
|
||||
if err != nil {
|
||||
if b.Daemon.Graph().IsNotExist(err) {
|
||||
image, err = b.pullImage(name)
|
||||
}
|
||||
|
||||
// note that the top level err will still be !nil here if IsNotExist is
|
||||
// not the error. This approach just simplifies hte logic a bit.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return b.processImageFrom(image)
|
||||
}
|
||||
|
||||
// ONBUILD RUN echo yo
|
||||
//
|
||||
// ONBUILD triggers run when the image is used in a FROM statement.
|
||||
//
|
||||
// ONBUILD handling has a lot of special-case functionality, the heading in
|
||||
// evaluator.go and comments around dispatch() in the same file explain the
|
||||
// special cases. search for 'OnBuild' in internals.go for additional special
|
||||
// cases.
|
||||
//
|
||||
func onbuild(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0]))
|
||||
switch triggerInstruction {
|
||||
case "ONBUILD":
|
||||
return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
|
||||
case "MAINTAINER", "FROM":
|
||||
return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction)
|
||||
}
|
||||
|
||||
original = strings.TrimSpace(strings.TrimLeft(original, "ONBUILD"))
|
||||
|
||||
b.Config.OnBuild = append(b.Config.OnBuild, original)
|
||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("ONBUILD %s", original))
|
||||
}
|
||||
|
||||
// WORKDIR /tmp
|
||||
//
|
||||
// Set the working directory for future RUN/CMD/etc statements.
|
||||
//
|
||||
func workdir(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("WORKDIR requires exactly one argument")
|
||||
}
|
||||
|
||||
workdir := args[0]
|
||||
|
||||
if workdir[0] == '/' {
|
||||
b.Config.WorkingDir = workdir
|
||||
} else {
|
||||
if b.Config.WorkingDir == "" {
|
||||
b.Config.WorkingDir = "/"
|
||||
}
|
||||
b.Config.WorkingDir = filepath.Join(b.Config.WorkingDir, workdir)
|
||||
}
|
||||
|
||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("WORKDIR %v", workdir))
|
||||
}
|
||||
|
||||
// RUN some command yo
|
||||
//
|
||||
// run a command and commit the image. Args are automatically prepended with
|
||||
// 'sh -c' in the event there is only one argument. The difference in
|
||||
// processing:
|
||||
//
|
||||
// RUN echo hi # sh -c echo hi
|
||||
// RUN [ "echo", "hi" ] # echo hi
|
||||
//
|
||||
func run(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if b.image == "" {
|
||||
return fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||
}
|
||||
|
||||
args = handleJsonArgs(args, attributes)
|
||||
|
||||
if len(args) == 1 {
|
||||
args = append([]string{"/bin/sh", "-c"}, args[0])
|
||||
}
|
||||
|
||||
runCmd := flag.NewFlagSet("run", flag.ContinueOnError)
|
||||
runCmd.SetOutput(ioutil.Discard)
|
||||
runCmd.Usage = nil
|
||||
|
||||
config, _, _, err := runconfig.Parse(runCmd, append([]string{b.image}, args...), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := b.Config.Cmd
|
||||
// set Cmd manually, this is special case only for Dockerfiles
|
||||
b.Config.Cmd = config.Cmd
|
||||
runconfig.Merge(b.Config, config)
|
||||
|
||||
defer func(cmd []string) { b.Config.Cmd = cmd }(cmd)
|
||||
|
||||
log.Debugf("Command to be executed: %v", b.Config.Cmd)
|
||||
|
||||
hit, err := b.probeCache()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hit {
|
||||
return nil
|
||||
}
|
||||
|
||||
c, err := b.create()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure that we keep the container mounted until the commit
|
||||
// to avoid unmounting and then mounting directly again
|
||||
c.Mount()
|
||||
defer c.Unmount()
|
||||
|
||||
err = b.run(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.commit(c.ID, cmd, "run"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CMD foo
|
||||
//
|
||||
// Set the default command to run in the container (which may be empty).
|
||||
// Argument handling is the same as RUN.
|
||||
//
|
||||
func cmd(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
b.Config.Cmd = handleJsonArgs(args, attributes)
|
||||
|
||||
if !attributes["json"] && len(b.Config.Entrypoint) == 0 {
|
||||
b.Config.Cmd = append([]string{"/bin/sh", "-c"}, b.Config.Cmd...)
|
||||
}
|
||||
|
||||
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %v", b.Config.Cmd)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(args) != 0 {
|
||||
b.cmdSet = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ENTRYPOINT /usr/sbin/nginx
|
||||
//
|
||||
// Set the entrypoint (which defaults to sh -c) to /usr/sbin/nginx. Will
|
||||
// accept the CMD as the arguments to /usr/sbin/nginx.
|
||||
//
|
||||
// Handles command processing similar to CMD and RUN, only b.Config.Entrypoint
|
||||
// is initialized at NewBuilder time instead of through argument parsing.
|
||||
//
|
||||
func entrypoint(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
parsed := handleJsonArgs(args, attributes)
|
||||
|
||||
switch {
|
||||
case len(parsed) == 0:
|
||||
// ENTYRPOINT []
|
||||
b.Config.Entrypoint = nil
|
||||
case attributes["json"]:
|
||||
// ENTRYPOINT ["echo", "hi"]
|
||||
b.Config.Entrypoint = parsed
|
||||
default:
|
||||
// ENTYRPOINT echo hi
|
||||
b.Config.Entrypoint = []string{"/bin/sh", "-c", parsed[0]}
|
||||
}
|
||||
|
||||
// when setting the entrypoint if a CMD was not explicitly set then
|
||||
// set the command to nil
|
||||
if !b.cmdSet {
|
||||
b.Config.Cmd = nil
|
||||
}
|
||||
|
||||
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("ENTRYPOINT %v", b.Config.Entrypoint)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EXPOSE 6667/tcp 7000/tcp
|
||||
//
|
||||
// Expose ports for links and port mappings. This all ends up in
|
||||
// b.Config.ExposedPorts for runconfig.
|
||||
//
|
||||
func expose(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
portsTab := args
|
||||
|
||||
if b.Config.ExposedPorts == nil {
|
||||
b.Config.ExposedPorts = make(nat.PortSet)
|
||||
}
|
||||
|
||||
ports, _, err := nat.ParsePortSpecs(append(portsTab, b.Config.PortSpecs...))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for port := range ports {
|
||||
if _, exists := b.Config.ExposedPorts[port]; !exists {
|
||||
b.Config.ExposedPorts[port] = struct{}{}
|
||||
}
|
||||
}
|
||||
b.Config.PortSpecs = nil
|
||||
|
||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
|
||||
}
|
||||
|
||||
// USER foo
|
||||
//
|
||||
// Set the user to 'foo' for future commands and when running the
|
||||
// ENTRYPOINT/CMD at container run time.
|
||||
//
|
||||
func user(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("USER requires exactly one argument")
|
||||
}
|
||||
|
||||
b.Config.User = args[0]
|
||||
return b.commit("", b.Config.Cmd, fmt.Sprintf("USER %v", args))
|
||||
}
|
||||
|
||||
// VOLUME /foo
|
||||
//
|
||||
// Expose the volume /foo for use. Will also accept the JSON array form.
|
||||
//
|
||||
func volume(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("Volume cannot be empty")
|
||||
}
|
||||
|
||||
if b.Config.Volumes == nil {
|
||||
b.Config.Volumes = map[string]struct{}{}
|
||||
}
|
||||
for _, v := range args {
|
||||
b.Config.Volumes[v] = struct{}{}
|
||||
}
|
||||
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("VOLUME %v", args)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// INSERT is no longer accepted, but we still parse it.
|
||||
func insert(b *Builder, args []string, attributes map[string]bool, original string) error {
|
||||
return fmt.Errorf("INSERT has been deprecated. Please use ADD instead")
|
||||
}
|
|
@ -0,0 +1,220 @@
|
|||
// builder is the evaluation step in the Dockerfile parse/evaluate pipeline.
|
||||
//
|
||||
// It incorporates a dispatch table based on the parser.Node values (see the
|
||||
// parser package for more information) that are yielded from the parser itself.
|
||||
// Calling NewBuilder with the BuildOpts struct can be used to customize the
|
||||
// experience for execution purposes only. Parsing is controlled in the parser
|
||||
// package, and this division of resposibility should be respected.
|
||||
//
|
||||
// Please see the jump table targets for the actual invocations, most of which
|
||||
// will call out to the functions in internals.go to deal with their tasks.
|
||||
//
|
||||
// ONBUILD is a special case, which is covered in the onbuild() func in
|
||||
// dispatchers.go.
|
||||
//
|
||||
// The evaluator uses the concept of "steps", which are usually each processable
|
||||
// line in the Dockerfile. Each step is numbered and certain actions are taken
|
||||
// before and after each step, such as creating an image ID and removing temporary
|
||||
// containers and images. Note that ONBUILD creates a kinda-sorta "sub run" which
|
||||
// includes its own set of steps (usually only one of them).
|
||||
package builder
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/builder/parser"
|
||||
"github.com/docker/docker/daemon"
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/pkg/log"
|
||||
"github.com/docker/docker/pkg/tarsum"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/docker/docker/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrDockerfileEmpty = errors.New("Dockerfile cannot be empty")
|
||||
)
|
||||
|
||||
var evaluateTable map[string]func(*Builder, []string, map[string]bool, string) error
|
||||
|
||||
func init() {
|
||||
evaluateTable = map[string]func(*Builder, []string, map[string]bool, string) error{
|
||||
"env": env,
|
||||
"maintainer": maintainer,
|
||||
"add": add,
|
||||
"copy": dispatchCopy, // copy() is a go builtin
|
||||
"from": from,
|
||||
"onbuild": onbuild,
|
||||
"workdir": workdir,
|
||||
"run": run,
|
||||
"cmd": cmd,
|
||||
"entrypoint": entrypoint,
|
||||
"expose": expose,
|
||||
"volume": volume,
|
||||
"user": user,
|
||||
"insert": insert,
|
||||
}
|
||||
}
|
||||
|
||||
// internal struct, used to maintain configuration of the Dockerfile's
|
||||
// processing as it evaluates the parsing result.
|
||||
type Builder struct {
|
||||
Daemon *daemon.Daemon
|
||||
Engine *engine.Engine
|
||||
|
||||
// effectively stdio for the run. Because it is not stdio, I said
|
||||
// "Effectively". Do not use stdio anywhere in this package for any reason.
|
||||
OutStream io.Writer
|
||||
ErrStream io.Writer
|
||||
|
||||
Verbose bool
|
||||
UtilizeCache bool
|
||||
|
||||
// controls how images and containers are handled between steps.
|
||||
Remove bool
|
||||
ForceRemove bool
|
||||
|
||||
AuthConfig *registry.AuthConfig
|
||||
AuthConfigFile *registry.ConfigFile
|
||||
|
||||
// Deprecated, original writer used for ImagePull. To be removed.
|
||||
OutOld io.Writer
|
||||
StreamFormatter *utils.StreamFormatter
|
||||
|
||||
Config *runconfig.Config // runconfig for cmd, run, entrypoint etc.
|
||||
|
||||
// both of these are controlled by the Remove and ForceRemove options in BuildOpts
|
||||
TmpContainers map[string]struct{} // a map of containers used for removes
|
||||
|
||||
dockerfile *parser.Node // the syntax tree of the dockerfile
|
||||
image string // image name for commit processing
|
||||
maintainer string // maintainer name. could probably be removed.
|
||||
cmdSet bool // indicates is CMD was set in current Dockerfile
|
||||
context tarsum.TarSum // the context is a tarball that is uploaded by the client
|
||||
contextPath string // the path of the temporary directory the local context is unpacked to (server side)
|
||||
|
||||
}
|
||||
|
||||
// Run the builder with the context. This is the lynchpin of this package. This
|
||||
// will (barring errors):
|
||||
//
|
||||
// * call readContext() which will set up the temporary directory and unpack
|
||||
// the context into it.
|
||||
// * read the dockerfile
|
||||
// * parse the dockerfile
|
||||
// * walk the parse tree and execute it by dispatching to handlers. If Remove
|
||||
// or ForceRemove is set, additional cleanup around containers happens after
|
||||
// processing.
|
||||
// * Print a happy message and return the image ID.
|
||||
//
|
||||
func (b *Builder) Run(context io.Reader) (string, error) {
|
||||
if err := b.readContext(context); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := os.RemoveAll(b.contextPath); err != nil {
|
||||
log.Debugf("[BUILDER] failed to remove temporary context: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
filename := path.Join(b.contextPath, "Dockerfile")
|
||||
|
||||
fi, err := os.Stat(filename)
|
||||
if os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("Cannot build a directory without a Dockerfile")
|
||||
}
|
||||
if fi.Size() == 0 {
|
||||
return "", ErrDockerfileEmpty
|
||||
}
|
||||
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
ast, err := parser.Parse(f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
b.dockerfile = ast
|
||||
|
||||
// some initializations that would not have been supplied by the caller.
|
||||
b.Config = &runconfig.Config{Entrypoint: []string{}, Cmd: nil}
|
||||
b.TmpContainers = map[string]struct{}{}
|
||||
|
||||
for i, n := range b.dockerfile.Children {
|
||||
if err := b.dispatch(i, n); err != nil {
|
||||
if b.ForceRemove {
|
||||
b.clearTmp()
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
fmt.Fprintf(b.OutStream, " ---> %s\n", utils.TruncateID(b.image))
|
||||
if b.Remove {
|
||||
b.clearTmp()
|
||||
}
|
||||
}
|
||||
|
||||
if b.image == "" {
|
||||
return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?\n")
|
||||
}
|
||||
|
||||
fmt.Fprintf(b.OutStream, "Successfully built %s\n", utils.TruncateID(b.image))
|
||||
return b.image, nil
|
||||
}
|
||||
|
||||
// This method is the entrypoint to all statement handling routines.
|
||||
//
|
||||
// Almost all nodes will have this structure:
|
||||
// Child[Node, Node, Node] where Child is from parser.Node.Children and each
|
||||
// node comes from parser.Node.Next. This forms a "line" with a statement and
|
||||
// arguments and we process them in this normalized form by hitting
|
||||
// evaluateTable with the leaf nodes of the command and the Builder object.
|
||||
//
|
||||
// ONBUILD is a special case; in this case the parser will emit:
|
||||
// Child[Node, Child[Node, Node...]] where the first node is the literal
|
||||
// "onbuild" and the child entrypoint is the command of the ONBUILD statmeent,
|
||||
// such as `RUN` in ONBUILD RUN foo. There is special case logic in here to
|
||||
// deal with that, at least until it becomes more of a general concern with new
|
||||
// features.
|
||||
func (b *Builder) dispatch(stepN int, ast *parser.Node) error {
|
||||
cmd := ast.Value
|
||||
attrs := ast.Attributes
|
||||
original := ast.Original
|
||||
strs := []string{}
|
||||
msg := fmt.Sprintf("Step %d : %s", stepN, strings.ToUpper(cmd))
|
||||
|
||||
if cmd == "onbuild" {
|
||||
ast = ast.Next.Children[0]
|
||||
strs = append(strs, b.replaceEnv(ast.Value))
|
||||
msg += " " + ast.Value
|
||||
}
|
||||
|
||||
for ast.Next != nil {
|
||||
ast = ast.Next
|
||||
strs = append(strs, b.replaceEnv(ast.Value))
|
||||
msg += " " + ast.Value
|
||||
}
|
||||
|
||||
fmt.Fprintln(b.OutStream, msg)
|
||||
|
||||
// XXX yes, we skip any cmds that are not valid; the parser should have
|
||||
// picked these out already.
|
||||
if f, ok := evaluateTable[cmd]; ok {
|
||||
return f(b, strs, attrs, original)
|
||||
}
|
||||
|
||||
fmt.Fprintf(b.ErrStream, "# Skipping unknown instruction %s\n", strings.ToUpper(cmd))
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,687 @@
|
|||
package builder
|
||||
|
||||
// internals for handling commands. Covers many areas and a lot of
|
||||
// non-contiguous functionality. Please read the comments.
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/builder/parser"
|
||||
"github.com/docker/docker/daemon"
|
||||
imagepkg "github.com/docker/docker/image"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/log"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
"github.com/docker/docker/pkg/promise"
|
||||
"github.com/docker/docker/pkg/symlink"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/docker/docker/pkg/tarsum"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/docker/utils"
|
||||
)
|
||||
|
||||
func (b *Builder) readContext(context io.Reader) error {
|
||||
tmpdirPath, err := ioutil.TempDir("", "docker-build")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
decompressedStream, err := archive.DecompressStream(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b.context, err = tarsum.NewTarSum(decompressedStream, true, tarsum.Version0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := archive.Untar(b.context, tmpdirPath, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.contextPath = tmpdirPath
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Builder) commit(id string, autoCmd []string, comment string) error {
|
||||
if b.image == "" {
|
||||
return fmt.Errorf("Please provide a source image with `from` prior to commit")
|
||||
}
|
||||
b.Config.Image = b.image
|
||||
if id == "" {
|
||||
cmd := b.Config.Cmd
|
||||
b.Config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
|
||||
defer func(cmd []string) { b.Config.Cmd = cmd }(cmd)
|
||||
|
||||
hit, err := b.probeCache()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hit {
|
||||
return nil
|
||||
}
|
||||
|
||||
container, err := b.create()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
id = container.ID
|
||||
|
||||
if err := container.Mount(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer container.Unmount()
|
||||
}
|
||||
container := b.Daemon.Get(id)
|
||||
if container == nil {
|
||||
return fmt.Errorf("An error occured while creating the container")
|
||||
}
|
||||
|
||||
// Note: Actually copy the struct
|
||||
autoConfig := *b.Config
|
||||
autoConfig.Cmd = autoCmd
|
||||
|
||||
// Commit the container
|
||||
image, err := b.Daemon.Commit(container, "", "", "", b.maintainer, true, &autoConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.image = image.ID
|
||||
return nil
|
||||
}
|
||||
|
||||
type copyInfo struct {
|
||||
origPath string
|
||||
destPath string
|
||||
hash string
|
||||
decompress bool
|
||||
tmpDir string
|
||||
}
|
||||
|
||||
func (b *Builder) runContextCommand(args []string, allowRemote bool, allowDecompression bool, cmdName string) error {
|
||||
if b.context == nil {
|
||||
return fmt.Errorf("No context given. Impossible to use %s", cmdName)
|
||||
}
|
||||
|
||||
if len(args) < 2 {
|
||||
return fmt.Errorf("Invalid %s format - at least two arguments required", cmdName)
|
||||
}
|
||||
|
||||
dest := args[len(args)-1] // last one is always the dest
|
||||
|
||||
copyInfos := []*copyInfo{}
|
||||
|
||||
b.Config.Image = b.image
|
||||
|
||||
defer func() {
|
||||
for _, ci := range copyInfos {
|
||||
if ci.tmpDir != "" {
|
||||
os.RemoveAll(ci.tmpDir)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Loop through each src file and calculate the info we need to
|
||||
// do the copy (e.g. hash value if cached). Don't actually do
|
||||
// the copy until we've looked at all src files
|
||||
for _, orig := range args[0 : len(args)-1] {
|
||||
err := calcCopyInfo(b, cmdName, ©Infos, orig, dest, allowRemote, allowDecompression)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(copyInfos) == 0 {
|
||||
return fmt.Errorf("No source files were specified")
|
||||
}
|
||||
|
||||
if len(copyInfos) > 1 && !strings.HasSuffix(dest, "/") {
|
||||
return fmt.Errorf("When using %s with more than one source file, the destination must be a directory and end with a /", cmdName)
|
||||
}
|
||||
|
||||
// For backwards compat, if there's just one CI then use it as the
|
||||
// cache look-up string, otherwise hash 'em all into one
|
||||
var srcHash string
|
||||
var origPaths string
|
||||
|
||||
if len(copyInfos) == 1 {
|
||||
srcHash = copyInfos[0].hash
|
||||
origPaths = copyInfos[0].origPath
|
||||
} else {
|
||||
var hashs []string
|
||||
var origs []string
|
||||
for _, ci := range copyInfos {
|
||||
hashs = append(hashs, ci.hash)
|
||||
origs = append(origs, ci.origPath)
|
||||
}
|
||||
hasher := sha256.New()
|
||||
hasher.Write([]byte(strings.Join(hashs, ",")))
|
||||
srcHash = "multi:" + hex.EncodeToString(hasher.Sum(nil))
|
||||
origPaths = strings.Join(origs, " ")
|
||||
}
|
||||
|
||||
cmd := b.Config.Cmd
|
||||
b.Config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest)}
|
||||
defer func(cmd []string) { b.Config.Cmd = cmd }(cmd)
|
||||
|
||||
hit, err := b.probeCache()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// If we do not have at least one hash, never use the cache
|
||||
if hit && b.UtilizeCache {
|
||||
return nil
|
||||
}
|
||||
|
||||
container, _, err := b.Daemon.Create(b.Config, nil, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.TmpContainers[container.ID] = struct{}{}
|
||||
|
||||
if err := container.Mount(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer container.Unmount()
|
||||
|
||||
for _, ci := range copyInfos {
|
||||
if err := b.addContext(container, ci.origPath, ci.destPath, ci.decompress); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := b.commit(container.ID, cmd, fmt.Sprintf("%s %s in %s", cmdName, origPaths, dest)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func calcCopyInfo(b *Builder, cmdName string, cInfos *[]*copyInfo, origPath string, destPath string, allowRemote bool, allowDecompression bool) error {
|
||||
|
||||
if origPath != "" && origPath[0] == '/' && len(origPath) > 1 {
|
||||
origPath = origPath[1:]
|
||||
}
|
||||
origPath = strings.TrimPrefix(origPath, "./")
|
||||
|
||||
// In the remote/URL case, download it and gen its hashcode
|
||||
if utils.IsURL(origPath) {
|
||||
if !allowRemote {
|
||||
return fmt.Errorf("Source can't be a URL for %s", cmdName)
|
||||
}
|
||||
|
||||
ci := copyInfo{}
|
||||
ci.origPath = origPath
|
||||
ci.hash = origPath // default to this but can change
|
||||
ci.destPath = destPath
|
||||
ci.decompress = false
|
||||
*cInfos = append(*cInfos, &ci)
|
||||
|
||||
// Initiate the download
|
||||
resp, err := utils.Download(ci.origPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create a tmp dir
|
||||
tmpDirName, err := ioutil.TempDir(b.contextPath, "docker-remote")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ci.tmpDir = tmpDirName
|
||||
|
||||
// Create a tmp file within our tmp dir
|
||||
tmpFileName := path.Join(tmpDirName, "tmp")
|
||||
tmpFile, err := os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Download and dump result to tmp file
|
||||
if _, err := io.Copy(tmpFile, utils.ProgressReader(resp.Body, int(resp.ContentLength), b.OutOld, b.StreamFormatter, true, "", "Downloading")); err != nil {
|
||||
tmpFile.Close()
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(b.OutStream, "\n")
|
||||
tmpFile.Close()
|
||||
|
||||
// Remove the mtime of the newly created tmp file
|
||||
if err := system.UtimesNano(tmpFileName, make([]syscall.Timespec, 2)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ci.origPath = path.Join(filepath.Base(tmpDirName), filepath.Base(tmpFileName))
|
||||
|
||||
// If the destination is a directory, figure out the filename.
|
||||
if strings.HasSuffix(ci.destPath, "/") {
|
||||
u, err := url.Parse(origPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := u.Path
|
||||
if strings.HasSuffix(path, "/") {
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
parts := strings.Split(path, "/")
|
||||
filename := parts[len(parts)-1]
|
||||
if filename == "" {
|
||||
return fmt.Errorf("cannot determine filename from url: %s", u)
|
||||
}
|
||||
ci.destPath = ci.destPath + filename
|
||||
}
|
||||
|
||||
// Calc the checksum, only if we're using the cache
|
||||
if b.UtilizeCache {
|
||||
r, err := archive.Tar(tmpFileName, archive.Uncompressed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tarSum, err := tarsum.NewTarSum(r, true, tarsum.Version0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(ioutil.Discard, tarSum); err != nil {
|
||||
return err
|
||||
}
|
||||
ci.hash = tarSum.Sum(nil)
|
||||
r.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deal with wildcards
|
||||
if ContainsWildcards(origPath) {
|
||||
for _, fileInfo := range b.context.GetSums() {
|
||||
if fileInfo.Name() == "" {
|
||||
continue
|
||||
}
|
||||
match, _ := path.Match(origPath, fileInfo.Name())
|
||||
if !match {
|
||||
continue
|
||||
}
|
||||
|
||||
calcCopyInfo(b, cmdName, cInfos, fileInfo.Name(), destPath, allowRemote, allowDecompression)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Must be a dir or a file
|
||||
|
||||
if err := b.checkPathForAddition(origPath); err != nil {
|
||||
return err
|
||||
}
|
||||
fi, _ := os.Stat(path.Join(b.contextPath, origPath))
|
||||
|
||||
ci := copyInfo{}
|
||||
ci.origPath = origPath
|
||||
ci.hash = origPath
|
||||
ci.destPath = destPath
|
||||
ci.decompress = allowDecompression
|
||||
*cInfos = append(*cInfos, &ci)
|
||||
|
||||
// If not using cache don't need to do anything else.
|
||||
// If we are using a cache then calc the hash for the src file/dir
|
||||
if !b.UtilizeCache {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deal with the single file case
|
||||
if !fi.IsDir() {
|
||||
// This will match first file in sums of the archive
|
||||
fis := b.context.GetSums().GetFile(ci.origPath)
|
||||
if fis != nil {
|
||||
ci.hash = "file:" + fis.Sum()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Must be a dir
|
||||
var subfiles []string
|
||||
absOrigPath := path.Join(b.contextPath, ci.origPath)
|
||||
|
||||
// Add a trailing / to make sure we only pick up nested files under
|
||||
// the dir and not sibling files of the dir that just happen to
|
||||
// start with the same chars
|
||||
if !strings.HasSuffix(absOrigPath, "/") {
|
||||
absOrigPath += "/"
|
||||
}
|
||||
|
||||
// Need path w/o / too to find matching dir w/o trailing /
|
||||
absOrigPathNoSlash := absOrigPath[:len(absOrigPath)-1]
|
||||
|
||||
for _, fileInfo := range b.context.GetSums() {
|
||||
absFile := path.Join(b.contextPath, fileInfo.Name())
|
||||
if strings.HasPrefix(absFile, absOrigPath) || absFile == absOrigPathNoSlash {
|
||||
subfiles = append(subfiles, fileInfo.Sum())
|
||||
}
|
||||
}
|
||||
sort.Strings(subfiles)
|
||||
hasher := sha256.New()
|
||||
hasher.Write([]byte(strings.Join(subfiles, ",")))
|
||||
ci.hash = "dir:" + hex.EncodeToString(hasher.Sum(nil))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ContainsWildcards(name string) bool {
|
||||
for i := 0; i < len(name); i++ {
|
||||
ch := name[i]
|
||||
if ch == '\\' {
|
||||
i++
|
||||
} else if ch == '*' || ch == '?' || ch == '[' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *Builder) pullImage(name string) (*imagepkg.Image, error) {
|
||||
remote, tag := parsers.ParseRepositoryTag(name)
|
||||
if tag == "" {
|
||||
tag = "latest"
|
||||
}
|
||||
pullRegistryAuth := b.AuthConfig
|
||||
if len(b.AuthConfigFile.Configs) > 0 {
|
||||
// The request came with a full auth config file, we prefer to use that
|
||||
endpoint, _, err := registry.ResolveRepositoryName(remote)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolvedAuth := b.AuthConfigFile.ResolveAuthConfig(endpoint)
|
||||
pullRegistryAuth = &resolvedAuth
|
||||
}
|
||||
job := b.Engine.Job("pull", remote, tag)
|
||||
job.SetenvBool("json", b.StreamFormatter.Json())
|
||||
job.SetenvBool("parallel", true)
|
||||
job.SetenvJson("authConfig", pullRegistryAuth)
|
||||
job.Stdout.Add(b.OutOld)
|
||||
if err := job.Run(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
image, err := b.Daemon.Repositories().LookupImage(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return image, nil
|
||||
}
|
||||
|
||||
func (b *Builder) processImageFrom(img *imagepkg.Image) error {
|
||||
b.image = img.ID
|
||||
|
||||
if img.Config != nil {
|
||||
b.Config = img.Config
|
||||
}
|
||||
|
||||
if len(b.Config.Env) == 0 {
|
||||
b.Config.Env = append(b.Config.Env, "PATH="+daemon.DefaultPathEnv)
|
||||
}
|
||||
|
||||
// Process ONBUILD triggers if they exist
|
||||
if nTriggers := len(b.Config.OnBuild); nTriggers != 0 {
|
||||
fmt.Fprintf(b.ErrStream, "# Executing %d build triggers\n", nTriggers)
|
||||
}
|
||||
|
||||
// Copy the ONBUILD triggers, and remove them from the config, since the config will be commited.
|
||||
onBuildTriggers := b.Config.OnBuild
|
||||
b.Config.OnBuild = []string{}
|
||||
|
||||
// parse the ONBUILD triggers by invoking the parser
|
||||
for stepN, step := range onBuildTriggers {
|
||||
ast, err := parser.Parse(strings.NewReader(step))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, n := range ast.Children {
|
||||
switch strings.ToUpper(n.Value) {
|
||||
case "ONBUILD":
|
||||
return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
|
||||
case "MAINTAINER", "FROM":
|
||||
return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", n.Value)
|
||||
}
|
||||
|
||||
fmt.Fprintf(b.OutStream, "Trigger %d, %s\n", stepN, step)
|
||||
|
||||
if err := b.dispatch(i, n); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// probeCache checks to see if image-caching is enabled (`b.UtilizeCache`)
|
||||
// and if so attempts to look up the current `b.image` and `b.Config` pair
|
||||
// in the current server `b.Daemon`. If an image is found, probeCache returns
|
||||
// `(true, nil)`. If no image is found, it returns `(false, nil)`. If there
|
||||
// is any error, it returns `(false, err)`.
|
||||
func (b *Builder) probeCache() (bool, error) {
|
||||
if b.UtilizeCache {
|
||||
if cache, err := b.Daemon.ImageGetCached(b.image, b.Config); err != nil {
|
||||
return false, err
|
||||
} else if cache != nil {
|
||||
fmt.Fprintf(b.OutStream, " ---> Using cache\n")
|
||||
log.Debugf("[BUILDER] Use cached version")
|
||||
b.image = cache.ID
|
||||
return true, nil
|
||||
} else {
|
||||
log.Debugf("[BUILDER] Cache miss")
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (b *Builder) create() (*daemon.Container, error) {
|
||||
if b.image == "" {
|
||||
return nil, fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||
}
|
||||
b.Config.Image = b.image
|
||||
|
||||
config := *b.Config
|
||||
|
||||
// Create the container
|
||||
c, warnings, err := b.Daemon.Create(b.Config, nil, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, warning := range warnings {
|
||||
fmt.Fprintf(b.OutStream, " ---> [Warning] %s\n", warning)
|
||||
}
|
||||
|
||||
b.TmpContainers[c.ID] = struct{}{}
|
||||
fmt.Fprintf(b.OutStream, " ---> Running in %s\n", utils.TruncateID(c.ID))
|
||||
|
||||
// override the entry point that may have been picked up from the base image
|
||||
c.Path = config.Cmd[0]
|
||||
c.Args = config.Cmd[1:]
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (b *Builder) run(c *daemon.Container) error {
|
||||
var errCh chan error
|
||||
if b.Verbose {
|
||||
errCh = promise.Go(func() error {
|
||||
// FIXME: call the 'attach' job so that daemon.Attach can be made private
|
||||
//
|
||||
// FIXME (LK4D4): Also, maybe makes sense to call "logs" job, it is like attach
|
||||
// but without hijacking for stdin. Also, with attach there can be race
|
||||
// condition because of some output already was printed before it.
|
||||
return <-b.Daemon.Attach(&c.StreamConfig, c.Config.OpenStdin, c.Config.StdinOnce, c.Config.Tty, nil, nil, b.OutStream, b.ErrStream)
|
||||
})
|
||||
}
|
||||
|
||||
//start the container
|
||||
if err := c.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if errCh != nil {
|
||||
if err := <-errCh; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for it to finish
|
||||
if ret, _ := c.WaitStop(-1 * time.Second); ret != 0 {
|
||||
err := &utils.JSONError{
|
||||
Message: fmt.Sprintf("The command %v returned a non-zero code: %d", b.Config.Cmd, ret),
|
||||
Code: ret,
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Builder) checkPathForAddition(orig string) error {
|
||||
origPath := path.Join(b.contextPath, orig)
|
||||
origPath, err := filepath.EvalSymlinks(origPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("%s: no such file or directory", orig)
|
||||
}
|
||||
return err
|
||||
}
|
||||
if !strings.HasPrefix(origPath, b.contextPath) {
|
||||
return fmt.Errorf("Forbidden path outside the build context: %s (%s)", orig, origPath)
|
||||
}
|
||||
if _, err := os.Stat(origPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("%s: no such file or directory", orig)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Builder) addContext(container *daemon.Container, orig, dest string, decompress bool) error {
|
||||
var (
|
||||
err error
|
||||
destExists = true
|
||||
origPath = path.Join(b.contextPath, orig)
|
||||
destPath = path.Join(container.RootfsPath(), dest)
|
||||
)
|
||||
|
||||
if destPath != container.RootfsPath() {
|
||||
destPath, err = symlink.FollowSymlinkInScope(destPath, container.RootfsPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Preserve the trailing '/'
|
||||
if strings.HasSuffix(dest, "/") || dest == "." {
|
||||
destPath = destPath + "/"
|
||||
}
|
||||
|
||||
destStat, err := os.Stat(destPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
destExists = false
|
||||
}
|
||||
|
||||
fi, err := os.Stat(origPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("%s: no such file or directory", orig)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
return copyAsDirectory(origPath, destPath, destExists)
|
||||
}
|
||||
|
||||
// If we are adding a remote file (or we've been told not to decompress), do not try to untar it
|
||||
if decompress {
|
||||
// First try to unpack the source as an archive
|
||||
// to support the untar feature we need to clean up the path a little bit
|
||||
// because tar is very forgiving. First we need to strip off the archive's
|
||||
// filename from the path but this is only added if it does not end in / .
|
||||
tarDest := destPath
|
||||
if strings.HasSuffix(tarDest, "/") {
|
||||
tarDest = filepath.Dir(destPath)
|
||||
}
|
||||
|
||||
// try to successfully untar the orig
|
||||
if err := archive.UntarPath(origPath, tarDest); err == nil {
|
||||
return nil
|
||||
} else if err != io.EOF {
|
||||
log.Debugf("Couldn't untar %s to %s: %s", origPath, tarDest, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := archive.CopyWithTar(origPath, destPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resPath := destPath
|
||||
if destExists && destStat.IsDir() {
|
||||
resPath = path.Join(destPath, path.Base(origPath))
|
||||
}
|
||||
|
||||
return fixPermissions(resPath, 0, 0)
|
||||
}
|
||||
|
||||
func copyAsDirectory(source, destination string, destinationExists bool) error {
|
||||
if err := archive.CopyWithTar(source, destination); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if destinationExists {
|
||||
files, err := ioutil.ReadDir(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if err := fixPermissions(filepath.Join(destination, file.Name()), 0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fixPermissions(destination, 0, 0)
|
||||
}
|
||||
|
||||
func fixPermissions(destination string, uid, gid int) error {
|
||||
return filepath.Walk(destination, func(path string, info os.FileInfo, err error) error {
|
||||
if err := os.Lchown(path, uid, gid); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (b *Builder) clearTmp() {
|
||||
for c := range b.TmpContainers {
|
||||
tmp := b.Daemon.Get(c)
|
||||
if err := b.Daemon.Destroy(tmp); err != nil {
|
||||
fmt.Fprintf(b.OutStream, "Error removing intermediate container %s: %s\n", utils.TruncateID(c), err.Error())
|
||||
return
|
||||
}
|
||||
b.Daemon.DeleteVolumes(tmp.VolumePaths())
|
||||
delete(b.TmpContainers, c)
|
||||
fmt.Fprintf(b.OutStream, "Removing intermediate container %s\n", utils.TruncateID(c))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
package builder
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/daemon"
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/graph"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/docker/utils"
|
||||
)
|
||||
|
||||
type BuilderJob struct {
|
||||
Engine *engine.Engine
|
||||
Daemon *daemon.Daemon
|
||||
}
|
||||
|
||||
func (b *BuilderJob) Install() {
|
||||
b.Engine.Register("build", b.CmdBuild)
|
||||
}
|
||||
|
||||
func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
|
||||
if len(job.Args) != 0 {
|
||||
return job.Errorf("Usage: %s\n", job.Name)
|
||||
}
|
||||
var (
|
||||
remoteURL = job.Getenv("remote")
|
||||
repoName = job.Getenv("t")
|
||||
suppressOutput = job.GetenvBool("q")
|
||||
noCache = job.GetenvBool("nocache")
|
||||
rm = job.GetenvBool("rm")
|
||||
forceRm = job.GetenvBool("forcerm")
|
||||
authConfig = ®istry.AuthConfig{}
|
||||
configFile = ®istry.ConfigFile{}
|
||||
tag string
|
||||
context io.ReadCloser
|
||||
)
|
||||
job.GetenvJson("authConfig", authConfig)
|
||||
job.GetenvJson("configFile", configFile)
|
||||
|
||||
repoName, tag = parsers.ParseRepositoryTag(repoName)
|
||||
if repoName != "" {
|
||||
if _, _, err := registry.ResolveRepositoryName(repoName); err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
if len(tag) > 0 {
|
||||
if err := graph.ValidateTagName(tag); err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if remoteURL == "" {
|
||||
context = ioutil.NopCloser(job.Stdin)
|
||||
} else if utils.IsGIT(remoteURL) {
|
||||
if !strings.HasPrefix(remoteURL, "git://") {
|
||||
remoteURL = "https://" + remoteURL
|
||||
}
|
||||
root, err := ioutil.TempDir("", "docker-build-git")
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
defer os.RemoveAll(root)
|
||||
|
||||
if output, err := exec.Command("git", "clone", "--recursive", remoteURL, root).CombinedOutput(); err != nil {
|
||||
return job.Errorf("Error trying to use git: %s (%s)", err, output)
|
||||
}
|
||||
|
||||
c, err := archive.Tar(root, archive.Uncompressed)
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
context = c
|
||||
} else if utils.IsURL(remoteURL) {
|
||||
f, err := utils.Download(remoteURL)
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
defer f.Body.Close()
|
||||
dockerFile, err := ioutil.ReadAll(f.Body)
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
c, err := archive.Generate("Dockerfile", string(dockerFile))
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
context = c
|
||||
}
|
||||
defer context.Close()
|
||||
|
||||
sf := utils.NewStreamFormatter(job.GetenvBool("json"))
|
||||
|
||||
builder := &Builder{
|
||||
Daemon: b.Daemon,
|
||||
Engine: b.Engine,
|
||||
OutStream: &utils.StdoutFormater{
|
||||
Writer: job.Stdout,
|
||||
StreamFormatter: sf,
|
||||
},
|
||||
ErrStream: &utils.StderrFormater{
|
||||
Writer: job.Stdout,
|
||||
StreamFormatter: sf,
|
||||
},
|
||||
Verbose: !suppressOutput,
|
||||
UtilizeCache: !noCache,
|
||||
Remove: rm,
|
||||
ForceRemove: forceRm,
|
||||
OutOld: job.Stdout,
|
||||
StreamFormatter: sf,
|
||||
AuthConfig: authConfig,
|
||||
AuthConfigFile: configFile,
|
||||
}
|
||||
|
||||
id, err := builder.Run(context)
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
|
||||
if repoName != "" {
|
||||
b.Daemon.Repositories().Set(repoName, tag, id, false)
|
||||
}
|
||||
return engine.StatusOK
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/docker/docker/builder/parser"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var f *os.File
|
||||
var err error
|
||||
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Println("please supply filename(s)")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for _, fn := range os.Args[1:] {
|
||||
f, err = os.Open(fn)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ast, err := parser.Parse(f)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
fmt.Println(ast.Dump())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
package parser
|
||||
|
||||
// line parsers are dispatch calls that parse a single unit of text into a
|
||||
// Node object which contains the whole statement. Dockerfiles have varied
|
||||
// (but not usually unique, see ONBUILD for a unique example) parsing rules
|
||||
// per-command, and these unify the processing in a way that makes it
|
||||
// manageable.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
errDockerfileJSONNesting = errors.New("You may not nest arrays in Dockerfile statements.")
|
||||
)
|
||||
|
||||
// ignore the current argument. This will still leave a command parsed, but
|
||||
// will not incorporate the arguments into the ast.
|
||||
func parseIgnore(rest string) (*Node, map[string]bool, error) {
|
||||
return &Node{}, nil, nil
|
||||
}
|
||||
|
||||
// used for onbuild. Could potentially be used for anything that represents a
|
||||
// statement with sub-statements.
|
||||
//
|
||||
// ONBUILD RUN foo bar -> (onbuild (run foo bar))
|
||||
//
|
||||
func parseSubCommand(rest string) (*Node, map[string]bool, error) {
|
||||
_, child, err := parseLine(rest)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return &Node{Children: []*Node{child}}, nil, nil
|
||||
}
|
||||
|
||||
// parse environment like statements. Note that this does *not* handle
|
||||
// variable interpolation, which will be handled in the evaluator.
|
||||
func parseEnv(rest string) (*Node, map[string]bool, error) {
|
||||
node := &Node{}
|
||||
rootnode := node
|
||||
strs := TOKEN_WHITESPACE.Split(rest, 2)
|
||||
|
||||
if len(strs) < 2 {
|
||||
return nil, nil, fmt.Errorf("ENV must have two arguments")
|
||||
}
|
||||
|
||||
node.Value = strs[0]
|
||||
node.Next = &Node{}
|
||||
node.Next.Value = strs[1]
|
||||
|
||||
return rootnode, nil, nil
|
||||
}
|
||||
|
||||
// parses a whitespace-delimited set of arguments. The result is effectively a
|
||||
// linked list of string arguments.
|
||||
func parseStringsWhitespaceDelimited(rest string) (*Node, map[string]bool, error) {
|
||||
node := &Node{}
|
||||
rootnode := node
|
||||
prevnode := node
|
||||
for _, str := range TOKEN_WHITESPACE.Split(rest, -1) { // use regexp
|
||||
prevnode = node
|
||||
node.Value = str
|
||||
node.Next = &Node{}
|
||||
node = node.Next
|
||||
}
|
||||
|
||||
// XXX to get around regexp.Split *always* providing an empty string at the
|
||||
// end due to how our loop is constructed, nil out the last node in the
|
||||
// chain.
|
||||
prevnode.Next = nil
|
||||
|
||||
return rootnode, nil, nil
|
||||
}
|
||||
|
||||
// parsestring just wraps the string in quotes and returns a working node.
|
||||
func parseString(rest string) (*Node, map[string]bool, error) {
|
||||
n := &Node{}
|
||||
n.Value = rest
|
||||
return n, nil, nil
|
||||
}
|
||||
|
||||
// parseJSON converts JSON arrays to an AST.
|
||||
func parseJSON(rest string) (*Node, map[string]bool, error) {
|
||||
var (
|
||||
myJson []interface{}
|
||||
next = &Node{}
|
||||
orignext = next
|
||||
prevnode = next
|
||||
)
|
||||
|
||||
if err := json.Unmarshal([]byte(rest), &myJson); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, str := range myJson {
|
||||
switch str.(type) {
|
||||
case string:
|
||||
case float64:
|
||||
str = strconv.FormatFloat(str.(float64), 'G', -1, 64)
|
||||
default:
|
||||
return nil, nil, errDockerfileJSONNesting
|
||||
}
|
||||
next.Value = str.(string)
|
||||
next.Next = &Node{}
|
||||
prevnode = next
|
||||
next = next.Next
|
||||
}
|
||||
|
||||
prevnode.Next = nil
|
||||
|
||||
return orignext, map[string]bool{"json": true}, nil
|
||||
}
|
||||
|
||||
// parseMaybeJSON determines if the argument appears to be a JSON array. If
|
||||
// so, passes to parseJSON; if not, quotes the result and returns a single
|
||||
// node.
|
||||
func parseMaybeJSON(rest string) (*Node, map[string]bool, error) {
|
||||
rest = strings.TrimSpace(rest)
|
||||
|
||||
node, attrs, err := parseJSON(rest)
|
||||
|
||||
if err == nil {
|
||||
return node, attrs, nil
|
||||
}
|
||||
if err == errDockerfileJSONNesting {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
node = &Node{}
|
||||
node.Value = rest
|
||||
return node, nil, nil
|
||||
}
|
||||
|
||||
// parseMaybeJSONToList determines if the argument appears to be a JSON array. If
|
||||
// so, passes to parseJSON; if not, attmpts to parse it as a whitespace
|
||||
// delimited string.
|
||||
func parseMaybeJSONToList(rest string) (*Node, map[string]bool, error) {
|
||||
rest = strings.TrimSpace(rest)
|
||||
|
||||
node, attrs, err := parseJSON(rest)
|
||||
|
||||
if err == nil {
|
||||
return node, attrs, nil
|
||||
}
|
||||
if err == errDockerfileJSONNesting {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return parseStringsWhitespaceDelimited(rest)
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
// This package implements a parser and parse tree dumper for Dockerfiles.
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Node is a structure used to represent a parse tree.
|
||||
//
|
||||
// In the node there are three fields, Value, Next, and Children. Value is the
|
||||
// current token's string value. Next is always the next non-child token, and
|
||||
// children contains all the children. Here's an example:
|
||||
//
|
||||
// (value next (child child-next child-next-next) next-next)
|
||||
//
|
||||
// This data structure is frankly pretty lousy for handling complex languages,
|
||||
// but lucky for us the Dockerfile isn't very complicated. This structure
|
||||
// works a little more effectively than a "proper" parse tree for our needs.
|
||||
//
|
||||
type Node struct {
|
||||
Value string // actual content
|
||||
Next *Node // the next item in the current sexp
|
||||
Children []*Node // the children of this sexp
|
||||
Attributes map[string]bool // special attributes for this node
|
||||
Original string // original line used before parsing
|
||||
}
|
||||
|
||||
var (
|
||||
dispatch map[string]func(string) (*Node, map[string]bool, error)
|
||||
TOKEN_WHITESPACE = regexp.MustCompile(`[\t\v\f\r ]+`)
|
||||
TOKEN_LINE_CONTINUATION = regexp.MustCompile(`\\\s*$`)
|
||||
TOKEN_COMMENT = regexp.MustCompile(`^#.*$`)
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Dispatch Table. see line_parsers.go for the parse functions.
|
||||
// The command is parsed and mapped to the line parser. The line parser
|
||||
// recieves the arguments but not the command, and returns an AST after
|
||||
// reformulating the arguments according to the rules in the parser
|
||||
// functions. Errors are propogated up by Parse() and the resulting AST can
|
||||
// be incorporated directly into the existing AST as a next.
|
||||
dispatch = map[string]func(string) (*Node, map[string]bool, error){
|
||||
"user": parseString,
|
||||
"onbuild": parseSubCommand,
|
||||
"workdir": parseString,
|
||||
"env": parseEnv,
|
||||
"maintainer": parseString,
|
||||
"from": parseString,
|
||||
"add": parseStringsWhitespaceDelimited,
|
||||
"copy": parseStringsWhitespaceDelimited,
|
||||
"run": parseMaybeJSON,
|
||||
"cmd": parseMaybeJSON,
|
||||
"entrypoint": parseMaybeJSON,
|
||||
"expose": parseStringsWhitespaceDelimited,
|
||||
"volume": parseMaybeJSONToList,
|
||||
"insert": parseIgnore,
|
||||
}
|
||||
}
|
||||
|
||||
// parse a line and return the remainder.
|
||||
func parseLine(line string) (string, *Node, error) {
|
||||
if line = stripComments(line); line == "" {
|
||||
return "", nil, nil
|
||||
}
|
||||
|
||||
if TOKEN_LINE_CONTINUATION.MatchString(line) {
|
||||
line = TOKEN_LINE_CONTINUATION.ReplaceAllString(line, "")
|
||||
return line, nil, nil
|
||||
}
|
||||
|
||||
cmd, args, err := splitCommand(line)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
node := &Node{}
|
||||
node.Value = cmd
|
||||
|
||||
sexp, attrs, err := fullDispatch(cmd, args)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
if sexp.Value != "" || sexp.Next != nil || sexp.Children != nil {
|
||||
node.Next = sexp
|
||||
node.Attributes = attrs
|
||||
node.Original = line
|
||||
}
|
||||
|
||||
return "", node, nil
|
||||
}
|
||||
|
||||
// The main parse routine. Handles an io.ReadWriteCloser and returns the root
|
||||
// of the AST.
|
||||
func Parse(rwc io.Reader) (*Node, error) {
|
||||
root := &Node{}
|
||||
scanner := bufio.NewScanner(rwc)
|
||||
|
||||
for scanner.Scan() {
|
||||
scannedLine := strings.TrimLeftFunc(scanner.Text(), unicode.IsSpace)
|
||||
if stripComments(scannedLine) == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
line, child, err := parseLine(scannedLine)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if line != "" && child == nil {
|
||||
for scanner.Scan() {
|
||||
newline := scanner.Text()
|
||||
|
||||
if stripComments(strings.TrimSpace(newline)) == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
line, child, err = parseLine(line + newline)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if child != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if child != nil {
|
||||
root.Children = append(root.Children, child)
|
||||
}
|
||||
}
|
||||
|
||||
return root, nil
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const testDir = "testfiles"
|
||||
const negativeTestDir = "testfiles-negative"
|
||||
|
||||
func getDirs(t *testing.T, dir string) []os.FileInfo {
|
||||
f, err := os.Open(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
dirs, err := f.Readdir(0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return dirs
|
||||
}
|
||||
|
||||
func TestTestNegative(t *testing.T) {
|
||||
for _, dir := range getDirs(t, negativeTestDir) {
|
||||
dockerfile := filepath.Join(negativeTestDir, dir.Name(), "Dockerfile")
|
||||
|
||||
df, err := os.Open(dockerfile)
|
||||
if err != nil {
|
||||
t.Fatalf("Dockerfile missing for %s: %s", dir.Name(), err.Error())
|
||||
}
|
||||
|
||||
_, err = Parse(df)
|
||||
if err == nil {
|
||||
t.Fatalf("No error parsing broken dockerfile for %s", dir.Name())
|
||||
}
|
||||
|
||||
df.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestData(t *testing.T) {
|
||||
for _, dir := range getDirs(t, testDir) {
|
||||
dockerfile := filepath.Join(testDir, dir.Name(), "Dockerfile")
|
||||
resultfile := filepath.Join(testDir, dir.Name(), "result")
|
||||
|
||||
df, err := os.Open(dockerfile)
|
||||
if err != nil {
|
||||
t.Fatalf("Dockerfile missing for %s: %s", dir.Name(), err.Error())
|
||||
}
|
||||
|
||||
rf, err := os.Open(resultfile)
|
||||
if err != nil {
|
||||
t.Fatalf("Result file missing for %s: %s", dir.Name(), err.Error())
|
||||
}
|
||||
|
||||
ast, err := Parse(df)
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing %s's dockerfile: %s", dir.Name(), err.Error())
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadAll(rf)
|
||||
if err != nil {
|
||||
t.Fatalf("Error reading %s's result file: %s", dir.Name(), err.Error())
|
||||
}
|
||||
|
||||
if ast.Dump()+"\n" != string(content) {
|
||||
fmt.Fprintln(os.Stderr, "Result:\n"+ast.Dump())
|
||||
fmt.Fprintln(os.Stderr, "Expected:\n"+string(content))
|
||||
t.Fatalf("%s: AST dump of dockerfile does not match result", dir.Name())
|
||||
}
|
||||
|
||||
df.Close()
|
||||
rf.Close()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
FROM busybox
|
||||
|
||||
ENV PATH=PATH
|
|
@ -0,0 +1,2 @@
|
|||
<html>
|
||||
</html>
|
|
@ -0,0 +1 @@
|
|||
CMD [ "echo", [ "nested json" ] ]
|
|
@ -0,0 +1,25 @@
|
|||
FROM brimstone/ubuntu:14.04
|
||||
|
||||
MAINTAINER brimstone@the.narro.ws
|
||||
|
||||
# TORUN -v /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
ENV GOPATH /go
|
||||
|
||||
# Set our command
|
||||
ENTRYPOINT ["/usr/local/bin/consuldock"]
|
||||
|
||||
# Install the packages we need, clean up after them and us
|
||||
RUN apt-get update \
|
||||
&& dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.clean \
|
||||
&& apt-get install -y --no-install-recommends git golang ca-certificates \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists \
|
||||
|
||||
&& go get -v github.com/brimstone/consuldock \
|
||||
&& mv $GOPATH/bin/consuldock /usr/local/bin/consuldock \
|
||||
|
||||
&& dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.dirty \
|
||||
&& apt-get remove --purge -y $(diff /tmp/dpkg.clean /tmp/dpkg.dirty | awk '/^>/ {print $2}') \
|
||||
&& rm /tmp/dpkg.* \
|
||||
&& rm -rf $GOPATH
|
|
@ -0,0 +1,5 @@
|
|||
(from "brimstone/ubuntu:14.04")
|
||||
(maintainer "brimstone@the.narro.ws")
|
||||
(env "GOPATH" "/go")
|
||||
(entrypoint "/usr/local/bin/consuldock")
|
||||
(run "apt-get update && dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.clean && apt-get install -y --no-install-recommends git golang ca-certificates && apt-get clean && rm -rf /var/lib/apt/lists && go get -v github.com/brimstone/consuldock && mv $GOPATH/bin/consuldock /usr/local/bin/consuldock && dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.dirty && apt-get remove --purge -y $(diff /tmp/dpkg.clean /tmp/dpkg.dirty | awk '/^>/ {print $2}') && rm /tmp/dpkg.* && rm -rf $GOPATH")
|
|
@ -0,0 +1,52 @@
|
|||
FROM brimstone/ubuntu:14.04
|
||||
|
||||
CMD []
|
||||
|
||||
ENTRYPOINT ["/usr/bin/consul", "agent", "-server", "-data-dir=/consul", "-client=0.0.0.0", "-ui-dir=/webui"]
|
||||
|
||||
EXPOSE 8500 8600 8400 8301 8302
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y unzip wget \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists
|
||||
|
||||
RUN cd /tmp \
|
||||
&& wget https://dl.bintray.com/mitchellh/consul/0.3.1_web_ui.zip \
|
||||
-O web_ui.zip \
|
||||
&& unzip web_ui.zip \
|
||||
&& mv dist /webui \
|
||||
&& rm web_ui.zip
|
||||
|
||||
RUN apt-get update \
|
||||
&& dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.clean \
|
||||
&& apt-get install -y --no-install-recommends unzip wget \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists \
|
||||
|
||||
&& cd /tmp \
|
||||
&& wget https://dl.bintray.com/mitchellh/consul/0.3.1_web_ui.zip \
|
||||
-O web_ui.zip \
|
||||
&& unzip web_ui.zip \
|
||||
&& mv dist /webui \
|
||||
&& rm web_ui.zip \
|
||||
|
||||
&& dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.dirty \
|
||||
&& apt-get remove --purge -y $(diff /tmp/dpkg.clean /tmp/dpkg.dirty | awk '/^>/ {print $2}') \
|
||||
&& rm /tmp/dpkg.*
|
||||
|
||||
ENV GOPATH /go
|
||||
|
||||
RUN apt-get update \
|
||||
&& dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.clean \
|
||||
&& apt-get install -y --no-install-recommends git golang ca-certificates build-essential \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists \
|
||||
|
||||
&& go get -v github.com/hashicorp/consul \
|
||||
&& mv $GOPATH/bin/consul /usr/bin/consul \
|
||||
|
||||
&& dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.dirty \
|
||||
&& apt-get remove --purge -y $(diff /tmp/dpkg.clean /tmp/dpkg.dirty | awk '/^>/ {print $2}') \
|
||||
&& rm /tmp/dpkg.* \
|
||||
&& rm -rf $GOPATH
|
|
@ -0,0 +1,9 @@
|
|||
(from "brimstone/ubuntu:14.04")
|
||||
(cmd)
|
||||
(entrypoint "/usr/bin/consul" "agent" "-server" "-data-dir=/consul" "-client=0.0.0.0" "-ui-dir=/webui")
|
||||
(expose "8500" "8600" "8400" "8301" "8302")
|
||||
(run "apt-get update && apt-get install -y unzip wget && apt-get clean && rm -rf /var/lib/apt/lists")
|
||||
(run "cd /tmp && wget https://dl.bintray.com/mitchellh/consul/0.3.1_web_ui.zip -O web_ui.zip && unzip web_ui.zip && mv dist /webui && rm web_ui.zip")
|
||||
(run "apt-get update && dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.clean && apt-get install -y --no-install-recommends unzip wget && apt-get clean && rm -rf /var/lib/apt/lists && cd /tmp && wget https://dl.bintray.com/mitchellh/consul/0.3.1_web_ui.zip -O web_ui.zip && unzip web_ui.zip && mv dist /webui && rm web_ui.zip && dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.dirty && apt-get remove --purge -y $(diff /tmp/dpkg.clean /tmp/dpkg.dirty | awk '/^>/ {print $2}') && rm /tmp/dpkg.*")
|
||||
(env "GOPATH" "/go")
|
||||
(run "apt-get update && dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.clean && apt-get install -y --no-install-recommends git golang ca-certificates build-essential && apt-get clean && rm -rf /var/lib/apt/lists && go get -v github.com/hashicorp/consul && mv $GOPATH/bin/consul /usr/bin/consul && dpkg -l | awk '/^ii/ {print $2}' > /tmp/dpkg.dirty && apt-get remove --purge -y $(diff /tmp/dpkg.clean /tmp/dpkg.dirty | awk '/^>/ {print $2}') && rm /tmp/dpkg.* && rm -rf $GOPATH")
|
|
@ -0,0 +1,36 @@
|
|||
FROM ubuntu:14.04
|
||||
|
||||
RUN echo hello\
|
||||
world\
|
||||
goodnight \
|
||||
moon\
|
||||
light\
|
||||
ning
|
||||
RUN echo hello \
|
||||
world
|
||||
RUN echo hello \
|
||||
world
|
||||
RUN echo hello \
|
||||
goodbye\
|
||||
frog
|
||||
RUN echo hello \
|
||||
world
|
||||
RUN echo hi \
|
||||
\
|
||||
world \
|
||||
\
|
||||
good\
|
||||
\
|
||||
night
|
||||
RUN echo goodbye\
|
||||
frog
|
||||
RUN echo good\
|
||||
bye\
|
||||
frog
|
||||
|
||||
RUN echo hello \
|
||||
# this is a comment
|
||||
|
||||
# this is a comment with a blank line surrounding it
|
||||
|
||||
this is some more useful stuff
|
|
@ -0,0 +1,10 @@
|
|||
(from "ubuntu:14.04")
|
||||
(run "echo hello world goodnight moon lightning")
|
||||
(run "echo hello world")
|
||||
(run "echo hello world")
|
||||
(run "echo hello goodbyefrog")
|
||||
(run "echo hello world")
|
||||
(run "echo hi world goodnight")
|
||||
(run "echo goodbyefrog")
|
||||
(run "echo goodbyefrog")
|
||||
(run "echo hello this is some more useful stuff")
|
|
@ -0,0 +1,54 @@
|
|||
FROM cpuguy83/ubuntu
|
||||
ENV NAGIOS_HOME /opt/nagios
|
||||
ENV NAGIOS_USER nagios
|
||||
ENV NAGIOS_GROUP nagios
|
||||
ENV NAGIOS_CMDUSER nagios
|
||||
ENV NAGIOS_CMDGROUP nagios
|
||||
ENV NAGIOSADMIN_USER nagiosadmin
|
||||
ENV NAGIOSADMIN_PASS nagios
|
||||
ENV APACHE_RUN_USER nagios
|
||||
ENV APACHE_RUN_GROUP nagios
|
||||
ENV NAGIOS_TIMEZONE UTC
|
||||
|
||||
RUN sed -i 's/universe/universe multiverse/' /etc/apt/sources.list
|
||||
RUN apt-get update && apt-get install -y iputils-ping netcat build-essential snmp snmpd snmp-mibs-downloader php5-cli apache2 libapache2-mod-php5 runit bc postfix bsd-mailx
|
||||
RUN ( egrep -i "^${NAGIOS_GROUP}" /etc/group || groupadd $NAGIOS_GROUP ) && ( egrep -i "^${NAGIOS_CMDGROUP}" /etc/group || groupadd $NAGIOS_CMDGROUP )
|
||||
RUN ( id -u $NAGIOS_USER || useradd --system $NAGIOS_USER -g $NAGIOS_GROUP -d $NAGIOS_HOME ) && ( id -u $NAGIOS_CMDUSER || useradd --system -d $NAGIOS_HOME -g $NAGIOS_CMDGROUP $NAGIOS_CMDUSER )
|
||||
|
||||
ADD http://downloads.sourceforge.net/project/nagios/nagios-3.x/nagios-3.5.1/nagios-3.5.1.tar.gz?r=http%3A%2F%2Fwww.nagios.org%2Fdownload%2Fcore%2Fthanks%2F%3Ft%3D1398863696&ts=1398863718&use_mirror=superb-dca3 /tmp/nagios.tar.gz
|
||||
RUN cd /tmp && tar -zxvf nagios.tar.gz && cd nagios && ./configure --prefix=${NAGIOS_HOME} --exec-prefix=${NAGIOS_HOME} --enable-event-broker --with-nagios-command-user=${NAGIOS_CMDUSER} --with-command-group=${NAGIOS_CMDGROUP} --with-nagios-user=${NAGIOS_USER} --with-nagios-group=${NAGIOS_GROUP} && make all && make install && make install-config && make install-commandmode && cp sample-config/httpd.conf /etc/apache2/conf.d/nagios.conf
|
||||
ADD http://www.nagios-plugins.org/download/nagios-plugins-1.5.tar.gz /tmp/
|
||||
RUN cd /tmp && tar -zxvf nagios-plugins-1.5.tar.gz && cd nagios-plugins-1.5 && ./configure --prefix=${NAGIOS_HOME} && make && make install
|
||||
|
||||
RUN sed -i.bak 's/.*\=www\-data//g' /etc/apache2/envvars
|
||||
RUN export DOC_ROOT="DocumentRoot $(echo $NAGIOS_HOME/share)"; sed -i "s,DocumentRoot.*,$DOC_ROOT," /etc/apache2/sites-enabled/000-default
|
||||
|
||||
RUN ln -s ${NAGIOS_HOME}/bin/nagios /usr/local/bin/nagios && mkdir -p /usr/share/snmp/mibs && chmod 0755 /usr/share/snmp/mibs && touch /usr/share/snmp/mibs/.foo
|
||||
|
||||
RUN echo "use_timezone=$NAGIOS_TIMEZONE" >> ${NAGIOS_HOME}/etc/nagios.cfg && echo "SetEnv TZ \"${NAGIOS_TIMEZONE}\"" >> /etc/apache2/conf.d/nagios.conf
|
||||
|
||||
RUN mkdir -p ${NAGIOS_HOME}/etc/conf.d && mkdir -p ${NAGIOS_HOME}/etc/monitor && ln -s /usr/share/snmp/mibs ${NAGIOS_HOME}/libexec/mibs
|
||||
RUN echo "cfg_dir=${NAGIOS_HOME}/etc/conf.d" >> ${NAGIOS_HOME}/etc/nagios.cfg
|
||||
RUN echo "cfg_dir=${NAGIOS_HOME}/etc/monitor" >> ${NAGIOS_HOME}/etc/nagios.cfg
|
||||
RUN download-mibs && echo "mibs +ALL" > /etc/snmp/snmp.conf
|
||||
|
||||
RUN sed -i 's,/bin/mail,/usr/bin/mail,' /opt/nagios/etc/objects/commands.cfg && \
|
||||
sed -i 's,/usr/usr,/usr,' /opt/nagios/etc/objects/commands.cfg
|
||||
RUN cp /etc/services /var/spool/postfix/etc/
|
||||
|
||||
RUN mkdir -p /etc/sv/nagios && mkdir -p /etc/sv/apache && rm -rf /etc/sv/getty-5 && mkdir -p /etc/sv/postfix
|
||||
ADD nagios.init /etc/sv/nagios/run
|
||||
ADD apache.init /etc/sv/apache/run
|
||||
ADD postfix.init /etc/sv/postfix/run
|
||||
ADD postfix.stop /etc/sv/postfix/finish
|
||||
|
||||
ADD start.sh /usr/local/bin/start_nagios
|
||||
|
||||
ENV APACHE_LOCK_DIR /var/run
|
||||
ENV APACHE_LOG_DIR /var/log/apache2
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
VOLUME ["/opt/nagios/var", "/opt/nagios/etc", "/opt/nagios/libexec", "/var/log/apache2", "/usr/share/snmp/mibs"]
|
||||
|
||||
CMD ["/usr/local/bin/start_nagios"]
|
|
@ -0,0 +1,40 @@
|
|||
(from "cpuguy83/ubuntu")
|
||||
(env "NAGIOS_HOME" "/opt/nagios")
|
||||
(env "NAGIOS_USER" "nagios")
|
||||
(env "NAGIOS_GROUP" "nagios")
|
||||
(env "NAGIOS_CMDUSER" "nagios")
|
||||
(env "NAGIOS_CMDGROUP" "nagios")
|
||||
(env "NAGIOSADMIN_USER" "nagiosadmin")
|
||||
(env "NAGIOSADMIN_PASS" "nagios")
|
||||
(env "APACHE_RUN_USER" "nagios")
|
||||
(env "APACHE_RUN_GROUP" "nagios")
|
||||
(env "NAGIOS_TIMEZONE" "UTC")
|
||||
(run "sed -i 's/universe/universe multiverse/' /etc/apt/sources.list")
|
||||
(run "apt-get update && apt-get install -y iputils-ping netcat build-essential snmp snmpd snmp-mibs-downloader php5-cli apache2 libapache2-mod-php5 runit bc postfix bsd-mailx")
|
||||
(run "( egrep -i \"^${NAGIOS_GROUP}\" /etc/group || groupadd $NAGIOS_GROUP ) && ( egrep -i \"^${NAGIOS_CMDGROUP}\" /etc/group || groupadd $NAGIOS_CMDGROUP )")
|
||||
(run "( id -u $NAGIOS_USER || useradd --system $NAGIOS_USER -g $NAGIOS_GROUP -d $NAGIOS_HOME ) && ( id -u $NAGIOS_CMDUSER || useradd --system -d $NAGIOS_HOME -g $NAGIOS_CMDGROUP $NAGIOS_CMDUSER )")
|
||||
(add "http://downloads.sourceforge.net/project/nagios/nagios-3.x/nagios-3.5.1/nagios-3.5.1.tar.gz?r=http%3A%2F%2Fwww.nagios.org%2Fdownload%2Fcore%2Fthanks%2F%3Ft%3D1398863696&ts=1398863718&use_mirror=superb-dca3" "/tmp/nagios.tar.gz")
|
||||
(run "cd /tmp && tar -zxvf nagios.tar.gz && cd nagios && ./configure --prefix=${NAGIOS_HOME} --exec-prefix=${NAGIOS_HOME} --enable-event-broker --with-nagios-command-user=${NAGIOS_CMDUSER} --with-command-group=${NAGIOS_CMDGROUP} --with-nagios-user=${NAGIOS_USER} --with-nagios-group=${NAGIOS_GROUP} && make all && make install && make install-config && make install-commandmode && cp sample-config/httpd.conf /etc/apache2/conf.d/nagios.conf")
|
||||
(add "http://www.nagios-plugins.org/download/nagios-plugins-1.5.tar.gz" "/tmp/")
|
||||
(run "cd /tmp && tar -zxvf nagios-plugins-1.5.tar.gz && cd nagios-plugins-1.5 && ./configure --prefix=${NAGIOS_HOME} && make && make install")
|
||||
(run "sed -i.bak 's/.*\\=www\\-data//g' /etc/apache2/envvars")
|
||||
(run "export DOC_ROOT=\"DocumentRoot $(echo $NAGIOS_HOME/share)\"; sed -i \"s,DocumentRoot.*,$DOC_ROOT,\" /etc/apache2/sites-enabled/000-default")
|
||||
(run "ln -s ${NAGIOS_HOME}/bin/nagios /usr/local/bin/nagios && mkdir -p /usr/share/snmp/mibs && chmod 0755 /usr/share/snmp/mibs && touch /usr/share/snmp/mibs/.foo")
|
||||
(run "echo \"use_timezone=$NAGIOS_TIMEZONE\" >> ${NAGIOS_HOME}/etc/nagios.cfg && echo \"SetEnv TZ \\\"${NAGIOS_TIMEZONE}\\\"\" >> /etc/apache2/conf.d/nagios.conf")
|
||||
(run "mkdir -p ${NAGIOS_HOME}/etc/conf.d && mkdir -p ${NAGIOS_HOME}/etc/monitor && ln -s /usr/share/snmp/mibs ${NAGIOS_HOME}/libexec/mibs")
|
||||
(run "echo \"cfg_dir=${NAGIOS_HOME}/etc/conf.d\" >> ${NAGIOS_HOME}/etc/nagios.cfg")
|
||||
(run "echo \"cfg_dir=${NAGIOS_HOME}/etc/monitor\" >> ${NAGIOS_HOME}/etc/nagios.cfg")
|
||||
(run "download-mibs && echo \"mibs +ALL\" > /etc/snmp/snmp.conf")
|
||||
(run "sed -i 's,/bin/mail,/usr/bin/mail,' /opt/nagios/etc/objects/commands.cfg && sed -i 's,/usr/usr,/usr,' /opt/nagios/etc/objects/commands.cfg")
|
||||
(run "cp /etc/services /var/spool/postfix/etc/")
|
||||
(run "mkdir -p /etc/sv/nagios && mkdir -p /etc/sv/apache && rm -rf /etc/sv/getty-5 && mkdir -p /etc/sv/postfix")
|
||||
(add "nagios.init" "/etc/sv/nagios/run")
|
||||
(add "apache.init" "/etc/sv/apache/run")
|
||||
(add "postfix.init" "/etc/sv/postfix/run")
|
||||
(add "postfix.stop" "/etc/sv/postfix/finish")
|
||||
(add "start.sh" "/usr/local/bin/start_nagios")
|
||||
(env "APACHE_LOCK_DIR" "/var/run")
|
||||
(env "APACHE_LOG_DIR" "/var/log/apache2")
|
||||
(expose "80")
|
||||
(volume "/opt/nagios/var" "/opt/nagios/etc" "/opt/nagios/libexec" "/var/log/apache2" "/usr/share/snmp/mibs")
|
||||
(cmd "/usr/local/bin/start_nagios")
|
|
@ -0,0 +1,105 @@
|
|||
# This file describes the standard way to build Docker, using docker
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# # Assemble the full dev environment. This is slow the first time.
|
||||
# docker build -t docker .
|
||||
#
|
||||
# # Mount your source in an interactive container for quick testing:
|
||||
# docker run -v `pwd`:/go/src/github.com/docker/docker --privileged -i -t docker bash
|
||||
#
|
||||
# # Run the test suite:
|
||||
# docker run --privileged docker hack/make.sh test
|
||||
#
|
||||
# # Publish a release:
|
||||
# docker run --privileged \
|
||||
# -e AWS_S3_BUCKET=baz \
|
||||
# -e AWS_ACCESS_KEY=foo \
|
||||
# -e AWS_SECRET_KEY=bar \
|
||||
# -e GPG_PASSPHRASE=gloubiboulga \
|
||||
# docker hack/release.sh
|
||||
#
|
||||
# Note: Apparmor used to mess with privileged mode, but this is no longer
|
||||
# the case. Therefore, you don't have to disable it anymore.
|
||||
#
|
||||
|
||||
docker-version 0.6.1
|
||||
FROM ubuntu:14.04
|
||||
MAINTAINER Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
|
||||
# Packaged dependencies
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq \
|
||||
apt-utils \
|
||||
aufs-tools \
|
||||
automake \
|
||||
btrfs-tools \
|
||||
build-essential \
|
||||
curl \
|
||||
dpkg-sig \
|
||||
git \
|
||||
iptables \
|
||||
libapparmor-dev \
|
||||
libcap-dev \
|
||||
libsqlite3-dev \
|
||||
lxc=1.0* \
|
||||
mercurial \
|
||||
pandoc \
|
||||
parallel \
|
||||
reprepro \
|
||||
ruby1.9.1 \
|
||||
ruby1.9.1-dev \
|
||||
s3cmd=1.1.0* \
|
||||
--no-install-recommends
|
||||
|
||||
# Get lvm2 source for compiling statically
|
||||
RUN git clone --no-checkout https://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2 && cd /usr/local/lvm2 && git checkout -q v2_02_103
|
||||
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
|
||||
# note: we don't use "git clone -b" above because it then spews big nasty warnings about 'detached HEAD' state that we can't silence as easily as we can silence them using "git checkout" directly
|
||||
|
||||
# Compile and install lvm2
|
||||
RUN cd /usr/local/lvm2 && ./configure --enable-static_link && make device-mapper && make install_device-mapper
|
||||
# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
|
||||
|
||||
# Install Go
|
||||
RUN curl -sSL https://golang.org/dl/go1.3.src.tar.gz | tar -v -C /usr/local -xz
|
||||
ENV PATH /usr/local/go/bin:$PATH
|
||||
ENV GOPATH /go:/go/src/github.com/docker/docker/vendor
|
||||
RUN cd /usr/local/go/src && ./make.bash --no-clean 2>&1
|
||||
|
||||
# Compile Go for cross compilation
|
||||
ENV DOCKER_CROSSPLATFORMS \
|
||||
linux/386 linux/arm \
|
||||
darwin/amd64 darwin/386 \
|
||||
freebsd/amd64 freebsd/386 freebsd/arm
|
||||
# (set an explicit GOARM of 5 for maximum compatibility)
|
||||
ENV GOARM 5
|
||||
RUN cd /usr/local/go/src && bash -xc 'for platform in $DOCKER_CROSSPLATFORMS; do GOOS=${platform%/*} GOARCH=${platform##*/} ./make.bash --no-clean 2>&1; done'
|
||||
|
||||
# Grab Go's cover tool for dead-simple code coverage testing
|
||||
RUN go get code.google.com/p/go.tools/cmd/cover
|
||||
|
||||
# TODO replace FPM with some very minimal debhelper stuff
|
||||
RUN gem install --no-rdoc --no-ri fpm --version 1.0.2
|
||||
|
||||
# Get the "busybox" image source so we can build locally instead of pulling
|
||||
RUN git clone -b buildroot-2014.02 https://github.com/jpetazzo/docker-busybox.git /docker-busybox
|
||||
|
||||
# Setup s3cmd config
|
||||
RUN /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY' > /.s3cfg
|
||||
|
||||
# Set user.email so crosbymichael's in-container merge commits go smoothly
|
||||
RUN git config --global user.email 'docker-dummy@example.com'
|
||||
|
||||
# Add an unprivileged user to be used for tests which need it
|
||||
RUN groupadd -r docker
|
||||
RUN useradd --create-home --gid docker unprivilegeduser
|
||||
|
||||
VOLUME /var/lib/docker
|
||||
WORKDIR /go/src/github.com/docker/docker
|
||||
ENV DOCKER_BUILDTAGS apparmor selinux
|
||||
|
||||
# Wrap all commands in the "docker-in-docker" script to allow nested containers
|
||||
ENTRYPOINT ["hack/dind"]
|
||||
|
||||
# Upload docker source
|
||||
COPY . /go/src/github.com/docker/docker
|
|
@ -0,0 +1,25 @@
|
|||
(docker-version)
|
||||
(from "ubuntu:14.04")
|
||||
(maintainer "Tianon Gravi <admwiggin@gmail.com> (@tianon)")
|
||||
(run "apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq apt-utils aufs-tools automake btrfs-tools build-essential curl dpkg-sig git iptables libapparmor-dev libcap-dev libsqlite3-dev lxc=1.0* mercurial pandoc parallel reprepro ruby1.9.1 ruby1.9.1-dev s3cmd=1.1.0* --no-install-recommends")
|
||||
(run "git clone --no-checkout https://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2 && cd /usr/local/lvm2 && git checkout -q v2_02_103")
|
||||
(run "cd /usr/local/lvm2 && ./configure --enable-static_link && make device-mapper && make install_device-mapper")
|
||||
(run "curl -sSL https://golang.org/dl/go1.3.src.tar.gz | tar -v -C /usr/local -xz")
|
||||
(env "PATH" "/usr/local/go/bin:$PATH")
|
||||
(env "GOPATH" "/go:/go/src/github.com/docker/docker/vendor")
|
||||
(run "cd /usr/local/go/src && ./make.bash --no-clean 2>&1")
|
||||
(env "DOCKER_CROSSPLATFORMS" "linux/386 linux/arm darwin/amd64 darwin/386 freebsd/amd64 freebsd/386 freebsd/arm")
|
||||
(env "GOARM" "5")
|
||||
(run "cd /usr/local/go/src && bash -xc 'for platform in $DOCKER_CROSSPLATFORMS; do GOOS=${platform%/*} GOARCH=${platform##*/} ./make.bash --no-clean 2>&1; done'")
|
||||
(run "go get code.google.com/p/go.tools/cmd/cover")
|
||||
(run "gem install --no-rdoc --no-ri fpm --version 1.0.2")
|
||||
(run "git clone -b buildroot-2014.02 https://github.com/jpetazzo/docker-busybox.git /docker-busybox")
|
||||
(run "/bin/echo -e '[default]\\naccess_key=$AWS_ACCESS_KEY\\nsecret_key=$AWS_SECRET_KEY' > /.s3cfg")
|
||||
(run "git config --global user.email 'docker-dummy@example.com'")
|
||||
(run "groupadd -r docker")
|
||||
(run "useradd --create-home --gid docker unprivilegeduser")
|
||||
(volume "/var/lib/docker")
|
||||
(workdir "/go/src/github.com/docker/docker")
|
||||
(env "DOCKER_BUILDTAGS" "apparmor selinux")
|
||||
(entrypoint "hack/dind")
|
||||
(copy "." "/go/src/github.com/docker/docker")
|
|
@ -0,0 +1,14 @@
|
|||
FROM ubuntu:14.04
|
||||
MAINTAINER Erik \\Hollensbe <erik@hollensbe.org>\"
|
||||
|
||||
RUN apt-get \update && \
|
||||
apt-get \"install znc -y
|
||||
ADD \conf\\" /.znc
|
||||
|
||||
RUN foo \
|
||||
|
||||
bar \
|
||||
|
||||
baz
|
||||
|
||||
CMD [ "\/usr\\\"/bin/znc", "-f", "-r" ]
|
|
@ -0,0 +1,6 @@
|
|||
(from "ubuntu:14.04")
|
||||
(maintainer "Erik \\\\Hollensbe <erik@hollensbe.org>\\\"")
|
||||
(run "apt-get \\update && apt-get \\\"install znc -y")
|
||||
(add "\\conf\\\\\"" "/.znc")
|
||||
(run "foo bar baz")
|
||||
(cmd "/usr\\\"/bin/znc" "-f" "-r")
|
|
@ -0,0 +1,15 @@
|
|||
FROM ubuntu:14.04
|
||||
|
||||
RUN apt-get update && apt-get install wget -y
|
||||
RUN wget http://s3.amazonaws.com/influxdb/influxdb_latest_amd64.deb
|
||||
RUN dpkg -i influxdb_latest_amd64.deb
|
||||
RUN rm -r /opt/influxdb/shared
|
||||
|
||||
VOLUME /opt/influxdb/shared
|
||||
|
||||
CMD /usr/bin/influxdb --pidfile /var/run/influxdb.pid -config /opt/influxdb/shared/config.toml
|
||||
|
||||
EXPOSE 8083
|
||||
EXPOSE 8086
|
||||
EXPOSE 8090
|
||||
EXPOSE 8099
|
|
@ -0,0 +1,11 @@
|
|||
(from "ubuntu:14.04")
|
||||
(run "apt-get update && apt-get install wget -y")
|
||||
(run "wget http://s3.amazonaws.com/influxdb/influxdb_latest_amd64.deb")
|
||||
(run "dpkg -i influxdb_latest_amd64.deb")
|
||||
(run "rm -r /opt/influxdb/shared")
|
||||
(volume "/opt/influxdb/shared")
|
||||
(cmd "/usr/bin/influxdb --pidfile /var/run/influxdb.pid -config /opt/influxdb/shared/config.toml")
|
||||
(expose "8083")
|
||||
(expose "8086")
|
||||
(expose "8090")
|
||||
(expose "8099")
|
|
@ -0,0 +1 @@
|
|||
CMD "[\"echo\", \"Phew, I just managed to escaped those double quotes\"]"
|
|
@ -0,0 +1 @@
|
|||
(cmd "\"[\\\"echo\\\", \\\"Phew, I just managed to escaped those double quotes\\\"]\"")
|
|
@ -0,0 +1 @@
|
|||
CMD '["echo", "Well, JSON in a string is JSON too?"]'
|
|
@ -0,0 +1 @@
|
|||
(cmd "'[\"echo\", \"Well, JSON in a string is JSON too?\"]'")
|
|
@ -0,0 +1 @@
|
|||
CMD ['echo','single quotes are invalid JSON']
|
|
@ -0,0 +1 @@
|
|||
(cmd "['echo','single quotes are invalid JSON']")
|
|
@ -0,0 +1 @@
|
|||
CMD ["echo", "Please, close the brackets when you're done"
|
|
@ -0,0 +1 @@
|
|||
(cmd "[\"echo\", \"Please, close the brackets when you're done\"")
|
|
@ -0,0 +1 @@
|
|||
CMD ["echo", "look ma, no quote!]
|
|
@ -0,0 +1 @@
|
|||
(cmd "[\"echo\", \"look ma, no quote!]")
|
|
@ -0,0 +1,7 @@
|
|||
FROM ubuntu:14.04
|
||||
MAINTAINER James Turnbull "james@example.com"
|
||||
ENV REFRESHED_AT 2014-06-01
|
||||
RUN apt-get update
|
||||
RUN apt-get -y install redis-server redis-tools
|
||||
EXPOSE 6379
|
||||
ENTRYPOINT [ "/usr/bin/redis-server" ]
|
|
@ -0,0 +1,7 @@
|
|||
(from "ubuntu:14.04")
|
||||
(maintainer "James Turnbull \"james@example.com\"")
|
||||
(env "REFRESHED_AT" "2014-06-01")
|
||||
(run "apt-get update")
|
||||
(run "apt-get -y install redis-server redis-tools")
|
||||
(expose "6379")
|
||||
(entrypoint "/usr/bin/redis-server")
|
|
@ -0,0 +1,48 @@
|
|||
FROM busybox:buildroot-2014.02
|
||||
|
||||
MAINTAINER docker <docker@docker.io>
|
||||
|
||||
ONBUILD RUN ["echo", "test"]
|
||||
ONBUILD RUN echo test
|
||||
ONBUILD COPY . /
|
||||
|
||||
|
||||
# RUN Commands \
|
||||
# linebreak in comment \
|
||||
RUN ["ls", "-la"]
|
||||
RUN ["echo", "'1234'"]
|
||||
RUN echo "1234"
|
||||
RUN echo 1234
|
||||
RUN echo '1234' && \
|
||||
echo "456" && \
|
||||
echo 789
|
||||
RUN sh -c 'echo root:testpass \
|
||||
> /tmp/passwd'
|
||||
RUN mkdir -p /test /test2 /test3/test
|
||||
|
||||
# ENV \
|
||||
ENV SCUBA 1 DUBA 3
|
||||
ENV SCUBA "1 DUBA 3"
|
||||
|
||||
# CMD \
|
||||
CMD ["echo", "test"]
|
||||
CMD echo test
|
||||
CMD echo "test"
|
||||
CMD echo 'test'
|
||||
CMD echo 'test' | wc -
|
||||
|
||||
#EXPOSE\
|
||||
EXPOSE 3000
|
||||
EXPOSE 9000 5000 6000
|
||||
|
||||
USER docker
|
||||
USER docker:root
|
||||
|
||||
VOLUME ["/test"]
|
||||
VOLUME ["/test", "/test2"]
|
||||
VOLUME /test3
|
||||
|
||||
WORKDIR /test
|
||||
|
||||
ADD . /
|
||||
COPY . copy
|
|
@ -0,0 +1,29 @@
|
|||
(from "busybox:buildroot-2014.02")
|
||||
(maintainer "docker <docker@docker.io>")
|
||||
(onbuild (run "echo" "test"))
|
||||
(onbuild (run "echo test"))
|
||||
(onbuild (copy "." "/"))
|
||||
(run "ls" "-la")
|
||||
(run "echo" "'1234'")
|
||||
(run "echo \"1234\"")
|
||||
(run "echo 1234")
|
||||
(run "echo '1234' && echo \"456\" && echo 789")
|
||||
(run "sh -c 'echo root:testpass > /tmp/passwd'")
|
||||
(run "mkdir -p /test /test2 /test3/test")
|
||||
(env "SCUBA" "1 DUBA 3")
|
||||
(env "SCUBA" "\"1 DUBA 3\"")
|
||||
(cmd "echo" "test")
|
||||
(cmd "echo test")
|
||||
(cmd "echo \"test\"")
|
||||
(cmd "echo 'test'")
|
||||
(cmd "echo 'test' | wc -")
|
||||
(expose "3000")
|
||||
(expose "9000" "5000" "6000")
|
||||
(user "docker")
|
||||
(user "docker:root")
|
||||
(volume "/test")
|
||||
(volume "/test" "/test2")
|
||||
(volume "/test3")
|
||||
(workdir "/test")
|
||||
(add "." "/")
|
||||
(copy "." "copy")
|
|
@ -0,0 +1,16 @@
|
|||
FROM ubuntu:14.04
|
||||
|
||||
RUN apt-get update -qy && apt-get install mutt offlineimap vim-nox abook elinks curl tmux cron zsh -y
|
||||
ADD .muttrc /
|
||||
ADD .offlineimaprc /
|
||||
ADD .tmux.conf /
|
||||
ADD mutt /.mutt
|
||||
ADD vim /.vim
|
||||
ADD vimrc /.vimrc
|
||||
ADD crontab /etc/crontab
|
||||
RUN chmod 644 /etc/crontab
|
||||
RUN mkdir /Mail
|
||||
RUN mkdir /.offlineimap
|
||||
RUN echo "export TERM=screen-256color" >/.zshenv
|
||||
|
||||
CMD setsid cron; tmux -2
|
|
@ -0,0 +1,14 @@
|
|||
(from "ubuntu:14.04")
|
||||
(run "apt-get update -qy && apt-get install mutt offlineimap vim-nox abook elinks curl tmux cron zsh -y")
|
||||
(add ".muttrc" "/")
|
||||
(add ".offlineimaprc" "/")
|
||||
(add ".tmux.conf" "/")
|
||||
(add "mutt" "/.mutt")
|
||||
(add "vim" "/.vim")
|
||||
(add "vimrc" "/.vimrc")
|
||||
(add "crontab" "/etc/crontab")
|
||||
(run "chmod 644 /etc/crontab")
|
||||
(run "mkdir /Mail")
|
||||
(run "mkdir /.offlineimap")
|
||||
(run "echo \"export TERM=screen-256color\" >/.zshenv")
|
||||
(cmd "setsid cron; tmux -2")
|
|
@ -0,0 +1,3 @@
|
|||
FROM foo
|
||||
|
||||
VOLUME /opt/nagios/var /opt/nagios/etc /opt/nagios/libexec /var/log/apache2 /usr/share/snmp/mibs
|
|
@ -0,0 +1,2 @@
|
|||
(from "foo")
|
||||
(volume "/opt/nagios/var" "/opt/nagios/etc" "/opt/nagios/libexec" "/var/log/apache2" "/usr/share/snmp/mibs")
|
|
@ -0,0 +1,7 @@
|
|||
FROM ubuntu:14.04
|
||||
|
||||
RUN apt-get update && apt-get install libcap2-bin mumble-server -y
|
||||
|
||||
ADD ./mumble-server.ini /etc/mumble-server.ini
|
||||
|
||||
CMD /usr/sbin/murmurd
|
|
@ -0,0 +1,4 @@
|
|||
(from "ubuntu:14.04")
|
||||
(run "apt-get update && apt-get install libcap2-bin mumble-server -y")
|
||||
(add "./mumble-server.ini" "/etc/mumble-server.ini")
|
||||
(cmd "/usr/sbin/murmurd")
|
|
@ -0,0 +1,14 @@
|
|||
FROM ubuntu:14.04
|
||||
MAINTAINER Erik Hollensbe <erik@hollensbe.org>
|
||||
|
||||
RUN apt-get update && apt-get install nginx-full -y
|
||||
RUN rm -rf /etc/nginx
|
||||
ADD etc /etc/nginx
|
||||
RUN chown -R root:root /etc/nginx
|
||||
RUN /usr/sbin/nginx -qt
|
||||
RUN mkdir /www
|
||||
|
||||
CMD ["/usr/sbin/nginx"]
|
||||
|
||||
VOLUME /www
|
||||
EXPOSE 80
|
|
@ -0,0 +1,11 @@
|
|||
(from "ubuntu:14.04")
|
||||
(maintainer "Erik Hollensbe <erik@hollensbe.org>")
|
||||
(run "apt-get update && apt-get install nginx-full -y")
|
||||
(run "rm -rf /etc/nginx")
|
||||
(add "etc" "/etc/nginx")
|
||||
(run "chown -R root:root /etc/nginx")
|
||||
(run "/usr/sbin/nginx -qt")
|
||||
(run "mkdir /www")
|
||||
(cmd "/usr/sbin/nginx")
|
||||
(volume "/www")
|
||||
(expose "80")
|
|
@ -0,0 +1,23 @@
|
|||
FROM ubuntu:12.04
|
||||
|
||||
EXPOSE 27015
|
||||
EXPOSE 27005
|
||||
EXPOSE 26901
|
||||
EXPOSE 27020
|
||||
|
||||
RUN apt-get update && apt-get install libc6-dev-i386 curl unzip -y
|
||||
RUN mkdir -p /steam
|
||||
RUN curl http://media.steampowered.com/client/steamcmd_linux.tar.gz | tar vxz -C /steam
|
||||
ADD ./script /steam/script
|
||||
RUN /steam/steamcmd.sh +runscript /steam/script
|
||||
RUN curl http://mirror.pointysoftware.net/alliedmodders/mmsource-1.10.0-linux.tar.gz | tar vxz -C /steam/tf2/tf
|
||||
RUN curl http://mirror.pointysoftware.net/alliedmodders/sourcemod-1.5.3-linux.tar.gz | tar vxz -C /steam/tf2/tf
|
||||
ADD ./server.cfg /steam/tf2/tf/cfg/server.cfg
|
||||
ADD ./ctf_2fort.cfg /steam/tf2/tf/cfg/ctf_2fort.cfg
|
||||
ADD ./sourcemod.cfg /steam/tf2/tf/cfg/sourcemod/sourcemod.cfg
|
||||
RUN rm -r /steam/tf2/tf/addons/sourcemod/configs
|
||||
ADD ./configs /steam/tf2/tf/addons/sourcemod/configs
|
||||
RUN mkdir -p /steam/tf2/tf/addons/sourcemod/translations/en
|
||||
RUN cp /steam/tf2/tf/addons/sourcemod/translations/*.txt /steam/tf2/tf/addons/sourcemod/translations/en
|
||||
|
||||
CMD cd /steam/tf2 && ./srcds_run -port 27015 +ip 0.0.0.0 +map ctf_2fort -autoupdate -steam_dir /steam -steamcmd_script /steam/script +tf_bot_quota 12 +tf_bot_quota_mode fill
|
|
@ -0,0 +1,20 @@
|
|||
(from "ubuntu:12.04")
|
||||
(expose "27015")
|
||||
(expose "27005")
|
||||
(expose "26901")
|
||||
(expose "27020")
|
||||
(run "apt-get update && apt-get install libc6-dev-i386 curl unzip -y")
|
||||
(run "mkdir -p /steam")
|
||||
(run "curl http://media.steampowered.com/client/steamcmd_linux.tar.gz | tar vxz -C /steam")
|
||||
(add "./script" "/steam/script")
|
||||
(run "/steam/steamcmd.sh +runscript /steam/script")
|
||||
(run "curl http://mirror.pointysoftware.net/alliedmodders/mmsource-1.10.0-linux.tar.gz | tar vxz -C /steam/tf2/tf")
|
||||
(run "curl http://mirror.pointysoftware.net/alliedmodders/sourcemod-1.5.3-linux.tar.gz | tar vxz -C /steam/tf2/tf")
|
||||
(add "./server.cfg" "/steam/tf2/tf/cfg/server.cfg")
|
||||
(add "./ctf_2fort.cfg" "/steam/tf2/tf/cfg/ctf_2fort.cfg")
|
||||
(add "./sourcemod.cfg" "/steam/tf2/tf/cfg/sourcemod/sourcemod.cfg")
|
||||
(run "rm -r /steam/tf2/tf/addons/sourcemod/configs")
|
||||
(add "./configs" "/steam/tf2/tf/addons/sourcemod/configs")
|
||||
(run "mkdir -p /steam/tf2/tf/addons/sourcemod/translations/en")
|
||||
(run "cp /steam/tf2/tf/addons/sourcemod/translations/*.txt /steam/tf2/tf/addons/sourcemod/translations/en")
|
||||
(cmd "cd /steam/tf2 && ./srcds_run -port 27015 +ip 0.0.0.0 +map ctf_2fort -autoupdate -steam_dir /steam -steamcmd_script /steam/script +tf_bot_quota 12 +tf_bot_quota_mode fill")
|
|
@ -0,0 +1,9 @@
|
|||
FROM ubuntu:14.04
|
||||
|
||||
RUN apt-get update -qy && apt-get install tmux zsh weechat-curses -y
|
||||
|
||||
ADD .weechat /.weechat
|
||||
ADD .tmux.conf /
|
||||
RUN echo "export TERM=screen-256color" >/.zshenv
|
||||
|
||||
CMD zsh -c weechat
|
|
@ -0,0 +1,6 @@
|
|||
(from "ubuntu:14.04")
|
||||
(run "apt-get update -qy && apt-get install tmux zsh weechat-curses -y")
|
||||
(add ".weechat" "/.weechat")
|
||||
(add ".tmux.conf" "/")
|
||||
(run "echo \"export TERM=screen-256color\" >/.zshenv")
|
||||
(cmd "zsh -c weechat")
|
|
@ -0,0 +1,7 @@
|
|||
FROM ubuntu:14.04
|
||||
MAINTAINER Erik Hollensbe <erik@hollensbe.org>
|
||||
|
||||
RUN apt-get update && apt-get install znc -y
|
||||
ADD conf /.znc
|
||||
|
||||
CMD [ "/usr/bin/znc", "-f", "-r" ]
|
|
@ -0,0 +1,5 @@
|
|||
(from "ubuntu:14.04")
|
||||
(maintainer "Erik Hollensbe <erik@hollensbe.org>")
|
||||
(run "apt-get update && apt-get install znc -y")
|
||||
(add "conf" "/.znc")
|
||||
(cmd "/usr/bin/znc" "-f" "-r")
|
|
@ -0,0 +1,94 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// QuoteString walks characters (after trimming), escapes any quotes and
|
||||
// escapes, then wraps the whole thing in quotes. Very useful for generating
|
||||
// argument output in nodes.
|
||||
func QuoteString(str string) string {
|
||||
result := ""
|
||||
chars := strings.Split(strings.TrimSpace(str), "")
|
||||
|
||||
for _, char := range chars {
|
||||
switch char {
|
||||
case `"`:
|
||||
result += `\"`
|
||||
case `\`:
|
||||
result += `\\`
|
||||
default:
|
||||
result += char
|
||||
}
|
||||
}
|
||||
|
||||
return `"` + result + `"`
|
||||
}
|
||||
|
||||
// dumps the AST defined by `node` as a list of sexps. Returns a string
|
||||
// suitable for printing.
|
||||
func (node *Node) Dump() string {
|
||||
str := ""
|
||||
str += node.Value
|
||||
|
||||
for _, n := range node.Children {
|
||||
str += "(" + n.Dump() + ")\n"
|
||||
}
|
||||
|
||||
if node.Next != nil {
|
||||
for n := node.Next; n != nil; n = n.Next {
|
||||
if len(n.Children) > 0 {
|
||||
str += " " + n.Dump()
|
||||
} else {
|
||||
str += " " + QuoteString(n.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return strings.TrimSpace(str)
|
||||
}
|
||||
|
||||
// performs the dispatch based on the two primal strings, cmd and args. Please
|
||||
// look at the dispatch table in parser.go to see how these dispatchers work.
|
||||
func fullDispatch(cmd, args string) (*Node, map[string]bool, error) {
|
||||
fn := dispatch[cmd]
|
||||
|
||||
// Ignore invalid Dockerfile instructions
|
||||
if fn == nil {
|
||||
fn = parseIgnore
|
||||
}
|
||||
|
||||
sexp, attrs, err := fn(args)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return sexp, attrs, nil
|
||||
}
|
||||
|
||||
// splitCommand takes a single line of text and parses out the cmd and args,
|
||||
// which are used for dispatching to more exact parsing functions.
|
||||
func splitCommand(line string) (string, string, error) {
|
||||
cmdline := TOKEN_WHITESPACE.Split(line, 2)
|
||||
|
||||
if len(cmdline) != 2 {
|
||||
return "", "", fmt.Errorf("We do not understand this file. Please ensure it is a valid Dockerfile. Parser error at %q", line)
|
||||
}
|
||||
|
||||
cmd := strings.ToLower(cmdline[0])
|
||||
// the cmd should never have whitespace, but it's possible for the args to
|
||||
// have trailing whitespace.
|
||||
return cmd, strings.TrimSpace(cmdline[1]), nil
|
||||
}
|
||||
|
||||
// covers comments and empty lines. Lines should be trimmed before passing to
|
||||
// this function.
|
||||
func stripComments(line string) string {
|
||||
// string is already trimmed at this point
|
||||
if TOKEN_COMMENT.MatchString(line) {
|
||||
return TOKEN_COMMENT.ReplaceAllString(line, "")
|
||||
}
|
||||
|
||||
return line
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package builder
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// `\\\\+|[^\\]|\b|\A` - match any number of "\\" (ie, properly-escaped backslashes), or a single non-backslash character, or a word boundary, or beginning-of-line
|
||||
// `\$` - match literal $
|
||||
// `[[:alnum:]_]+` - match things like `$SOME_VAR`
|
||||
// `{[[:alnum:]_]+}` - match things like `${SOME_VAR}`
|
||||
tokenEnvInterpolation = regexp.MustCompile(`(\\\\+|[^\\]|\b|\A)\$([[:alnum:]_]+|{[[:alnum:]_]+})`)
|
||||
// this intentionally punts on more exotic interpolations like ${SOME_VAR%suffix} and lets the shell handle those directly
|
||||
)
|
||||
|
||||
// handle environment replacement. Used in dispatcher.
|
||||
func (b *Builder) replaceEnv(str string) string {
|
||||
for _, match := range tokenEnvInterpolation.FindAllString(str, -1) {
|
||||
match = match[strings.Index(match, "$"):]
|
||||
matchKey := strings.Trim(match, "${}")
|
||||
|
||||
for _, keyval := range b.Config.Env {
|
||||
tmp := strings.SplitN(keyval, "=", 2)
|
||||
if tmp[0] == matchKey {
|
||||
str = strings.Replace(str, match, tmp[1], -1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
func handleJsonArgs(args []string, attributes map[string]bool) []string {
|
||||
if len(args) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
if attributes != nil && attributes["json"] {
|
||||
return args
|
||||
}
|
||||
|
||||
// literal string command, not an exec array
|
||||
return []string{strings.Join(args, " ")}
|
||||
}
|
|
@ -61,7 +61,7 @@ func dockerVersion(job *engine.Job) engine.Status {
|
|||
v := &engine.Env{}
|
||||
v.SetJson("Version", dockerversion.VERSION)
|
||||
v.SetJson("ApiVersion", api.APIVERSION)
|
||||
v.Set("GitCommit", dockerversion.GITCOMMIT)
|
||||
v.SetJson("GitCommit", dockerversion.GITCOMMIT)
|
||||
v.Set("GoVersion", runtime.Version())
|
||||
v.Set("Os", runtime.GOOS)
|
||||
v.Set("Arch", runtime.GOARCH)
|
||||
|
|
|
@ -146,13 +146,14 @@ echo 'Optional Features:'
|
|||
flags=(
|
||||
MEMCG_SWAP
|
||||
RESOURCE_COUNTERS
|
||||
CGROUP_PERF
|
||||
)
|
||||
check_flags "${flags[@]}"
|
||||
|
||||
echo '- Storage Drivers:'
|
||||
{
|
||||
echo '- "'$(wrap_color 'aufs' blue)'":'
|
||||
check_flags AUFS_FS | sed 's/^/ /'
|
||||
check_flags AUFS_FS EXT4_FS_POSIX_ACL EXT4_FS_SECURITY | sed 's/^/ /'
|
||||
if ! is_set AUFS_FS && grep -q aufs /proc/filesystems; then
|
||||
echo " $(wrap_color '(note that some kernels include AUFS patches but not the AUFS_FS flag)' bold black)"
|
||||
fi
|
||||
|
@ -161,7 +162,7 @@ echo '- Storage Drivers:'
|
|||
check_flags BTRFS_FS | sed 's/^/ /'
|
||||
|
||||
echo '- "'$(wrap_color 'devicemapper' blue)'":'
|
||||
check_flags BLK_DEV_DM DM_THIN_PROVISIONING EXT4_FS | sed 's/^/ /'
|
||||
check_flags BLK_DEV_DM DM_THIN_PROVISIONING EXT4_FS EXT4_FS_POSIX_ACL EXT4_FS_SECURITY | sed 's/^/ /'
|
||||
} | sed 's/^/ /'
|
||||
echo
|
||||
|
||||
|
|
|
@ -25,63 +25,59 @@ __docker_q() {
|
|||
docker 2>/dev/null "$@"
|
||||
}
|
||||
|
||||
__docker_containers_all()
|
||||
{
|
||||
local containers="$( __docker_q ps -a -q )"
|
||||
local names="$( __docker_q inspect --format '{{.Name}}' $containers | sed 's,^/,,' )"
|
||||
COMPREPLY=( $( compgen -W "$names $containers" -- "$cur" ) )
|
||||
__docker_containers_all() {
|
||||
local IFS=$'\n'
|
||||
local containers=( $(__docker_q ps -aq --no-trunc) )
|
||||
if [ "$1" ]; then
|
||||
containers=( $(__docker_q inspect --format "{{if $1}}{{.Id}}{{end}}" "${containers[@]}") )
|
||||
fi
|
||||
local names=( $(__docker_q inspect --format '{{.Name}}' "${containers[@]}") )
|
||||
names=( "${names[@]#/}" ) # trim off the leading "/" from the container names
|
||||
unset IFS
|
||||
COMPREPLY=( $(compgen -W "${names[*]} ${containers[*]}" -- "$cur") )
|
||||
}
|
||||
|
||||
__docker_containers_running()
|
||||
{
|
||||
local containers="$( __docker_q ps -q )"
|
||||
local names="$( __docker_q inspect --format '{{.Name}}' $containers | sed 's,^/,,' )"
|
||||
COMPREPLY=( $( compgen -W "$names $containers" -- "$cur" ) )
|
||||
__docker_containers_running() {
|
||||
__docker_containers_all '.State.Running'
|
||||
}
|
||||
|
||||
__docker_containers_stopped()
|
||||
{
|
||||
local containers="$( { __docker_q ps -a -q; __docker_q ps -q; } | sort | uniq -u )"
|
||||
local names="$( __docker_q inspect --format '{{.Name}}' $containers | sed 's,^/,,' )"
|
||||
COMPREPLY=( $( compgen -W "$names $containers" -- "$cur" ) )
|
||||
__docker_containers_stopped() {
|
||||
__docker_containers_all 'not .State.Running'
|
||||
}
|
||||
|
||||
__docker_image_repos()
|
||||
{
|
||||
local repos="$( __docker_q images | awk 'NR>1{print $1}' | grep -v '^<none>$' )"
|
||||
COMPREPLY=( $( compgen -W "$repos" -- "$cur" ) )
|
||||
__docker_containers_pauseable() {
|
||||
__docker_containers_all 'and .State.Running (not .State.Paused)'
|
||||
}
|
||||
|
||||
__docker_image_repos_and_tags()
|
||||
{
|
||||
local repos="$( __docker_q images | awk 'NR>1{print $1}' | grep -v '^<none>$' )"
|
||||
local images="$( __docker_q images | awk 'NR>1{print $1":"$2}' | grep -v '^<none>:' )"
|
||||
COMPREPLY=( $( compgen -W "$repos $images" -- "$cur" ) )
|
||||
__docker_containers_unpauseable() {
|
||||
__docker_containers_all '.State.Paused'
|
||||
}
|
||||
|
||||
__docker_image_repos() {
|
||||
local repos="$(__docker_q images | awk 'NR>1 && $1 != "<none>" { print $1 }')"
|
||||
COMPREPLY=( $(compgen -W "$repos" -- "$cur") )
|
||||
}
|
||||
|
||||
__docker_image_repos_and_tags() {
|
||||
local reposAndTags="$(__docker_q images | awk 'NR>1 && $1 != "<none>" { print $1; print $1":"$2 }')"
|
||||
COMPREPLY=( $(compgen -W "$reposAndTags" -- "$cur") )
|
||||
__ltrim_colon_completions "$cur"
|
||||
}
|
||||
|
||||
__docker_image_repos_and_tags_and_ids()
|
||||
{
|
||||
local repos="$( __docker_q images | awk 'NR>1{print $1}' | grep -v '^<none>$' )"
|
||||
local images="$( __docker_q images | awk 'NR>1{print $1":"$2}' | grep -v '^<none>:' )"
|
||||
local ids="$( __docker_q images -a -q )"
|
||||
COMPREPLY=( $( compgen -W "$repos $images $ids" -- "$cur" ) )
|
||||
__docker_image_repos_and_tags_and_ids() {
|
||||
local images="$(__docker_q images -a --no-trunc | awk 'NR>1 { print $3; if ($1 != "<none>") { print $1; print $1":"$2 } }')"
|
||||
COMPREPLY=( $(compgen -W "$images" -- "$cur") )
|
||||
__ltrim_colon_completions "$cur"
|
||||
}
|
||||
|
||||
__docker_containers_and_images()
|
||||
{
|
||||
local containers="$( __docker_q ps -a -q )"
|
||||
local names="$( __docker_q inspect --format '{{.Name}}' $containers | sed 's,^/,,' )"
|
||||
local repos="$( __docker_q images | awk 'NR>1{print $1}' | grep -v '^<none>$' )"
|
||||
local images="$( __docker_q images | awk 'NR>1{print $1":"$2}' | grep -v '^<none>:' )"
|
||||
local ids="$( __docker_q images -a -q )"
|
||||
COMPREPLY=( $( compgen -W "$containers $names $repos $images $ids" -- "$cur" ) )
|
||||
__ltrim_colon_completions "$cur"
|
||||
__docker_containers_and_images() {
|
||||
__docker_containers_all
|
||||
local containers=( "${COMPREPLY[@]}" )
|
||||
__docker_image_repos_and_tags_and_ids
|
||||
COMPREPLY+=( "${containers[@]}" )
|
||||
}
|
||||
|
||||
__docker_pos_first_nonflag()
|
||||
{
|
||||
__docker_pos_first_nonflag() {
|
||||
local argument_flags=$1
|
||||
|
||||
local counter=$cpos
|
||||
|
@ -103,8 +99,7 @@ __docker_pos_first_nonflag()
|
|||
echo $counter
|
||||
}
|
||||
|
||||
_docker_docker()
|
||||
{
|
||||
_docker_docker() {
|
||||
case "$prev" in
|
||||
-H)
|
||||
return
|
||||
|
@ -118,13 +113,12 @@ _docker_docker()
|
|||
COMPREPLY=( $( compgen -W "-H" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
COMPREPLY=( $( compgen -W "$commands help" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "${commands[*]} help" -- "$cur" ) )
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_attach()
|
||||
{
|
||||
_docker_attach() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--no-stdin --sig-proxy" -- "$cur" ) )
|
||||
|
@ -138,8 +132,7 @@ _docker_attach()
|
|||
esac
|
||||
}
|
||||
|
||||
_docker_build()
|
||||
{
|
||||
_docker_build() {
|
||||
case "$prev" in
|
||||
-t|--tag)
|
||||
__docker_image_repos_and_tags
|
||||
|
@ -151,7 +144,7 @@ _docker_build()
|
|||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-t --tag -q --quiet --no-cache --rm" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "-t --tag -q --quiet --no-cache --rm --force-rm" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter="$(__docker_pos_first_nonflag '-t|--tag')"
|
||||
|
@ -162,8 +155,7 @@ _docker_build()
|
|||
esac
|
||||
}
|
||||
|
||||
_docker_commit()
|
||||
{
|
||||
_docker_commit() {
|
||||
case "$prev" in
|
||||
-m|--message|-a|--author|--run)
|
||||
return
|
||||
|
@ -193,8 +185,7 @@ _docker_commit()
|
|||
esac
|
||||
}
|
||||
|
||||
_docker_cp()
|
||||
{
|
||||
_docker_cp() {
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
case "$cur" in
|
||||
|
@ -217,273 +208,7 @@ _docker_cp()
|
|||
fi
|
||||
}
|
||||
|
||||
_docker_diff()
|
||||
{
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_events()
|
||||
{
|
||||
case "$prev" in
|
||||
--since)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--since" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_export()
|
||||
{
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_help()
|
||||
{
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
COMPREPLY=( $( compgen -W "$commands" -- "$cur" ) )
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_history()
|
||||
{
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-q --quiet --no-trunc" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags_and_ids
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_images()
|
||||
{
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-q --quiet -a --all --no-trunc -v --viz -t --tree" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_import()
|
||||
{
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
return
|
||||
fi
|
||||
(( counter++ ))
|
||||
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags
|
||||
return
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_info()
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
_docker_inspect()
|
||||
{
|
||||
case "$prev" in
|
||||
-f|--format)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-f --format" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_containers_and_images
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_kill()
|
||||
{
|
||||
__docker_containers_running
|
||||
}
|
||||
|
||||
_docker_load()
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
_docker_login()
|
||||
{
|
||||
case "$prev" in
|
||||
-u|--username|-p|--password|-e|--email)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-u --username -p --password -e --email" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_logs()
|
||||
{
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-f --follow" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_port()
|
||||
{
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_ps()
|
||||
{
|
||||
case "$prev" in
|
||||
--since|--before)
|
||||
__docker_containers_all
|
||||
;;
|
||||
-n)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-q --quiet -s --size -a --all --no-trunc -l --latest --since --before -n" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_pull()
|
||||
{
|
||||
case "$prev" in
|
||||
-t|--tag)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-t --tag" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag '-t|--tag')
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_push()
|
||||
{
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_restart()
|
||||
{
|
||||
case "$prev" in
|
||||
-t|--time)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-t --time" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_containers_all
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_rm()
|
||||
{
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-f --force -l --link -v --volumes" -- "$cur" ) )
|
||||
return
|
||||
;;
|
||||
*)
|
||||
local force=
|
||||
for arg in "${COMP_WORDS[@]}"; do
|
||||
case "$arg" in
|
||||
-f|--force)
|
||||
__docker_containers_all
|
||||
return
|
||||
;;
|
||||
esac
|
||||
done
|
||||
__docker_containers_stopped
|
||||
return
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_rmi()
|
||||
{
|
||||
__docker_image_repos_and_tags_and_ids
|
||||
}
|
||||
|
||||
_docker_run()
|
||||
{
|
||||
_docker_create() {
|
||||
case "$prev" in
|
||||
-a|--attach)
|
||||
COMPREPLY=( $( compgen -W 'stdin stdout stderr' -- "$cur" ) )
|
||||
|
@ -539,7 +264,7 @@ _docker_run()
|
|||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--rm -d --detach -n --networking --privileged -P --publish-all -i --interactive -t --tty --cidfile --entrypoint -h --hostname -m --memory -u --user -w --workdir -c --cpu-shares --sig-proxy --name -a --attach -v --volume --link -e --env -p --publish --expose --dns --volumes-from --lxc-conf" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "-n --networking --privileged -P --publish-all -i --interactive -t --tty --cidfile --entrypoint -h --hostname -m --memory -u --user -w --workdir -c --cpu-shares --name -a --attach -v --volume --link -e --env -p --publish --expose --dns --volumes-from --lxc-conf" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag '--cidfile|--volumes-from|-v|--volume|-e|--env|--entrypoint|-h|--hostname|-m|--memory|-u|--user|-w|--workdir|-c|--cpu-shares|-n|--name|-a|--attach|--link|-p|--publish|--expose|--dns|--lxc-conf')
|
||||
|
@ -551,16 +276,346 @@ _docker_run()
|
|||
esac
|
||||
}
|
||||
|
||||
_docker_save()
|
||||
{
|
||||
_docker_diff() {
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_events() {
|
||||
case "$prev" in
|
||||
--since)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--since" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_exec() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-d --detach -i --interactive -t --tty" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_containers_running
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_export() {
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_help() {
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
COMPREPLY=( $( compgen -W "${commands[*]}" -- "$cur" ) )
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_history() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-q --quiet --no-trunc" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags_and_ids
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_images() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-q --quiet -a --all --no-trunc -v --viz -t --tree" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_import() {
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
return
|
||||
fi
|
||||
(( counter++ ))
|
||||
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags
|
||||
return
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_info() {
|
||||
return
|
||||
}
|
||||
|
||||
_docker_inspect() {
|
||||
case "$prev" in
|
||||
-f|--format)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-f --format" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_containers_and_images
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_kill() {
|
||||
__docker_containers_running
|
||||
}
|
||||
|
||||
_docker_load() {
|
||||
return
|
||||
}
|
||||
|
||||
_docker_login() {
|
||||
case "$prev" in
|
||||
-u|--username|-p|--password|-e|--email)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-u --username -p --password -e --email" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_logs() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-f --follow" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_pause() {
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_pauseable
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_port() {
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_all
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_ps() {
|
||||
case "$prev" in
|
||||
--since|--before)
|
||||
__docker_containers_all
|
||||
;;
|
||||
-n)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-q --quiet -s --size -a --all --no-trunc -l --latest --since --before -n" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_pull() {
|
||||
case "$prev" in
|
||||
-t|--tag)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-t --tag" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag '-t|--tag')
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_push() {
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_restart() {
|
||||
case "$prev" in
|
||||
-t|--time)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-t --time" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_containers_all
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_rm() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-f --force -l --link -v --volumes" -- "$cur" ) )
|
||||
return
|
||||
;;
|
||||
*)
|
||||
local force=
|
||||
for arg in "${COMP_WORDS[@]}"; do
|
||||
case "$arg" in
|
||||
-f|--force)
|
||||
__docker_containers_all
|
||||
return
|
||||
;;
|
||||
esac
|
||||
done
|
||||
__docker_containers_stopped
|
||||
return
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_rmi() {
|
||||
__docker_image_repos_and_tags_and_ids
|
||||
}
|
||||
|
||||
_docker_run() {
|
||||
case "$prev" in
|
||||
-a|--attach)
|
||||
COMPREPLY=( $( compgen -W 'stdin stdout stderr' -- "$cur" ) )
|
||||
return
|
||||
;;
|
||||
--cidfile|--env-file)
|
||||
_filedir
|
||||
return
|
||||
;;
|
||||
--volumes-from)
|
||||
__docker_containers_all
|
||||
return
|
||||
;;
|
||||
-v|--volume)
|
||||
case "$cur" in
|
||||
*:*)
|
||||
# TODO somehow do _filedir for stuff inside the image, if it's already specified (which is also somewhat difficult to determine)
|
||||
;;
|
||||
'')
|
||||
COMPREPLY=( $( compgen -W '/' -- "$cur" ) )
|
||||
compopt -o nospace
|
||||
;;
|
||||
/*)
|
||||
_filedir
|
||||
compopt -o nospace
|
||||
;;
|
||||
esac
|
||||
return
|
||||
;;
|
||||
-e|--env)
|
||||
COMPREPLY=( $( compgen -e -- "$cur" ) )
|
||||
compopt -o nospace
|
||||
return
|
||||
;;
|
||||
--link)
|
||||
case "$cur" in
|
||||
*:*)
|
||||
;;
|
||||
*)
|
||||
__docker_containers_running
|
||||
COMPREPLY=( $( compgen -W "${COMPREPLY[*]}" -S ':' ) )
|
||||
compopt -o nospace
|
||||
;;
|
||||
esac
|
||||
return
|
||||
;;
|
||||
--entrypoint|-h|--hostname|-m|--memory|-u|--user|-w|--workdir|--cpuset|-c|--cpu-shares|-n|--name|-p|--publish|--expose|--dns|--lxc-conf)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--rm -d --detach -n --networking --privileged -P --publish-all -i --interactive -t --tty --cidfile --entrypoint -h --hostname -m --memory -u --user -w --workdir --cpuset -c --cpu-shares --sig-proxy --name -a --attach -v --volume --link -e --env -p --publish --expose --dns --volumes-from --lxc-conf --security-opt" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
|
||||
local counter=$(__docker_pos_first_nonflag '--cidfile|--volumes-from|-v|--volume|-e|--env|--entrypoint|-h|--hostname|-m|--memory|-u|--user|-w|--workdir|--cpuset|-c|--cpu-shares|-n|--name|-a|--attach|--link|-p|--publish|--expose|--dns|--lxc-conf|--security-opt')
|
||||
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags_and_ids
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_save() {
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_image_repos_and_tags_and_ids
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_search()
|
||||
{
|
||||
_docker_search() {
|
||||
case "$prev" in
|
||||
-s|--stars)
|
||||
return
|
||||
|
@ -578,8 +633,7 @@ _docker_search()
|
|||
esac
|
||||
}
|
||||
|
||||
_docker_start()
|
||||
{
|
||||
_docker_start() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-a --attach -i --interactive" -- "$cur" ) )
|
||||
|
@ -590,8 +644,7 @@ _docker_start()
|
|||
esac
|
||||
}
|
||||
|
||||
_docker_stop()
|
||||
{
|
||||
_docker_stop() {
|
||||
case "$prev" in
|
||||
-t|--time)
|
||||
return
|
||||
|
@ -610,8 +663,7 @@ _docker_stop()
|
|||
esac
|
||||
}
|
||||
|
||||
_docker_tag()
|
||||
{
|
||||
_docker_tag() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "-f --force" -- "$cur" ) )
|
||||
|
@ -633,61 +685,68 @@ _docker_tag()
|
|||
esac
|
||||
}
|
||||
|
||||
_docker_top()
|
||||
{
|
||||
_docker_unpause() {
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_unpauseable
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_top() {
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_containers_running
|
||||
fi
|
||||
}
|
||||
|
||||
_docker_version()
|
||||
{
|
||||
_docker_version() {
|
||||
return
|
||||
}
|
||||
|
||||
_docker_wait()
|
||||
{
|
||||
_docker_wait() {
|
||||
__docker_containers_all
|
||||
}
|
||||
|
||||
_docker()
|
||||
{
|
||||
local commands="
|
||||
attach
|
||||
build
|
||||
commit
|
||||
cp
|
||||
diff
|
||||
events
|
||||
export
|
||||
history
|
||||
images
|
||||
import
|
||||
info
|
||||
insert
|
||||
inspect
|
||||
kill
|
||||
load
|
||||
login
|
||||
logs
|
||||
port
|
||||
ps
|
||||
pull
|
||||
push
|
||||
restart
|
||||
rm
|
||||
rmi
|
||||
run
|
||||
save
|
||||
search
|
||||
start
|
||||
stop
|
||||
tag
|
||||
top
|
||||
version
|
||||
wait
|
||||
"
|
||||
_docker() {
|
||||
local commands=(
|
||||
attach
|
||||
build
|
||||
commit
|
||||
cp
|
||||
create
|
||||
diff
|
||||
events
|
||||
exec
|
||||
export
|
||||
history
|
||||
images
|
||||
import
|
||||
info
|
||||
insert
|
||||
inspect
|
||||
kill
|
||||
load
|
||||
login
|
||||
logs
|
||||
pause
|
||||
port
|
||||
ps
|
||||
pull
|
||||
push
|
||||
restart
|
||||
rm
|
||||
rmi
|
||||
run
|
||||
save
|
||||
search
|
||||
start
|
||||
stop
|
||||
tag
|
||||
top
|
||||
unpause
|
||||
version
|
||||
wait
|
||||
)
|
||||
|
||||
COMPREPLY=()
|
||||
local cur prev words cword
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
function __fish_docker_no_subcommand --description 'Test if docker has yet to be given the subcommand'
|
||||
for i in (commandline -opc)
|
||||
if contains -- $i attach build commit cp diff events export history images import info insert inspect kill load login logs port ps pull push restart rm rmi run save search start stop tag top version wait
|
||||
if contains -- $i attach build commit cp create diff events export history images import info insert inspect kill load login logs port ps pull push restart rm rmi run save search start stop tag top version wait
|
||||
return 1
|
||||
end
|
||||
end
|
||||
|
@ -72,6 +72,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from attach' -a '(__fish_pri
|
|||
|
||||
# build
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a build -d 'Build an image from a Dockerfile'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l force-rm -d 'Always remove intermediate containers, even after unsuccessful builds'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l no-cache -d 'Do not use cache when building the image'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from build' -s q -l quiet -d 'Suppress the verbose output generated by the containers'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l rm -d 'Remove intermediate containers after a successful build'
|
||||
|
@ -87,6 +88,33 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from commit' -a '(__fish_pri
|
|||
# cp
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a cp -d "Copy files/folders from a container's filesystem to the host path"
|
||||
|
||||
# create
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a run -d 'Run a command in a new container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s P -l publish-all -d 'Publish all exposed ports to the host interfaces'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s a -l attach -d 'Attach to stdin, stdout or stderr.'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s c -l cpu-shares -d 'CPU shares (relative weight)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l cidfile -d 'Write the container ID to the file'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l dns -d 'Set custom dns servers'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s e -l env -d 'Set environment variables'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l entrypoint -d 'Overwrite the default entrypoint of the image'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l expose -d 'Expose a port from the container without publishing it to your host'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s h -l hostname -d 'Container host name'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s i -l interactive -d 'Keep stdin open even if not attached'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l link -d 'Add link to another container (name:alias)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l lxc-conf -d 'Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s m -l memory -d 'Memory limit (format: <number><optional unit>, where unit = b, k, m or g)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s n -l networking -d 'Enable networking for this container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l name -d 'Assign a name to the container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s p -l publish -d "Publish a container's port to the host (format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort) (use 'docker port' to see the actual mapping)"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l privileged -d 'Give extended privileges to this container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s t -l tty -d 'Allocate a pseudo-tty'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s u -l user -d 'Username or UID'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s v -l volume -d 'Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l volumes-from -d 'Mount volumes from the specified container(s)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s w -l workdir -d 'Working directory inside the container'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -a '(__fish_print_docker_images)' -d "Image"
|
||||
|
||||
|
||||
# diff
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a diff -d "Inspect changes on a container's filesystem"
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from diff' -a '(__fish_print_docker_containers all)' -d "Container"
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
# version: 0.3.0
|
||||
# github: https://github.com/felixr/docker-zsh-completion
|
||||
#
|
||||
# contributers:
|
||||
# contributors:
|
||||
# - Felix Riedel
|
||||
# - Vincent Bernat
|
||||
#
|
||||
|
@ -37,65 +37,86 @@
|
|||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
|
||||
__parse_docker_list() {
|
||||
awk '
|
||||
NR == 1 {
|
||||
idx=1;i=0;f[i]=0
|
||||
header=$0
|
||||
while ( match(header, / ([A-Z]+|[A-Z]+ [A-Z]+)/) ) {
|
||||
idx += RSTART+1
|
||||
f[++i]=idx
|
||||
header = substr($0,idx)
|
||||
}
|
||||
f[++i]=999
|
||||
}
|
||||
__docker_get_containers() {
|
||||
local kind expl
|
||||
declare -a running stopped lines args
|
||||
|
||||
NR > 1 '"$1"' {
|
||||
for(j=0;j<i;j++) {
|
||||
x[j] = substr($0, f[j], f[j+1]-f[j]-1)
|
||||
gsub(/[ ]+$/, "", x[j])
|
||||
kind=$1
|
||||
shift
|
||||
[[ $kind = (stopped|all) ]] && args=($args -a)
|
||||
|
||||
lines=(${(f)"$(_call_program commands docker ps ${args})"})
|
||||
|
||||
# Parse header line to find columns
|
||||
local i=1 j=1 k header=${lines[1]}
|
||||
declare -A begin end
|
||||
while (( $j < ${#header} - 1 )) {
|
||||
i=$(( $j + ${${header[$j,-1]}[(i)[^ ]]} - 1))
|
||||
j=$(( $i + ${${header[$i,-1]}[(i) ]} - 1))
|
||||
k=$(( $j + ${${header[$j,-1]}[(i)[^ ]]} - 2))
|
||||
begin[${header[$i,$(($j-1))]}]=$i
|
||||
end[${header[$i,$(($j-1))]}]=$k
|
||||
}
|
||||
printf("%s:%7s, %s\n", x[0], x[3], x[1])
|
||||
if (x[6] != "") {
|
||||
split(x[6], names, /,/)
|
||||
for (name in names) printf("%s:%7s, %s\n", names[name], x[3], x[1])
|
||||
}
|
||||
}
|
||||
'| sed -e 's/ \([hdwm]\)\(inutes\|ays\|ours\|eeks\)/\1/'
|
||||
lines=(${lines[2,-1]})
|
||||
|
||||
# Container ID
|
||||
local line
|
||||
local s
|
||||
for line in $lines; do
|
||||
s="${line[${begin[CONTAINER ID]},${end[CONTAINER ID]}]%% ##}"
|
||||
s="$s:${(l:15:: :::)${${line[${begin[CREATED]},${end[CREATED]}]/ ago/}%% ##}}"
|
||||
s="$s, ${${${line[$begin[IMAGE],$end[IMAGE]]}/:/\\:}%% ##}"
|
||||
if [[ ${line[${begin[STATUS]},${end[STATUS]}]} = Exit* ]]; then
|
||||
stopped=($stopped $s)
|
||||
else
|
||||
running=($running $s)
|
||||
fi
|
||||
done
|
||||
|
||||
# Names
|
||||
local name
|
||||
local -a names
|
||||
for line in $lines; do
|
||||
names=(${(ps:,:)${${line[${begin[NAMES]},-1]}%% *}})
|
||||
for name in $names; do
|
||||
s="${name}:${(l:15:: :::)${${line[${begin[CREATED]},${end[CREATED]}]/ ago/}%% ##}}"
|
||||
s="$s, ${${${line[$begin[IMAGE],$end[IMAGE]]}/:/\\:}%% ##}"
|
||||
if [[ ${line[${begin[STATUS]},${end[STATUS]}]} = Exit* ]]; then
|
||||
stopped=($stopped $s)
|
||||
else
|
||||
running=($running $s)
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
[[ $kind = (running|all) ]] && _describe -t containers-running "running containers" running
|
||||
[[ $kind = (stopped|all) ]] && _describe -t containers-stopped "stopped containers" stopped
|
||||
}
|
||||
|
||||
__docker_stoppedcontainers() {
|
||||
local expl
|
||||
declare -a stoppedcontainers
|
||||
stoppedcontainers=(${(f)"$(_call_program commands docker ps -a | __parse_docker_list '&& / Exit/')"})
|
||||
_describe -t containers-stopped "Stopped Containers" stoppedcontainers "$@"
|
||||
__docker_get_containers stopped "$@"
|
||||
}
|
||||
|
||||
__docker_runningcontainers() {
|
||||
local expl
|
||||
declare -a containers
|
||||
|
||||
containers=(${(f)"$(_call_program commands docker ps | __parse_docker_list)"})
|
||||
_describe -t containers-active "Running Containers" containers "$@"
|
||||
__docker_get_containers running "$@"
|
||||
}
|
||||
|
||||
__docker_containers () {
|
||||
__docker_stoppedcontainers "$@"
|
||||
__docker_runningcontainers "$@"
|
||||
__docker_get_containers all "$@"
|
||||
}
|
||||
|
||||
__docker_images () {
|
||||
local expl
|
||||
declare -a images
|
||||
images=(${(f)"$(_call_program commands docker images | awk '(NR > 1 && $1 != "<none>"){printf("%s", $1);if ($2 != "<none>") printf("\\:%s", $2); printf("\n")}')"})
|
||||
images=($images ${(f)"$(_call_program commands docker images | awk '(NR > 1){printf("%s:%-15s in %s\n", $3,$2,$1)}')"})
|
||||
_describe -t docker-images "Images" images
|
||||
images=(${${${${(f)"$(_call_program commands docker images)"}[2,-1]}/ ##/\\:}%% *})
|
||||
images=(${${images%\\:<none>}#<none>} ${${${(f)"$(_call_program commands docker images)"}[2,-1]}/(#b)([^ ]##) ##([^ ]##) ##([^ ]##)*/${match[3]}:${(r:15:: :::)match[2]} in ${match[1]}})
|
||||
_describe -t docker-images "images" images
|
||||
}
|
||||
|
||||
__docker_tags() {
|
||||
local expl
|
||||
declare -a tags
|
||||
tags=(${(f)"$(_call_program commands docker images | awk '(NR>1){print $2}'| sort | uniq)"})
|
||||
tags=(${${${${${(f)"$(_call_program commands docker images)"}#* }## #}%% *}[2,-1]})
|
||||
_describe -t docker-tags "tags" tags
|
||||
}
|
||||
|
||||
|
@ -124,16 +145,15 @@ __docker_search() {
|
|||
if ( [[ ${(P)+cachename} -eq 0 ]] || _cache_invalid ${cachename#_} ) \
|
||||
&& ! _retrieve_cache ${cachename#_}; then
|
||||
_message "Searching for ${searchterm}..."
|
||||
result=(${(f)"$(_call_program commands docker search ${searchterm} | awk '(NR>2){print $1}')"})
|
||||
result=(${${${(f)"$(_call_program commands docker search ${searchterm})"}%% *}[2,-1]})
|
||||
_store_cache ${cachename#_} result
|
||||
fi
|
||||
_wanted dockersearch expl 'Available images' compadd -a result
|
||||
_wanted dockersearch expl 'available images' compadd -a result
|
||||
}
|
||||
|
||||
__docker_caching_policy()
|
||||
{
|
||||
# oldp=( "$1"(Nmh+24) ) # 24 hour
|
||||
oldp=( "$1"(Nmh+1) ) # 24 hour
|
||||
oldp=( "$1"(Nmh+1) ) # 1 hour
|
||||
(( $#oldp ))
|
||||
}
|
||||
|
||||
|
@ -141,8 +161,8 @@ __docker_caching_policy()
|
|||
__docker_repositories () {
|
||||
local expl
|
||||
declare -a repos
|
||||
repos=(${(f)"$(_call_program commands docker images | sed -e '1d' -e 's/[ ].*//' | sort | uniq)"})
|
||||
_describe -t docker-repos "Repositories" repos "$@"
|
||||
repos=(${${${(f)"$(_call_program commands docker images)"}%% *}[2,-1]})
|
||||
_describe -t docker-repos "repositories" repos "$@"
|
||||
}
|
||||
|
||||
__docker_commands () {
|
||||
|
@ -157,8 +177,7 @@ __docker_commands () {
|
|||
if ( [[ ${+_docker_subcommands} -eq 0 ]] || _cache_invalid docker_subcommands) \
|
||||
&& ! _retrieve_cache docker_subcommands;
|
||||
then
|
||||
_docker_subcommands=(${${(f)"$(_call_program commands
|
||||
docker 2>&1 | sed -e '1,6d' -e '/^[ ]*$/d' -e 's/[ ]*\([^ ]\+\)\s*\([^ ].*\)/\1:\2/' )"}})
|
||||
_docker_subcommands=(${${${${(f)"$(_call_program commands docker 2>&1)"}[5,-1]}## #}/ ##/:})
|
||||
_docker_subcommands=($_docker_subcommands 'help:Show help for a command')
|
||||
_store_cache docker_subcommands _docker_subcommands
|
||||
fi
|
||||
|
@ -176,16 +195,17 @@ __docker_subcommand () {
|
|||
;;
|
||||
(build)
|
||||
_arguments \
|
||||
'--force-rm[Always remove intermediate containers, even after unsuccessful builds]' \
|
||||
'--no-cache[Do not use cache when building the image]' \
|
||||
'-q[Suppress verbose build output]' \
|
||||
'--rm[Remove intermediate containers after a successful build]' \
|
||||
'-t=-:repository:__docker_repositories_with_tags' \
|
||||
'-t:repository:__docker_repositories_with_tags' \
|
||||
':path or URL:_directories'
|
||||
;;
|
||||
(commit)
|
||||
_arguments \
|
||||
'--author=-[Author]:author: ' \
|
||||
'-m=-[Commit message]:message: ' \
|
||||
'-m[Commit message]:message: ' \
|
||||
'--run=-[Configuration automatically applied when the image is run]:configuration: ' \
|
||||
':container:__docker_containers' \
|
||||
':repository:__docker_repositories_with_tags'
|
||||
|
@ -204,9 +224,42 @@ __docker_subcommand () {
|
|||
;;
|
||||
esac
|
||||
;;
|
||||
(create)
|
||||
_arguments \
|
||||
'-P[Publish all exposed ports to the host]' \
|
||||
'-a[Attach to stdin, stdout or stderr]' \
|
||||
'-c=-[CPU shares (relative weight)]:CPU shares:(0 10 100 200 500 800 1000)' \
|
||||
'--cidfile=-[Write the container ID to the file]:CID file:_files' \
|
||||
'*--dns=-[Set custom dns servers]:dns server: ' \
|
||||
'*-e=-[Set environment variables]:environment variable: ' \
|
||||
'--entrypoint=-[Overwrite the default entrypoint of the image]:entry point: ' \
|
||||
'*--expose=-[Expose a port from the container without publishing it]: ' \
|
||||
'-h=-[Container host name]:hostname:_hosts' \
|
||||
'-i[Keep stdin open even if not attached]' \
|
||||
'--link=-[Add link to another container]:link:->link' \
|
||||
'--lxc-conf=-[Add custom lxc options]:lxc options: ' \
|
||||
'-m=-[Memory limit (in bytes)]:limit: ' \
|
||||
'--name=-[Container name]:name: ' \
|
||||
'*-p=-[Expose a container'"'"'s port to the host]:port:_ports' \
|
||||
'--privileged[Give extended privileges to this container]' \
|
||||
'-t[Allocate a pseudo-tty]' \
|
||||
'-u=-[Username or UID]:user:_users' \
|
||||
'*-v=-[Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)]:volume: '\
|
||||
'--volumes-from=-[Mount volumes from the specified container]:volume: ' \
|
||||
'-w=-[Working directory inside the container]:directory:_directories' \
|
||||
'(-):images:__docker_images' \
|
||||
'(-):command: _command_names -e' \
|
||||
'*::arguments: _normal'
|
||||
(diff|export)
|
||||
_arguments '*:containers:__docker_containers'
|
||||
;;
|
||||
(exec)
|
||||
_arguments \
|
||||
'-d[Detached mode: leave the container running in the background]' \
|
||||
'-i[Keep stdin open even if not attached]' \
|
||||
'-t[Allocate a pseudo-tty]' \
|
||||
':containers:__docker_runningcontainers'
|
||||
;;
|
||||
(history)
|
||||
_arguments \
|
||||
'--no-trunc[Do not truncate output]' \
|
||||
|
@ -251,9 +304,9 @@ __docker_subcommand () {
|
|||
;;
|
||||
(login)
|
||||
_arguments \
|
||||
'-e=-[Email]:email: ' \
|
||||
'-p=-[Password]:password: ' \
|
||||
'-u=-[Username]:username: ' \
|
||||
'-e[Email]:email: ' \
|
||||
'-p[Password]:password: ' \
|
||||
'-u[Username]:username: ' \
|
||||
':server: '
|
||||
;;
|
||||
(logs)
|
||||
|
@ -283,7 +336,7 @@ __docker_subcommand () {
|
|||
'*:images:__docker_images'
|
||||
;;
|
||||
(restart|stop)
|
||||
_arguments '-t=-[Number of seconds to try to stop for before killing the container]:seconds to before killing:(1 5 10 30 60)' \
|
||||
_arguments '-t[Number of seconds to try to stop for before killing the container]:seconds to before killing:(1 5 10 30 60)' \
|
||||
'*:containers:__docker_runningcontainers'
|
||||
;;
|
||||
(top)
|
||||
|
@ -302,7 +355,7 @@ __docker_subcommand () {
|
|||
'-a[Show all containers]' \
|
||||
'--before=-[Show only container created before...]:containers:__docker_containers' \
|
||||
'-l[Show only the latest created container]' \
|
||||
'-n=-[Show n last created containers, include non-running one]:n:(1 5 10 25 50)' \
|
||||
'-n[Show n last created containers, include non-running one]:n:(1 5 10 25 50)' \
|
||||
'--no-trunc[Do not truncate output]' \
|
||||
'-q[Only show numeric IDs]' \
|
||||
'-s[Display sizes]' \
|
||||
|
@ -318,28 +371,28 @@ __docker_subcommand () {
|
|||
_arguments \
|
||||
'-P[Publish all exposed ports to the host]' \
|
||||
'-a[Attach to stdin, stdout or stderr]' \
|
||||
'-c=-[CPU shares (relative weight)]:CPU shares:(0 10 100 200 500 800 1000)' \
|
||||
'-c[CPU shares (relative weight)]:CPU shares:(0 10 100 200 500 800 1000)' \
|
||||
'--cidfile=-[Write the container ID to the file]:CID file:_files' \
|
||||
'-d[Detached mode: leave the container running in the background]' \
|
||||
'*--dns=-[Set custom dns servers]:dns server: ' \
|
||||
'*-e=-[Set environment variables]:environment variable: ' \
|
||||
'*-e[Set environment variables]:environment variable: ' \
|
||||
'--entrypoint=-[Overwrite the default entrypoint of the image]:entry point: ' \
|
||||
'*--expose=-[Expose a port from the container without publishing it]: ' \
|
||||
'-h=-[Container host name]:hostname:_hosts' \
|
||||
'-h[Container host name]:hostname:_hosts' \
|
||||
'-i[Keep stdin open even if not attached]' \
|
||||
'--link=-[Add link to another container]:link:->link' \
|
||||
'--lxc-conf=-[Add custom lxc options]:lxc options: ' \
|
||||
'-m=-[Memory limit (in bytes)]:limit: ' \
|
||||
'-m[Memory limit (in bytes)]:limit: ' \
|
||||
'--name=-[Container name]:name: ' \
|
||||
'*-p=-[Expose a container'"'"'s port to the host]:port:_ports' \
|
||||
'*-p[Expose a container'"'"'s port to the host]:port:_ports' \
|
||||
'--privileged[Give extended privileges to this container]' \
|
||||
'--rm[Remove intermediate containers when it exits]' \
|
||||
'--sig-proxy[Proxify all received signal]' \
|
||||
'-t[Allocate a pseudo-tty]' \
|
||||
'-u=-[Username or UID]:user:_users' \
|
||||
'*-v=-[Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)]:volume: '\
|
||||
'-u[Username or UID]:user:_users' \
|
||||
'*-v[Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)]:volume: '\
|
||||
'--volumes-from=-[Mount volumes from the specified container]:volume: ' \
|
||||
'-w=-[Working directory inside the container]:directory:_directories' \
|
||||
'-w[Working directory inside the container]:directory:_directories' \
|
||||
'(-):images:__docker_images' \
|
||||
'(-):command: _command_names -e' \
|
||||
'*::arguments: _normal'
|
||||
|
@ -359,7 +412,7 @@ __docker_subcommand () {
|
|||
_arguments ':name:__docker_search'
|
||||
;;
|
||||
(push)
|
||||
_arguments ':repository:__docker_repositories_with_tags'
|
||||
_arguments ':images:__docker_images'
|
||||
;;
|
||||
(save)
|
||||
_arguments \
|
||||
|
@ -389,7 +442,7 @@ _docker () {
|
|||
typeset -A opt_args
|
||||
|
||||
_arguments -C \
|
||||
'-H=-[tcp://host:port to bind/connect to]:socket: ' \
|
||||
'-H[tcp://host:port to bind/connect to]:socket: ' \
|
||||
'(-): :->command' \
|
||||
'(-)*:: :->option-or-argument'
|
||||
|
||||
|
@ -408,3 +461,11 @@ _docker () {
|
|||
}
|
||||
|
||||
_docker "$@"
|
||||
|
||||
# Local Variables:
|
||||
# mode: Shell-Script
|
||||
# sh-indentation: 4
|
||||
# indent-tabs-mode: nil
|
||||
# sh-basic-offset: 4
|
||||
# End:
|
||||
# vim: ft=zsh sw=4 ts=4 et
|
||||
|
|
|
@ -7,5 +7,5 @@ desktop applications.
|
|||
Examples
|
||||
========
|
||||
|
||||
* Data container: ./data/Dockerfile creates a data image sharing /data volume
|
||||
* Iceweasel: ./iceweasel/Dockerfile shows a way to dockerize a common multimedia application
|
||||
* Chromium: ./chromium/Dockerfile shows a way to dockerize a common application
|
||||
* Gparted: ./gparted/Dockerfile shows a way to dockerize a common application w devices
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# VERSION: 0.1
|
||||
# DESCRIPTION: Create chromium container with its dependencies
|
||||
# AUTHOR: Jessica Frazelle <jess@docker.com>
|
||||
# COMMENTS:
|
||||
# This file describes how to build a Chromium container with all
|
||||
# dependencies installed. It uses native X11 unix socket.
|
||||
# Tested on Debian Jessie
|
||||
# USAGE:
|
||||
# # Download Chromium Dockerfile
|
||||
# wget http://raw.githubusercontent.com/docker/docker/master/contrib/desktop-integration/chromium/Dockerfile
|
||||
#
|
||||
# # Build chromium image
|
||||
# docker build -t chromium .
|
||||
#
|
||||
# # Run stateful data-on-host chromium. For ephemeral, remove -v /data/chromium:/data
|
||||
# docker run -v /data/chromium:/data -v /tmp/.X11-unix:/tmp/.X11-unix \
|
||||
# -e DISPLAY=unix$DISPLAY chromium
|
||||
|
||||
# # To run stateful dockerized data containers
|
||||
# docker run --volumes-from chromium-data -v /tmp/.X11-unix:/tmp/.X11-unix \
|
||||
# -e DISPLAY=unix$DISPLAY chromium
|
||||
|
||||
DOCKER_VERSION 1.3
|
||||
|
||||
# Base docker image
|
||||
FROM debian:jessie
|
||||
MAINTAINER Jessica Frazelle <jess@docker.com>
|
||||
|
||||
# Install Chromium
|
||||
RUN apt-get update && apt-get install -y \
|
||||
chromium \
|
||||
chromium-l10n \
|
||||
libcanberra-gtk-module \
|
||||
libexif-dev \
|
||||
--no-install-recommends
|
||||
|
||||
# Autorun chromium
|
||||
CMD ["/usr/bin/chromium", "--no-sandbox", "--user-data-dir=/data"]
|
|
@ -1,38 +0,0 @@
|
|||
# VERSION: 0.1
|
||||
# DESCRIPTION: Create data image sharing /data volume
|
||||
# AUTHOR: Daniel Mizyrycki <daniel@dotcloud.com>
|
||||
# COMMENTS:
|
||||
# This image is used as base for all data containers.
|
||||
# /data volume is owned by sysadmin.
|
||||
# USAGE:
|
||||
# # Download data Dockerfile
|
||||
# wget http://raw.githubusercontent.com/dotcloud/docker/master/contrib/desktop-integration/data/Dockerfile
|
||||
#
|
||||
# # Build data image
|
||||
# docker build -t data .
|
||||
#
|
||||
# # Create a data container. (eg: iceweasel-data)
|
||||
# docker run --name iceweasel-data data true
|
||||
#
|
||||
# # List data from it
|
||||
# docker run --volumes-from iceweasel-data busybox ls -al /data
|
||||
|
||||
docker-version 0.6.5
|
||||
|
||||
# Smallest base image, just to launch a container
|
||||
FROM busybox
|
||||
MAINTAINER Daniel Mizyrycki <daniel@docker.com>
|
||||
|
||||
# Create a regular user
|
||||
RUN echo 'sysadmin:x:1000:1000::/data:/bin/sh' >> /etc/passwd
|
||||
RUN echo 'sysadmin:x:1000:' >> /etc/group
|
||||
|
||||
# Create directory for that user
|
||||
RUN mkdir /data
|
||||
RUN chown sysadmin.sysadmin /data
|
||||
|
||||
# Add content to /data. This will keep sysadmin ownership
|
||||
RUN touch /data/init_volume
|
||||
|
||||
# Create /data volume
|
||||
VOLUME /data
|
|
@ -0,0 +1,33 @@
|
|||
# VERSION: 0.1
|
||||
# DESCRIPTION: Create gparted container with its dependencies
|
||||
# AUTHOR: Jessica Frazelle <jess@docker.com>
|
||||
# COMMENTS:
|
||||
# This file describes how to build a gparted container with all
|
||||
# dependencies installed. It uses native X11 unix socket.
|
||||
# Tested on Debian Jessie
|
||||
# USAGE:
|
||||
# # Download gparted Dockerfile
|
||||
# wget http://raw.githubusercontent.com/docker/docker/master/contrib/desktop-integration/gparted/Dockerfile
|
||||
#
|
||||
# # Build gparted image
|
||||
# docker build -t gparted .
|
||||
#
|
||||
# docker run -v /tmp/.X11-unix:/tmp/.X11-unix \
|
||||
# --device=/dev/sda:/dev/sda \
|
||||
# -e DISPLAY=unix$DISPLAY gparted
|
||||
#
|
||||
|
||||
DOCKER-VERSION 1.3
|
||||
|
||||
# Base docker image
|
||||
FROM debian:jessie
|
||||
MAINTAINER Jessica Frazelle <jess@docker.com>
|
||||
|
||||
# Install Gparted and its dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gparted \
|
||||
libcanberra-gtk-module \
|
||||
--no-install-recommends
|
||||
|
||||
# Autorun gparted
|
||||
CMD ["/usr/sbin/gparted"]
|
|
@ -1,41 +0,0 @@
|
|||
# VERSION: 0.7
|
||||
# DESCRIPTION: Create iceweasel container with its dependencies
|
||||
# AUTHOR: Daniel Mizyrycki <daniel@dotcloud.com>
|
||||
# COMMENTS:
|
||||
# This file describes how to build a Iceweasel container with all
|
||||
# dependencies installed. It uses native X11 unix socket and alsa
|
||||
# sound devices. Tested on Debian 7.2
|
||||
# USAGE:
|
||||
# # Download Iceweasel Dockerfile
|
||||
# wget http://raw.githubusercontent.com/dotcloud/docker/master/contrib/desktop-integration/iceweasel/Dockerfile
|
||||
#
|
||||
# # Build iceweasel image
|
||||
# docker build -t iceweasel .
|
||||
#
|
||||
# # Run stateful data-on-host iceweasel. For ephemeral, remove -v /data/iceweasel:/data
|
||||
# docker run -v /data/iceweasel:/data -v /tmp/.X11-unix:/tmp/.X11-unix \
|
||||
# -v /dev/snd:/dev/snd --lxc-conf='lxc.cgroup.devices.allow = c 116:* rwm' \
|
||||
# -e DISPLAY=unix$DISPLAY iceweasel
|
||||
#
|
||||
# # To run stateful dockerized data containers
|
||||
# docker run --volumes-from iceweasel-data -v /tmp/.X11-unix:/tmp/.X11-unix \
|
||||
# -v /dev/snd:/dev/snd --lxc-conf='lxc.cgroup.devices.allow = c 116:* rwm' \
|
||||
# -e DISPLAY=unix$DISPLAY iceweasel
|
||||
|
||||
docker-version 0.6.5
|
||||
|
||||
# Base docker image
|
||||
FROM debian:wheezy
|
||||
MAINTAINER Daniel Mizyrycki <daniel@docker.com>
|
||||
|
||||
# Install Iceweasel and "sudo"
|
||||
RUN apt-get update && apt-get install -y iceweasel sudo
|
||||
|
||||
# create sysadmin account
|
||||
RUN useradd -m -d /data -p saIVpsc0EVTwA sysadmin
|
||||
RUN sed -Ei 's/sudo:x:27:/sudo:x:27:sysadmin/' /etc/group
|
||||
RUN sed -Ei 's/(\%sudo\s+ALL=\(ALL\:ALL\) )ALL/\1 NOPASSWD:ALL/' /etc/sudoers
|
||||
|
||||
# Autorun iceweasel. -no-remote is necessary to create a new container, as
|
||||
# iceweasel appears to communicate with itself through X11.
|
||||
CMD ["/usr/bin/sudo", "-u", "sysadmin", "-H", "-E", "/usr/bin/iceweasel", "-no-remote"]
|
|
@ -10,4 +10,4 @@ LimitNOFILE=1048576
|
|||
LimitNPROC=1048576
|
||||
|
||||
[Install]
|
||||
Also=docker.socket
|
||||
WantedBy=multi-user.target
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
[Unit]
|
||||
Description=Docker Socket for the API
|
||||
PartOf=docker.service
|
||||
|
||||
[Socket]
|
||||
ListenStream=/var/run/docker.sock
|
||||
|
|
|
@ -68,7 +68,7 @@ start() {
|
|||
|
||||
stop() {
|
||||
echo -n $"Stopping $prog: "
|
||||
killproc -p $pidfile $prog
|
||||
killproc -p $pidfile -d 300 $prog
|
||||
retval=$?
|
||||
echo
|
||||
[ $retval -eq 0 ] && rm -f $lockfile
|
||||
|
|
|
@ -6,7 +6,7 @@ mkimg="$(basename "$0")"
|
|||
usage() {
|
||||
echo >&2 "usage: $mkimg [-d dir] [-t tag] script [script-args]"
|
||||
echo >&2 " ie: $mkimg -t someuser/debian debootstrap --variant=minbase jessie"
|
||||
echo >&2 " $mkimg -t someuser/ubuntu debootstrap --include=ubuntu-minimal --components main,universe trusty"
|
||||
echo >&2 " $mkimg -t someuser/ubuntu debootstrap --include=ubuntu-minimal --components=main,universe trusty"
|
||||
echo >&2 " $mkimg -t someuser/busybox busybox-static"
|
||||
echo >&2 " $mkimg -t someuser/centos:5 rinse --distribution centos-5"
|
||||
echo >&2 " $mkimg -t someuser/mageia:4 mageia-urpmi --version=4"
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE language SYSTEM "language.dtd">
|
||||
<!-- Dockerfile syntaxfile v1.0 by James Turnbull <james@lovedthanlost.net> -->
|
||||
<language name="Dockerfile" section="Other"
|
||||
version="1.0" kateversion="2.4"
|
||||
extensions="Dockerfile"
|
||||
mimetype="text/plain"
|
||||
author="James Turnbull (james@lovedthanlost.net)"
|
||||
license="GPL">
|
||||
<highlighting>
|
||||
<list name="keywords">
|
||||
<item> FROM </item>
|
||||
<item> MAINTAINER </item>
|
||||
<item> ENV </item>
|
||||
<item> RUN </item>
|
||||
<item> ONBUILD </item>
|
||||
<item> COPY </item>
|
||||
<item> ADD </item>
|
||||
<item> VOLUME </item>
|
||||
<item> EXPOSE </item>
|
||||
<item> ENTRYPOINT </item>
|
||||
<item> CMD </item>
|
||||
<item> WORKDIR </item>
|
||||
<item> USER </item>
|
||||
</list>
|
||||
|
||||
<contexts>
|
||||
<context name="normal" attribute="Normal" lineEndContext="#stay">
|
||||
<DetectSpaces/>
|
||||
<DetectChar attribute="Comment" context="Comment" char="#"/>
|
||||
<keyword attribute="Keyword" context="#stay" String="keywords"/>
|
||||
<DetectIdentifier/>
|
||||
<DetectChar attribute="String" context="string"" char="""/>
|
||||
<DetectChar attribute="String" context="string'" char="'"/>
|
||||
</context>
|
||||
|
||||
<context attribute="Comment" lineEndContext="#pop" name="Comment">
|
||||
<LineContinue attribute="Comment" context="#stay" />
|
||||
</context>
|
||||
|
||||
<context name="string"" attribute="String" lineEndContext="#pop">
|
||||
<LineContinue attribute="Operator" context="#stay"/>
|
||||
<DetectChar attribute="String" context="#pop" char="""/>
|
||||
<DetectChar attribute="Operator" context="dollar" char="$"/>
|
||||
</context>
|
||||
|
||||
<context name="string'" attribute="String" lineEndContext="#pop">
|
||||
<LineContinue attribute="String" context="#stay"/>
|
||||
<DetectChar attribute="String" context="#pop" char="'"/>
|
||||
<DetectChar attribute="Operator" context="dollar" char="$"/>
|
||||
</context>
|
||||
|
||||
</contexts>
|
||||
<itemDatas>
|
||||
<itemData name="Normal" defStyleNum="dsNormal" spellChecking="0"/>
|
||||
<itemData name="Keyword" defStyleNum="dsKeyword" spellChecking="0"/>
|
||||
<itemData name="Comment" defStyleNum="dsComment"/>
|
||||
<itemData name="String" defStyleNum="dsString" spellChecking="0"/>
|
||||
</itemDatas>
|
||||
</highlighting>
|
||||
<general>
|
||||
<comments>
|
||||
<comment name = "singleLine" start = "#"/>
|
||||
</comments>
|
||||
</general>
|
||||
</language>
|
||||
<!-- kate: space-indent on; indent-width 2; replace-tabs on; -->
|
||||
|
|
@ -2,14 +2,15 @@ package daemon
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/pkg/jsonlog"
|
||||
"github.com/docker/docker/pkg/log"
|
||||
"github.com/docker/docker/pkg/promise"
|
||||
"github.com/docker/docker/utils"
|
||||
)
|
||||
|
||||
|
@ -68,10 +69,10 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
|
|||
break
|
||||
}
|
||||
if l.Stream == "stdout" && stdout {
|
||||
fmt.Fprintf(job.Stdout, "%s", l.Log)
|
||||
io.WriteString(job.Stdout, l.Log)
|
||||
}
|
||||
if l.Stream == "stderr" && stderr {
|
||||
fmt.Fprintf(job.Stderr, "%s", l.Log)
|
||||
io.WriteString(job.Stderr, l.Log)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,12 +103,11 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
|
|||
cStderr = job.Stderr
|
||||
}
|
||||
|
||||
<-daemon.Attach(container, cStdin, cStdinCloser, cStdout, cStderr)
|
||||
|
||||
<-daemon.Attach(&container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, cStdin, cStdinCloser, cStdout, cStderr)
|
||||
// If we are in stdinonce mode, wait for the process to end
|
||||
// otherwise, simply return
|
||||
if container.Config.StdinOnce && !container.Config.Tty {
|
||||
container.State.WaitStop(-1 * time.Second)
|
||||
container.WaitStop(-1 * time.Second)
|
||||
}
|
||||
}
|
||||
return engine.StatusOK
|
||||
|
@ -115,27 +115,29 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
|
|||
|
||||
// FIXME: this should be private, and every outside subsystem
|
||||
// should go through the "container_attach" job. But that would require
|
||||
// that job to be properly documented, as well as the relationship betweem
|
||||
// that job to be properly documented, as well as the relationship between
|
||||
// Attach and ContainerAttach.
|
||||
//
|
||||
// This method is in use by builder/builder.go.
|
||||
func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error {
|
||||
func (daemon *Daemon) Attach(streamConfig *StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error {
|
||||
var (
|
||||
cStdout, cStderr io.ReadCloser
|
||||
nJobs int
|
||||
errors = make(chan error, 3)
|
||||
)
|
||||
|
||||
if stdin != nil && container.Config.OpenStdin {
|
||||
nJobs += 1
|
||||
if cStdin, err := container.StdinPipe(); err != nil {
|
||||
// Connect stdin of container to the http conn.
|
||||
if stdin != nil && openStdin {
|
||||
nJobs++
|
||||
// Get the stdin pipe.
|
||||
if cStdin, err := streamConfig.StdinPipe(); err != nil {
|
||||
errors <- err
|
||||
} else {
|
||||
go func() {
|
||||
log.Debugf("attach: stdin: begin")
|
||||
defer log.Debugf("attach: stdin: end")
|
||||
// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
|
||||
if container.Config.StdinOnce && !container.Config.Tty {
|
||||
if stdinOnce && !tty {
|
||||
defer cStdin.Close()
|
||||
} else {
|
||||
defer func() {
|
||||
|
@ -147,10 +149,11 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
|
|||
}
|
||||
}()
|
||||
}
|
||||
if container.Config.Tty {
|
||||
if tty {
|
||||
_, err = utils.CopyEscapable(cStdin, stdin)
|
||||
} else {
|
||||
_, err = io.Copy(cStdin, stdin)
|
||||
|
||||
}
|
||||
if err == io.ErrClosedPipe {
|
||||
err = nil
|
||||
|
@ -163,8 +166,9 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
|
|||
}
|
||||
}
|
||||
if stdout != nil {
|
||||
nJobs += 1
|
||||
if p, err := container.StdoutPipe(); err != nil {
|
||||
nJobs++
|
||||
// Get a reader end of a pipe that is attached as stdout to the container.
|
||||
if p, err := streamConfig.StdoutPipe(); err != nil {
|
||||
errors <- err
|
||||
} else {
|
||||
cStdout = p
|
||||
|
@ -172,7 +176,7 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
|
|||
log.Debugf("attach: stdout: begin")
|
||||
defer log.Debugf("attach: stdout: end")
|
||||
// If we are in StdinOnce mode, then close stdin
|
||||
if container.Config.StdinOnce && stdin != nil {
|
||||
if stdinOnce && stdin != nil {
|
||||
defer stdin.Close()
|
||||
}
|
||||
if stdinCloser != nil {
|
||||
|
@ -189,20 +193,21 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
|
|||
}()
|
||||
}
|
||||
} else {
|
||||
// Point stdout of container to a no-op writer.
|
||||
go func() {
|
||||
if stdinCloser != nil {
|
||||
defer stdinCloser.Close()
|
||||
}
|
||||
if cStdout, err := container.StdoutPipe(); err != nil {
|
||||
if cStdout, err := streamConfig.StdoutPipe(); err != nil {
|
||||
log.Errorf("attach: stdout pipe: %s", err)
|
||||
} else {
|
||||
io.Copy(&utils.NopWriter{}, cStdout)
|
||||
io.Copy(&ioutils.NopWriter{}, cStdout)
|
||||
}
|
||||
}()
|
||||
}
|
||||
if stderr != nil {
|
||||
nJobs += 1
|
||||
if p, err := container.StderrPipe(); err != nil {
|
||||
nJobs++
|
||||
if p, err := streamConfig.StderrPipe(); err != nil {
|
||||
errors <- err
|
||||
} else {
|
||||
cStderr = p
|
||||
|
@ -210,7 +215,8 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
|
|||
log.Debugf("attach: stderr: begin")
|
||||
defer log.Debugf("attach: stderr: end")
|
||||
// If we are in StdinOnce mode, then close stdin
|
||||
if container.Config.StdinOnce && stdin != nil {
|
||||
// Why are we closing stdin here and above while handling stdout?
|
||||
if stdinOnce && stdin != nil {
|
||||
defer stdin.Close()
|
||||
}
|
||||
if stdinCloser != nil {
|
||||
|
@ -227,20 +233,21 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
|
|||
}()
|
||||
}
|
||||
} else {
|
||||
// Point stderr at a no-op writer.
|
||||
go func() {
|
||||
if stdinCloser != nil {
|
||||
defer stdinCloser.Close()
|
||||
}
|
||||
|
||||
if cStderr, err := container.StderrPipe(); err != nil {
|
||||
if cStderr, err := streamConfig.StderrPipe(); err != nil {
|
||||
log.Errorf("attach: stdout pipe: %s", err)
|
||||
} else {
|
||||
io.Copy(&utils.NopWriter{}, cStderr)
|
||||
io.Copy(&ioutils.NopWriter{}, cStderr)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return utils.Go(func() error {
|
||||
return promise.Go(func() error {
|
||||
defer func() {
|
||||
if cStdout != nil {
|
||||
cStdout.Close()
|
||||
|
@ -252,7 +259,7 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
|
|||
|
||||
// FIXME: how to 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 {
|
||||
for i := 0; i < nJobs; i++ {
|
||||
log.Debugf("attach: waiting for job %d/%d", i+1, nJobs)
|
||||
if err := <-errors; err != nil {
|
||||
log.Errorf("attach: job %d returned error %s, aborting all jobs", i+1, err)
|
||||
|
|
1008
daemon/build.go
1008
daemon/build.go
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -10,7 +10,7 @@ import (
|
|||
|
||||
const (
|
||||
defaultNetworkMtu = 1500
|
||||
DisableNetworkBridge = "none"
|
||||
disableNetworkBridge = "none"
|
||||
)
|
||||
|
||||
// Config define the configuration of a docker daemon
|
||||
|
@ -23,11 +23,14 @@ type Config struct {
|
|||
AutoRestart bool
|
||||
Dns []string
|
||||
DnsSearch []string
|
||||
Mirrors []string
|
||||
EnableIptables bool
|
||||
EnableIpForward bool
|
||||
EnableIpMasq bool
|
||||
DefaultIp net.IP
|
||||
BridgeIface string
|
||||
BridgeIP string
|
||||
FixedCIDR string
|
||||
InterContainerCommunication bool
|
||||
GraphDriver string
|
||||
GraphOptions []string
|
||||
|
@ -45,11 +48,13 @@ type Config struct {
|
|||
func (config *Config) InstallFlags() {
|
||||
flag.StringVar(&config.Pidfile, []string{"p", "-pidfile"}, "/var/run/docker.pid", "Path to use for daemon PID file")
|
||||
flag.StringVar(&config.Root, []string{"g", "-graph"}, "/var/lib/docker", "Path to use as the root of the Docker runtime")
|
||||
flag.BoolVar(&config.AutoRestart, []string{"#r", "#-restart"}, true, "--restart on the daemon has been deprecated infavor of --restart policies on docker run")
|
||||
flag.BoolVar(&config.AutoRestart, []string{"#r", "#-restart"}, true, "--restart on the daemon has been deprecated in favor of --restart policies on docker run")
|
||||
flag.BoolVar(&config.EnableIptables, []string{"#iptables", "-iptables"}, true, "Enable Docker's addition of iptables rules")
|
||||
flag.BoolVar(&config.EnableIpForward, []string{"#ip-forward", "-ip-forward"}, true, "Enable net.ipv4.ip_forward")
|
||||
flag.BoolVar(&config.EnableIpMasq, []string{"-ip-masq"}, true, "Enable IP masquerading for bridge's IP range")
|
||||
flag.StringVar(&config.BridgeIP, []string{"#bip", "-bip"}, "", "Use this CIDR notation address for the network bridge's IP, not compatible with -b")
|
||||
flag.StringVar(&config.BridgeIface, []string{"b", "-bridge"}, "", "Attach containers to a pre-existing network bridge\nuse 'none' to disable container networking")
|
||||
flag.StringVar(&config.FixedCIDR, []string{"-fixed-cidr"}, "", "IPv4 subnet for fixed IPs (ex: 10.20.0.0/16)\nthis subnet must be nested in the bridge subnet (which is defined by -b or --bip)")
|
||||
flag.BoolVar(&config.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Enable inter-container communication")
|
||||
flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Force the Docker runtime to use a specific storage driver")
|
||||
flag.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, "native", "Force the Docker runtime to use a specific exec driver")
|
||||
|
@ -60,6 +65,7 @@ func (config *Config) InstallFlags() {
|
|||
// FIXME: why the inconsistency between "hosts" and "sockets"?
|
||||
opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "Force Docker to use specific DNS servers")
|
||||
opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "Force Docker to use specific DNS search domains")
|
||||
opts.MirrorListVar(&config.Mirrors, []string{"-registry-mirror"}, "Specify a preferred Docker registry mirror")
|
||||
}
|
||||
|
||||
func GetDefaultNetworkMtu() int {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package daemon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -10,24 +11,24 @@ import (
|
|||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/docker/libcontainer/devices"
|
||||
"github.com/docker/libcontainer/label"
|
||||
|
||||
"github.com/docker/docker/archive"
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/docker/docker/daemon/graphdriver"
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/links"
|
||||
"github.com/docker/docker/nat"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/broadcastwriter"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/pkg/log"
|
||||
"github.com/docker/docker/pkg/networkfs/etchosts"
|
||||
"github.com/docker/docker/pkg/networkfs/resolvconf"
|
||||
"github.com/docker/docker/pkg/promise"
|
||||
"github.com/docker/docker/pkg/symlink"
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/docker/docker/utils"
|
||||
|
@ -42,10 +43,17 @@ var (
|
|||
ErrContainerStartTimeout = errors.New("The container failed to start due to timed out.")
|
||||
)
|
||||
|
||||
type StreamConfig struct {
|
||||
stdout *broadcastwriter.BroadcastWriter
|
||||
stderr *broadcastwriter.BroadcastWriter
|
||||
stdin io.ReadCloser
|
||||
stdinPipe io.WriteCloser
|
||||
}
|
||||
|
||||
type Container struct {
|
||||
sync.Mutex
|
||||
root string // Path to the "home" of the container, including metadata.
|
||||
basefs string // Path to the graphdriver mountpoint
|
||||
*State `json:"State"` // Needed for remote api version <= 1.11
|
||||
root string // Path to the "home" of the container, including metadata.
|
||||
basefs string // Path to the graphdriver mountpoint
|
||||
|
||||
ID string
|
||||
|
||||
|
@ -55,7 +63,6 @@ type Container struct {
|
|||
Args []string
|
||||
|
||||
Config *runconfig.Config
|
||||
State *State
|
||||
Image string
|
||||
|
||||
NetworkSettings *NetworkSettings
|
||||
|
@ -67,24 +74,26 @@ type Container struct {
|
|||
Driver string
|
||||
ExecDriver string
|
||||
|
||||
command *execdriver.Command
|
||||
stdout *broadcastwriter.BroadcastWriter
|
||||
stderr *broadcastwriter.BroadcastWriter
|
||||
stdin io.ReadCloser
|
||||
stdinPipe io.WriteCloser
|
||||
command *execdriver.Command
|
||||
StreamConfig
|
||||
|
||||
daemon *Daemon
|
||||
MountLabel, ProcessLabel string
|
||||
AppArmorProfile string
|
||||
RestartCount int
|
||||
|
||||
// Maps container paths to volume paths. The key in this is the path to which
|
||||
// the volume is being mounted inside the container. Value is the path of the
|
||||
// volume on disk
|
||||
Volumes map[string]string
|
||||
// Store rw/ro in a separate structure to preserve reverse-compatibility on-disk.
|
||||
// Easier than migrating older container configs :)
|
||||
VolumesRW map[string]bool
|
||||
hostConfig *runconfig.HostConfig
|
||||
|
||||
activeLinks map[string]*links.Link
|
||||
monitor *containerMonitor
|
||||
activeLinks map[string]*links.Link
|
||||
monitor *containerMonitor
|
||||
execCommands *execStore
|
||||
}
|
||||
|
||||
func (container *Container) FromDisk() error {
|
||||
|
@ -189,14 +198,7 @@ func (container *Container) getRootResourcePath(path string) (string, error) {
|
|||
}
|
||||
|
||||
func populateCommand(c *Container, env []string) error {
|
||||
var (
|
||||
en *execdriver.Network
|
||||
context = make(map[string][]string)
|
||||
)
|
||||
context["process_label"] = []string{c.GetProcessLabel()}
|
||||
context["mount_label"] = []string{c.GetMountLabel()}
|
||||
|
||||
en = &execdriver.Network{
|
||||
en := &execdriver.Network{
|
||||
Mtu: c.daemon.config.Mtu,
|
||||
Interface: nil,
|
||||
}
|
||||
|
@ -214,6 +216,7 @@ func populateCommand(c *Container, env []string) error {
|
|||
Bridge: network.Bridge,
|
||||
IPAddress: network.IPAddress,
|
||||
IPPrefixLen: network.IPPrefixLen,
|
||||
MacAddress: network.MacAddress,
|
||||
}
|
||||
}
|
||||
case "container":
|
||||
|
@ -230,10 +233,10 @@ func populateCommand(c *Container, env []string) error {
|
|||
userSpecifiedDevices := make([]*devices.Device, len(c.hostConfig.Devices))
|
||||
for i, deviceMapping := range c.hostConfig.Devices {
|
||||
device, err := devices.GetDevice(deviceMapping.PathOnHost, deviceMapping.CgroupPermissions)
|
||||
device.Path = deviceMapping.PathInContainer
|
||||
if err != nil {
|
||||
return fmt.Errorf("error gathering device information while adding custom device %s", err)
|
||||
return fmt.Errorf("error gathering device information while adding custom device %q: %s", deviceMapping.PathOnHost, err)
|
||||
}
|
||||
device.Path = deviceMapping.PathInContainer
|
||||
userSpecifiedDevices[i] = device
|
||||
}
|
||||
allowedDevices := append(devices.DefaultAllowedDevices, userSpecifiedDevices...)
|
||||
|
@ -241,7 +244,7 @@ func populateCommand(c *Container, env []string) error {
|
|||
autoCreatedDevices := append(devices.DefaultAutoCreatedDevices, userSpecifiedDevices...)
|
||||
|
||||
// TODO: this can be removed after lxc-conf is fully deprecated
|
||||
mergeLxcConfIntoOptions(c.hostConfig, context)
|
||||
lxcConfig := mergeLxcConfIntoOptions(c.hostConfig)
|
||||
|
||||
resources := &execdriver.Resources{
|
||||
Memory: c.Config.Memory,
|
||||
|
@ -249,26 +252,36 @@ func populateCommand(c *Container, env []string) error {
|
|||
CpuShares: c.Config.CpuShares,
|
||||
Cpuset: c.Config.Cpuset,
|
||||
}
|
||||
|
||||
processConfig := execdriver.ProcessConfig{
|
||||
Privileged: c.hostConfig.Privileged,
|
||||
Entrypoint: c.Path,
|
||||
Arguments: c.Args,
|
||||
Tty: c.Config.Tty,
|
||||
User: c.Config.User,
|
||||
}
|
||||
|
||||
processConfig.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
||||
processConfig.Env = env
|
||||
|
||||
c.command = &execdriver.Command{
|
||||
ID: c.ID,
|
||||
Privileged: c.hostConfig.Privileged,
|
||||
Rootfs: c.RootfsPath(),
|
||||
InitPath: "/.dockerinit",
|
||||
Entrypoint: c.Path,
|
||||
Arguments: c.Args,
|
||||
WorkingDir: c.Config.WorkingDir,
|
||||
Network: en,
|
||||
Tty: c.Config.Tty,
|
||||
User: c.Config.User,
|
||||
Config: context,
|
||||
Resources: resources,
|
||||
AllowedDevices: allowedDevices,
|
||||
AutoCreatedDevices: autoCreatedDevices,
|
||||
CapAdd: c.hostConfig.CapAdd,
|
||||
CapDrop: c.hostConfig.CapDrop,
|
||||
ProcessConfig: processConfig,
|
||||
ProcessLabel: c.GetProcessLabel(),
|
||||
MountLabel: c.GetMountLabel(),
|
||||
LxcConfig: lxcConfig,
|
||||
AppArmorProfile: c.AppArmorProfile,
|
||||
}
|
||||
c.command.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
||||
c.command.Env = env
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -276,7 +289,7 @@ func (container *Container) Start() (err error) {
|
|||
container.Lock()
|
||||
defer container.Unlock()
|
||||
|
||||
if container.State.IsRunning() {
|
||||
if container.Running {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -297,8 +310,11 @@ func (container *Container) Start() (err error) {
|
|||
if err := container.initializeNetworking(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.updateParentsHosts(); err != nil {
|
||||
return err
|
||||
}
|
||||
container.verifyDaemonSettings()
|
||||
if err := prepareVolumesForContainer(container); err != nil {
|
||||
if err := container.prepareVolumes(); err != nil {
|
||||
return err
|
||||
}
|
||||
linkedEnv, err := container.setupLinkedContainers()
|
||||
|
@ -312,7 +328,7 @@ func (container *Container) Start() (err error) {
|
|||
if err := populateCommand(container, env); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := setupMountsForContainer(container); err != nil {
|
||||
if err := container.setupMounts(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -323,7 +339,7 @@ func (container *Container) Run() error {
|
|||
if err := container.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
container.State.WaitStop(-1 * time.Second)
|
||||
container.WaitStop(-1 * time.Second)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -337,11 +353,11 @@ func (container *Container) Output() (output []byte, err error) {
|
|||
return nil, err
|
||||
}
|
||||
output, err = ioutil.ReadAll(pipe)
|
||||
container.State.WaitStop(-1 * time.Second)
|
||||
container.WaitStop(-1 * time.Second)
|
||||
return output, err
|
||||
}
|
||||
|
||||
// Container.StdinPipe returns a WriteCloser which can be used to feed data
|
||||
// StreamConfig.StdinPipe returns a WriteCloser which can be used to feed data
|
||||
// to the standard input of the container's active process.
|
||||
// Container.StdoutPipe and Container.StderrPipe each return a ReadCloser
|
||||
// which can be used to retrieve the standard output (and error) generated
|
||||
|
@ -349,32 +365,32 @@ func (container *Container) Output() (output []byte, err error) {
|
|||
// copied and delivered to all StdoutPipe and StderrPipe consumers, using
|
||||
// a kind of "broadcaster".
|
||||
|
||||
func (container *Container) StdinPipe() (io.WriteCloser, error) {
|
||||
return container.stdinPipe, nil
|
||||
func (streamConfig *StreamConfig) StdinPipe() (io.WriteCloser, error) {
|
||||
return streamConfig.stdinPipe, nil
|
||||
}
|
||||
|
||||
func (container *Container) StdoutPipe() (io.ReadCloser, error) {
|
||||
func (streamConfig *StreamConfig) StdoutPipe() (io.ReadCloser, error) {
|
||||
reader, writer := io.Pipe()
|
||||
container.stdout.AddWriter(writer, "")
|
||||
return utils.NewBufReader(reader), nil
|
||||
streamConfig.stdout.AddWriter(writer, "")
|
||||
return ioutils.NewBufReader(reader), nil
|
||||
}
|
||||
|
||||
func (container *Container) StderrPipe() (io.ReadCloser, error) {
|
||||
func (streamConfig *StreamConfig) StderrPipe() (io.ReadCloser, error) {
|
||||
reader, writer := io.Pipe()
|
||||
container.stderr.AddWriter(writer, "")
|
||||
return utils.NewBufReader(reader), nil
|
||||
streamConfig.stderr.AddWriter(writer, "")
|
||||
return ioutils.NewBufReader(reader), nil
|
||||
}
|
||||
|
||||
func (container *Container) StdoutLogPipe() io.ReadCloser {
|
||||
func (streamConfig *StreamConfig) StdoutLogPipe() io.ReadCloser {
|
||||
reader, writer := io.Pipe()
|
||||
container.stdout.AddWriter(writer, "stdout")
|
||||
return utils.NewBufReader(reader)
|
||||
streamConfig.stdout.AddWriter(writer, "stdout")
|
||||
return ioutils.NewBufReader(reader)
|
||||
}
|
||||
|
||||
func (container *Container) StderrLogPipe() io.ReadCloser {
|
||||
func (streamConfig *StreamConfig) StderrLogPipe() io.ReadCloser {
|
||||
reader, writer := io.Pipe()
|
||||
container.stderr.AddWriter(writer, "stderr")
|
||||
return utils.NewBufReader(reader)
|
||||
streamConfig.stderr.AddWriter(writer, "stderr")
|
||||
return ioutils.NewBufReader(reader)
|
||||
}
|
||||
|
||||
func (container *Container) buildHostnameFile() error {
|
||||
|
@ -390,10 +406,7 @@ func (container *Container) buildHostnameFile() error {
|
|||
return ioutil.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644)
|
||||
}
|
||||
|
||||
func (container *Container) buildHostnameAndHostsFiles(IP string) error {
|
||||
if err := container.buildHostnameFile(); err != nil {
|
||||
return err
|
||||
}
|
||||
func (container *Container) buildHostsFiles(IP string) error {
|
||||
|
||||
hostsPath, err := container.getRootResourcePath("hosts")
|
||||
if err != nil {
|
||||
|
@ -413,12 +426,25 @@ func (container *Container) buildHostnameAndHostsFiles(IP string) error {
|
|||
extraContent[alias] = child.NetworkSettings.IPAddress
|
||||
}
|
||||
|
||||
for _, extraHost := range container.hostConfig.ExtraHosts {
|
||||
parts := strings.Split(extraHost, ":")
|
||||
extraContent[parts[0]] = parts[1]
|
||||
}
|
||||
|
||||
return etchosts.Build(container.HostsPath, IP, container.Config.Hostname, container.Config.Domainname, &extraContent)
|
||||
}
|
||||
|
||||
func (container *Container) allocateNetwork() error {
|
||||
func (container *Container) buildHostnameAndHostsFiles(IP string) error {
|
||||
if err := container.buildHostnameFile(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return container.buildHostsFiles(IP)
|
||||
}
|
||||
|
||||
func (container *Container) AllocateNetwork() error {
|
||||
mode := container.hostConfig.NetworkMode
|
||||
if container.Config.NetworkDisabled || mode.IsContainer() || mode.IsHost() {
|
||||
if container.Config.NetworkDisabled || !mode.IsPrivate() {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -432,16 +458,22 @@ func (container *Container) allocateNetwork() error {
|
|||
if env, err = job.Stdout.AddEnv(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := job.Run(); err != nil {
|
||||
if err = job.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Error handling: At this point, the interface is allocated so we have to
|
||||
// make sure that it is always released in case of error, otherwise we
|
||||
// might leak resources.
|
||||
|
||||
if container.Config.PortSpecs != nil {
|
||||
if err := migratePortMappings(container.Config, container.hostConfig); err != nil {
|
||||
if err = migratePortMappings(container.Config, container.hostConfig); err != nil {
|
||||
eng.Job("release_interface", container.ID).Run()
|
||||
return err
|
||||
}
|
||||
container.Config.PortSpecs = nil
|
||||
if err := container.WriteHostConfig(); err != nil {
|
||||
if err = container.WriteHostConfig(); err != nil {
|
||||
eng.Job("release_interface", container.ID).Run()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -470,7 +502,8 @@ func (container *Container) allocateNetwork() error {
|
|||
container.NetworkSettings.PortMapping = nil
|
||||
|
||||
for port := range portSpecs {
|
||||
if err := container.allocatePort(eng, port, bindings); err != nil {
|
||||
if err = container.allocatePort(eng, port, bindings); err != nil {
|
||||
eng.Job("release_interface", container.ID).Run()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -480,12 +513,13 @@ func (container *Container) allocateNetwork() error {
|
|||
container.NetworkSettings.Bridge = env.Get("Bridge")
|
||||
container.NetworkSettings.IPAddress = env.Get("IP")
|
||||
container.NetworkSettings.IPPrefixLen = env.GetInt("IPPrefixLen")
|
||||
container.NetworkSettings.MacAddress = env.Get("MacAddress")
|
||||
container.NetworkSettings.Gateway = env.Get("Gateway")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) releaseNetwork() {
|
||||
func (container *Container) ReleaseNetwork() {
|
||||
if container.Config.NetworkDisabled {
|
||||
return
|
||||
}
|
||||
|
@ -495,10 +529,42 @@ func (container *Container) releaseNetwork() {
|
|||
container.NetworkSettings = &NetworkSettings{}
|
||||
}
|
||||
|
||||
func (container *Container) isNetworkAllocated() bool {
|
||||
return container.NetworkSettings.IPAddress != ""
|
||||
}
|
||||
|
||||
func (container *Container) RestoreNetwork() error {
|
||||
mode := container.hostConfig.NetworkMode
|
||||
// Don't attempt a restore if we previously didn't allocate networking.
|
||||
// This might be a legacy container with no network allocated, in which case the
|
||||
// allocation will happen once and for all at start.
|
||||
if !container.isNetworkAllocated() || container.Config.NetworkDisabled || !mode.IsPrivate() {
|
||||
return nil
|
||||
}
|
||||
|
||||
eng := container.daemon.eng
|
||||
|
||||
// Re-allocate the interface with the same IP and MAC address.
|
||||
job := eng.Job("allocate_interface", container.ID)
|
||||
job.Setenv("RequestedIP", container.NetworkSettings.IPAddress)
|
||||
job.Setenv("RequestedMac", container.NetworkSettings.MacAddress)
|
||||
if err := job.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Re-allocate any previously allocated ports.
|
||||
for port := range container.NetworkSettings.Ports {
|
||||
if err := container.allocatePort(eng, port, container.NetworkSettings.Ports); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// cleanup releases any network resources allocated to the container along with any rules
|
||||
// around how containers are linked together. It also unmounts the container's root filesystem.
|
||||
func (container *Container) cleanup() {
|
||||
container.releaseNetwork()
|
||||
container.ReleaseNetwork()
|
||||
|
||||
// Disable all active links
|
||||
if container.activeLinks != nil {
|
||||
|
@ -518,11 +584,11 @@ func (container *Container) KillSig(sig int) error {
|
|||
defer container.Unlock()
|
||||
|
||||
// We could unpause the container for them rather than returning this error
|
||||
if container.State.IsPaused() {
|
||||
if container.Paused {
|
||||
return fmt.Errorf("Container %s is paused. Unpause the container before stopping", container.ID)
|
||||
}
|
||||
|
||||
if !container.State.IsRunning() {
|
||||
if !container.Running {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -533,7 +599,7 @@ func (container *Container) KillSig(sig int) error {
|
|||
// if the container is currently restarting we do not need to send the signal
|
||||
// to the process. Telling the monitor that it should exit on it's next event
|
||||
// loop is enough
|
||||
if container.State.IsRestarting() {
|
||||
if container.Restarting {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -541,27 +607,27 @@ func (container *Container) KillSig(sig int) error {
|
|||
}
|
||||
|
||||
func (container *Container) Pause() error {
|
||||
if container.State.IsPaused() {
|
||||
if container.IsPaused() {
|
||||
return fmt.Errorf("Container %s is already paused", container.ID)
|
||||
}
|
||||
if !container.State.IsRunning() {
|
||||
if !container.IsRunning() {
|
||||
return fmt.Errorf("Container %s is not running", container.ID)
|
||||
}
|
||||
return container.daemon.Pause(container)
|
||||
}
|
||||
|
||||
func (container *Container) Unpause() error {
|
||||
if !container.State.IsPaused() {
|
||||
if !container.IsPaused() {
|
||||
return fmt.Errorf("Container %s is not paused", container.ID)
|
||||
}
|
||||
if !container.State.IsRunning() {
|
||||
if !container.IsRunning() {
|
||||
return fmt.Errorf("Container %s is not running", container.ID)
|
||||
}
|
||||
return container.daemon.Unpause(container)
|
||||
}
|
||||
|
||||
func (container *Container) Kill() error {
|
||||
if !container.State.IsRunning() {
|
||||
if !container.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -571,9 +637,9 @@ func (container *Container) Kill() error {
|
|||
}
|
||||
|
||||
// 2. Wait for the process to die, in last resort, try to kill the process directly
|
||||
if _, err := container.State.WaitStop(10 * time.Second); err != nil {
|
||||
if _, err := container.WaitStop(10 * time.Second); err != nil {
|
||||
// Ensure that we don't kill ourselves
|
||||
if pid := container.State.GetPid(); pid != 0 {
|
||||
if pid := container.GetPid(); pid != 0 {
|
||||
log.Infof("Container %s failed to exit within 10 seconds of kill - trying direct SIGKILL", utils.TruncateID(container.ID))
|
||||
if err := syscall.Kill(pid, 9); err != nil {
|
||||
return err
|
||||
|
@ -581,12 +647,12 @@ func (container *Container) Kill() error {
|
|||
}
|
||||
}
|
||||
|
||||
container.State.WaitStop(-1 * time.Second)
|
||||
container.WaitStop(-1 * time.Second)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) Stop(seconds int) error {
|
||||
if !container.State.IsRunning() {
|
||||
if !container.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -599,11 +665,11 @@ func (container *Container) Stop(seconds int) error {
|
|||
}
|
||||
|
||||
// 2. Wait for the process to exit on its own
|
||||
if _, err := container.State.WaitStop(time.Duration(seconds) * time.Second); err != nil {
|
||||
if _, err := container.WaitStop(time.Duration(seconds) * time.Second); err != nil {
|
||||
log.Infof("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.ID, seconds)
|
||||
// 3. If it doesn't, then send SIGKILL
|
||||
if err := container.Kill(); err != nil {
|
||||
container.State.WaitStop(-1 * time.Second)
|
||||
container.WaitStop(-1 * time.Second)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -625,7 +691,7 @@ func (container *Container) Restart(seconds int) error {
|
|||
}
|
||||
|
||||
func (container *Container) Resize(h, w int) error {
|
||||
return container.command.Terminal.Resize(h, w)
|
||||
return container.command.ProcessConfig.Terminal.Resize(h, w)
|
||||
}
|
||||
|
||||
func (container *Container) ExportRw() (archive.Archive, error) {
|
||||
|
@ -640,7 +706,7 @@ func (container *Container) ExportRw() (archive.Archive, error) {
|
|||
container.Unmount()
|
||||
return nil, err
|
||||
}
|
||||
return utils.NewReadCloserWrapper(archive, func() error {
|
||||
return ioutils.NewReadCloserWrapper(archive, func() error {
|
||||
err := archive.Close()
|
||||
container.Unmount()
|
||||
return err
|
||||
|
@ -658,7 +724,7 @@ func (container *Container) Export() (archive.Archive, error) {
|
|||
container.Unmount()
|
||||
return nil, err
|
||||
}
|
||||
return utils.NewReadCloserWrapper(archive, func() error {
|
||||
return ioutils.NewReadCloserWrapper(archive, func() error {
|
||||
err := archive.Close()
|
||||
container.Unmount()
|
||||
return err
|
||||
|
@ -670,10 +736,14 @@ func (container *Container) Mount() error {
|
|||
return container.daemon.Mount(container)
|
||||
}
|
||||
|
||||
func (container *Container) changes() ([]archive.Change, error) {
|
||||
return container.daemon.Changes(container)
|
||||
}
|
||||
|
||||
func (container *Container) Changes() ([]archive.Change, error) {
|
||||
container.Lock()
|
||||
defer container.Unlock()
|
||||
return container.daemon.Changes(container)
|
||||
return container.changes()
|
||||
}
|
||||
|
||||
func (container *Container) GetImage() (*image.Image, error) {
|
||||
|
@ -734,21 +804,13 @@ func (container *Container) GetSize() (int64, int64) {
|
|||
}
|
||||
defer container.Unmount()
|
||||
|
||||
if differ, ok := container.daemon.driver.(graphdriver.Differ); ok {
|
||||
sizeRw, err = differ.DiffSize(container.ID)
|
||||
if err != nil {
|
||||
log.Errorf("Warning: driver %s couldn't return diff size of container %s: %s", driver, container.ID, err)
|
||||
// FIXME: GetSize should return an error. Not changing it now in case
|
||||
// there is a side-effect.
|
||||
sizeRw = -1
|
||||
}
|
||||
} else {
|
||||
changes, _ := container.Changes()
|
||||
if changes != nil {
|
||||
sizeRw = archive.ChangesSize(container.basefs, changes)
|
||||
} else {
|
||||
sizeRw = -1
|
||||
}
|
||||
initID := fmt.Sprintf("%s-init", container.ID)
|
||||
sizeRw, err = driver.DiffSize(container.ID, initID)
|
||||
if err != nil {
|
||||
log.Errorf("Warning: driver %s couldn't return diff size of container %s: %s", driver, container.ID, err)
|
||||
// FIXME: GetSize should return an error. Not changing it now in case
|
||||
// there is a side-effect.
|
||||
sizeRw = -1
|
||||
}
|
||||
|
||||
if _, err = os.Stat(container.basefs); err != nil {
|
||||
|
@ -794,7 +856,7 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) {
|
|||
container.Unmount()
|
||||
return nil, err
|
||||
}
|
||||
return utils.NewReadCloserWrapper(archive, func() error {
|
||||
return ioutils.NewReadCloserWrapper(archive, func() error {
|
||||
err := archive.Close()
|
||||
container.Unmount()
|
||||
return err
|
||||
|
@ -809,7 +871,7 @@ func (container *Container) Exposes(p nat.Port) bool {
|
|||
}
|
||||
|
||||
func (container *Container) GetPtyMaster() (*os.File, error) {
|
||||
ttyConsole, ok := container.command.Terminal.(execdriver.TtyTerminal)
|
||||
ttyConsole, ok := container.command.ProcessConfig.Terminal.(execdriver.TtyTerminal)
|
||||
if !ok {
|
||||
return nil, ErrNoTTY
|
||||
}
|
||||
|
@ -858,26 +920,58 @@ func (container *Container) setupContainerDns() error {
|
|||
return err
|
||||
}
|
||||
|
||||
if config.NetworkMode != "host" && (len(config.Dns) > 0 || len(daemon.config.Dns) > 0 || len(config.DnsSearch) > 0 || len(daemon.config.DnsSearch) > 0) {
|
||||
var (
|
||||
dns = resolvconf.GetNameservers(resolvConf)
|
||||
dnsSearch = resolvconf.GetSearchDomains(resolvConf)
|
||||
)
|
||||
if len(config.Dns) > 0 {
|
||||
dns = config.Dns
|
||||
} else if len(daemon.config.Dns) > 0 {
|
||||
dns = daemon.config.Dns
|
||||
if config.NetworkMode != "host" {
|
||||
// check configurations for any container/daemon dns settings
|
||||
if len(config.Dns) > 0 || len(daemon.config.Dns) > 0 || len(config.DnsSearch) > 0 || len(daemon.config.DnsSearch) > 0 {
|
||||
var (
|
||||
dns = resolvconf.GetNameservers(resolvConf)
|
||||
dnsSearch = resolvconf.GetSearchDomains(resolvConf)
|
||||
)
|
||||
if len(config.Dns) > 0 {
|
||||
dns = config.Dns
|
||||
} else if len(daemon.config.Dns) > 0 {
|
||||
dns = daemon.config.Dns
|
||||
}
|
||||
if len(config.DnsSearch) > 0 {
|
||||
dnsSearch = config.DnsSearch
|
||||
} else if len(daemon.config.DnsSearch) > 0 {
|
||||
dnsSearch = daemon.config.DnsSearch
|
||||
}
|
||||
return resolvconf.Build(container.ResolvConfPath, dns, dnsSearch)
|
||||
}
|
||||
if len(config.DnsSearch) > 0 {
|
||||
dnsSearch = config.DnsSearch
|
||||
} else if len(daemon.config.DnsSearch) > 0 {
|
||||
dnsSearch = daemon.config.DnsSearch
|
||||
|
||||
// replace any localhost/127.* nameservers
|
||||
resolvConf = utils.RemoveLocalDns(resolvConf)
|
||||
// if the resulting resolvConf is empty, use DefaultDns
|
||||
if !bytes.Contains(resolvConf, []byte("nameserver")) {
|
||||
log.Infof("No non localhost DNS resolver found in resolv.conf and containers can't use it. Using default external servers : %v", DefaultDns)
|
||||
// prefix the default dns options with nameserver
|
||||
resolvConf = append(resolvConf, []byte("\nnameserver "+strings.Join(DefaultDns, "\nnameserver "))...)
|
||||
}
|
||||
return resolvconf.Build(container.ResolvConfPath, dns, dnsSearch)
|
||||
}
|
||||
return ioutil.WriteFile(container.ResolvConfPath, resolvConf, 0644)
|
||||
}
|
||||
|
||||
func (container *Container) updateParentsHosts() error {
|
||||
parents, err := container.daemon.Parents(container.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, cid := range parents {
|
||||
if cid == "0" {
|
||||
continue
|
||||
}
|
||||
|
||||
c := container.daemon.Get(cid)
|
||||
if c != nil && !container.daemon.config.DisableNetwork && container.hostConfig.NetworkMode.IsPrivate() {
|
||||
if err := etchosts.Update(c.HostsPath, container.NetworkSettings.IPAddress, container.Name[1:]); err != nil {
|
||||
return fmt.Errorf("Failed to update /etc/hosts in parent container: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) initializeNetworking() error {
|
||||
var err error
|
||||
if container.hostConfig.NetworkMode.IsHost() {
|
||||
|
@ -910,7 +1004,8 @@ func (container *Container) initializeNetworking() error {
|
|||
container.HostsPath = hostsPath
|
||||
|
||||
return ioutil.WriteFile(container.HostsPath, content, 0644)
|
||||
} else if container.hostConfig.NetworkMode.IsContainer() {
|
||||
}
|
||||
if container.hostConfig.NetworkMode.IsContainer() {
|
||||
// we need to get the hosts files from the container to join
|
||||
nc, err := container.getNetworkedContainer()
|
||||
if err != nil {
|
||||
|
@ -920,16 +1015,16 @@ func (container *Container) initializeNetworking() error {
|
|||
container.ResolvConfPath = nc.ResolvConfPath
|
||||
container.Config.Hostname = nc.Config.Hostname
|
||||
container.Config.Domainname = nc.Config.Domainname
|
||||
} else if container.daemon.config.DisableNetwork {
|
||||
return nil
|
||||
}
|
||||
if container.daemon.config.DisableNetwork {
|
||||
container.Config.NetworkDisabled = true
|
||||
return container.buildHostnameAndHostsFiles("127.0.1.1")
|
||||
} else {
|
||||
if err := container.allocateNetwork(); err != nil {
|
||||
return err
|
||||
}
|
||||
return container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress)
|
||||
}
|
||||
return nil
|
||||
if err := container.AllocateNetwork(); err != nil {
|
||||
return err
|
||||
}
|
||||
return container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress)
|
||||
}
|
||||
|
||||
// Make sure the config is compatible with the current kernel
|
||||
|
@ -970,7 +1065,7 @@ func (container *Container) setupLinkedContainers() ([]string, error) {
|
|||
}
|
||||
|
||||
for linkAlias, child := range children {
|
||||
if !child.State.IsRunning() {
|
||||
if !child.IsRunning() {
|
||||
return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", child.Name, linkAlias)
|
||||
}
|
||||
|
||||
|
@ -1002,10 +1097,15 @@ func (container *Container) setupLinkedContainers() ([]string, error) {
|
|||
}
|
||||
|
||||
func (container *Container) createDaemonEnvironment(linkedEnv []string) []string {
|
||||
// if a domain name was specified, append it to the hostname (see #7851)
|
||||
fullHostname := container.Config.Hostname
|
||||
if container.Config.Domainname != "" {
|
||||
fullHostname = fmt.Sprintf("%s.%s", fullHostname, container.Config.Domainname)
|
||||
}
|
||||
// Setup environment
|
||||
env := []string{
|
||||
"PATH=" + DefaultPathEnv,
|
||||
"HOSTNAME=" + container.Config.Hostname,
|
||||
"HOSTNAME=" + fullHostname,
|
||||
// Note: we don't set HOME here because it'll get autoset intelligently
|
||||
// based on the value of USER inside dockerinit, but only if it isn't
|
||||
// set already (ie, that can be overridden by setting HOME via -e or ENV
|
||||
|
@ -1074,7 +1174,7 @@ func (container *Container) waitForStart() error {
|
|||
// process or until the process is running in the container
|
||||
select {
|
||||
case <-container.monitor.startSignal:
|
||||
case err := <-utils.Go(container.monitor.Start):
|
||||
case err := <-promise.Go(container.monitor.Start):
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -1101,7 +1201,6 @@ func (container *Container) allocatePort(eng *engine.Engine, port nat.Port, bind
|
|||
return err
|
||||
}
|
||||
if err := job.Run(); err != nil {
|
||||
eng.Job("release_interface", container.ID).Run()
|
||||
return err
|
||||
}
|
||||
b.HostIp = portEnv.Get("HostIP")
|
||||
|
@ -1137,7 +1236,7 @@ func (container *Container) getNetworkedContainer() (*Container, error) {
|
|||
if nc == nil {
|
||||
return nil, fmt.Errorf("no such container to join network: %s", parts[1])
|
||||
}
|
||||
if !nc.State.IsRunning() {
|
||||
if !nc.IsRunning() {
|
||||
return nil, fmt.Errorf("cannot join network of a non running container: %s", parts[1])
|
||||
}
|
||||
return nc, nil
|
||||
|
|
|
@ -178,3 +178,20 @@ func TestGetFullName(t *testing.T) {
|
|||
t.Fatal("Error should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidContainerNames(t *testing.T) {
|
||||
invalidNames := []string{"-rm", "&sdfsfd", "safd%sd"}
|
||||
validNames := []string{"word-word", "word_word", "1weoid"}
|
||||
|
||||
for _, name := range invalidNames {
|
||||
if validContainerNamePattern.MatchString(name) {
|
||||
t.Fatalf("%q is not a valid container name and was returned as valid.", name)
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range validNames {
|
||||
if !validContainerNamePattern.MatchString(name) {
|
||||
t.Fatalf("%q is a valid container name and was returned as invalid.", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,8 @@ func (daemon *Daemon) ContainerCreate(job *engine.Job) engine.Status {
|
|||
return job.Errorf("Usage: %s", job.Name)
|
||||
}
|
||||
config := runconfig.ContainerConfigFromJob(job)
|
||||
if config.Memory != 0 && config.Memory < 524288 {
|
||||
return job.Errorf("Minimum memory limit allowed is 512k")
|
||||
if config.Memory != 0 && config.Memory < 4194304 {
|
||||
return job.Errorf("Minimum memory limit allowed is 4MB")
|
||||
}
|
||||
if config.Memory > 0 && !daemon.SystemConfig().MemoryLimit {
|
||||
job.Errorf("Your kernel does not support memory limit capabilities. Limitation discarded.\n")
|
||||
|
@ -26,7 +26,16 @@ func (daemon *Daemon) ContainerCreate(job *engine.Job) engine.Status {
|
|||
job.Errorf("Your kernel does not support swap limit capabilities. Limitation discarded.\n")
|
||||
config.MemorySwap = -1
|
||||
}
|
||||
container, buildWarnings, err := daemon.Create(config, name)
|
||||
|
||||
var hostConfig *runconfig.HostConfig
|
||||
if job.EnvExists("HostConfig") {
|
||||
hostConfig = runconfig.ContainerHostConfigFromJob(job)
|
||||
} else {
|
||||
// Older versions of the API don't provide a HostConfig.
|
||||
hostConfig = nil
|
||||
}
|
||||
|
||||
container, buildWarnings, err := daemon.Create(config, hostConfig, name)
|
||||
if err != nil {
|
||||
if daemon.Graph().IsNotExist(err) {
|
||||
_, tag := parsers.ParseRepositoryTag(config.Image)
|
||||
|
@ -50,11 +59,12 @@ func (daemon *Daemon) ContainerCreate(job *engine.Job) engine.Status {
|
|||
for _, warning := range buildWarnings {
|
||||
job.Errorf("%s\n", warning)
|
||||
}
|
||||
|
||||
return engine.StatusOK
|
||||
}
|
||||
|
||||
// Create creates a new container from the given configuration with a given name.
|
||||
func (daemon *Daemon) Create(config *runconfig.Config, name string) (*Container, []string, error) {
|
||||
func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.HostConfig, name string) (*Container, []string, error) {
|
||||
var (
|
||||
container *Container
|
||||
warnings []string
|
||||
|
@ -73,14 +83,19 @@ func (daemon *Daemon) Create(config *runconfig.Config, name string) (*Container,
|
|||
if container, err = daemon.newContainer(name, config, img); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := daemon.Register(container); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := daemon.createRootfs(container, img); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if hostConfig != nil {
|
||||
if err := daemon.setHostConfig(container, hostConfig); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
if err := container.ToDisk(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := daemon.Register(container); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return container, warnings, nil
|
||||
}
|
||||
|
|
214
daemon/daemon.go
214
daemon/daemon.go
|
@ -14,7 +14,6 @@ import (
|
|||
|
||||
"github.com/docker/libcontainer/label"
|
||||
|
||||
"github.com/docker/docker/archive"
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/docker/docker/daemon/execdriver/execdrivers"
|
||||
"github.com/docker/docker/daemon/execdriver/lxc"
|
||||
|
@ -26,22 +25,25 @@ import (
|
|||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/graph"
|
||||
"github.com/docker/docker/image"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/broadcastwriter"
|
||||
"github.com/docker/docker/pkg/graphdb"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/pkg/log"
|
||||
"github.com/docker/docker/pkg/namesgenerator"
|
||||
"github.com/docker/docker/pkg/networkfs/resolvconf"
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
"github.com/docker/docker/pkg/parsers/kernel"
|
||||
"github.com/docker/docker/pkg/sysinfo"
|
||||
"github.com/docker/docker/pkg/truncindex"
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/docker/docker/trust"
|
||||
"github.com/docker/docker/utils"
|
||||
"github.com/docker/docker/volumes"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultDns = []string{"8.8.8.8", "8.8.4.4"}
|
||||
validContainerNameChars = `[a-zA-Z0-9_.-]`
|
||||
validContainerNameChars = `[a-zA-Z0-9][a-zA-Z0-9_.-]`
|
||||
validContainerNamePattern = regexp.MustCompile(`^/?` + validContainerNameChars + `+$`)
|
||||
)
|
||||
|
||||
|
@ -84,33 +86,32 @@ type Daemon struct {
|
|||
repository string
|
||||
sysInitPath string
|
||||
containers *contStore
|
||||
execCommands *execStore
|
||||
graph *graph.Graph
|
||||
repositories *graph.TagStore
|
||||
idIndex *truncindex.TruncIndex
|
||||
sysInfo *sysinfo.SysInfo
|
||||
volumes *graph.Graph
|
||||
volumes *volumes.Repository
|
||||
eng *engine.Engine
|
||||
config *Config
|
||||
containerGraph *graphdb.Database
|
||||
driver graphdriver.Driver
|
||||
execDriver execdriver.Driver
|
||||
trustStore *trust.TrustStore
|
||||
}
|
||||
|
||||
// Install installs daemon capabilities to eng.
|
||||
func (daemon *Daemon) Install(eng *engine.Engine) error {
|
||||
// FIXME: rename "delete" to "rm" for consistency with the CLI command
|
||||
// FIXME: rename ContainerDestroy to ContainerRm for consistency with the CLI command
|
||||
// FIXME: remove ImageDelete's dependency on Daemon, then move to graph/
|
||||
for name, method := range map[string]engine.Handler{
|
||||
"attach": daemon.ContainerAttach,
|
||||
"build": daemon.CmdBuild,
|
||||
"commit": daemon.ContainerCommit,
|
||||
"container_changes": daemon.ContainerChanges,
|
||||
"container_copy": daemon.ContainerCopy,
|
||||
"container_inspect": daemon.ContainerInspect,
|
||||
"containers": daemon.Containers,
|
||||
"create": daemon.ContainerCreate,
|
||||
"delete": daemon.ContainerDestroy,
|
||||
"rm": daemon.ContainerRm,
|
||||
"export": daemon.ContainerExport,
|
||||
"info": daemon.CmdInfo,
|
||||
"kill": daemon.ContainerKill,
|
||||
|
@ -124,6 +125,9 @@ func (daemon *Daemon) Install(eng *engine.Engine) error {
|
|||
"unpause": daemon.ContainerUnpause,
|
||||
"wait": daemon.ContainerWait,
|
||||
"image_delete": daemon.ImageDelete, // FIXME: see above
|
||||
"execCreate": daemon.ContainerExecCreate,
|
||||
"execStart": daemon.ContainerExecStart,
|
||||
"execResize": daemon.ContainerExecResize,
|
||||
} {
|
||||
if err := eng.Register(name, method); err != nil {
|
||||
return err
|
||||
|
@ -132,6 +136,9 @@ func (daemon *Daemon) Install(eng *engine.Engine) error {
|
|||
if err := daemon.Repositories().Install(eng); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := daemon.trustStore.Install(eng); err != nil {
|
||||
return err
|
||||
}
|
||||
// FIXME: this hack is necessary for legacy integration tests to access
|
||||
// the daemon object.
|
||||
eng.Hack_SetGlobalVar("httpapi.daemon", daemon)
|
||||
|
@ -163,7 +170,11 @@ func (daemon *Daemon) containerRoot(id string) string {
|
|||
// Load reads the contents of a container from disk
|
||||
// This is typically done at startup.
|
||||
func (daemon *Daemon) load(id string) (*Container, error) {
|
||||
container := &Container{root: daemon.containerRoot(id), State: NewState()}
|
||||
container := &Container{
|
||||
root: daemon.containerRoot(id),
|
||||
State: NewState(),
|
||||
execCommands: newExecStore(),
|
||||
}
|
||||
if err := container.FromDisk(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -204,7 +215,7 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err
|
|||
if container.Config.OpenStdin {
|
||||
container.stdin, container.stdinPipe = io.Pipe()
|
||||
} else {
|
||||
container.stdinPipe = utils.NopWriteCloser(ioutil.Discard) // Silently drop stdin
|
||||
container.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin
|
||||
}
|
||||
// done
|
||||
daemon.containers.Add(container.ID, container)
|
||||
|
@ -216,11 +227,11 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err
|
|||
// FIXME: if the container is supposed to be running but is not, auto restart it?
|
||||
// if so, then we need to restart monitor and init a new lock
|
||||
// If the container is supposed to be running, make sure of it
|
||||
if container.State.IsRunning() {
|
||||
if container.IsRunning() {
|
||||
log.Debugf("killing old running container %s", container.ID)
|
||||
|
||||
existingPid := container.State.Pid
|
||||
container.State.SetStopped(0)
|
||||
existingPid := container.Pid
|
||||
container.SetStopped(0)
|
||||
|
||||
// We only have to handle this for lxc because the other drivers will ensure that
|
||||
// no processes are left when docker dies
|
||||
|
@ -232,7 +243,7 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err
|
|||
ID: container.ID,
|
||||
}
|
||||
var err error
|
||||
cmd.Process, err = os.FindProcess(existingPid)
|
||||
cmd.ProcessConfig.Process, err = os.FindProcess(existingPid)
|
||||
if err != nil {
|
||||
log.Debugf("cannot find existing process for %d", existingPid)
|
||||
}
|
||||
|
@ -252,7 +263,7 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err
|
|||
|
||||
log.Debugf("Marking as stopped")
|
||||
|
||||
container.State.SetStopped(-127)
|
||||
container.SetStopped(-127)
|
||||
if err := container.ToDisk(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -366,7 +377,7 @@ func (daemon *Daemon) restore() error {
|
|||
|
||||
for _, container := range registeredContainers {
|
||||
if container.hostConfig.RestartPolicy.Name == "always" ||
|
||||
(container.hostConfig.RestartPolicy.Name == "on-failure" && container.State.ExitCode != 0) {
|
||||
(container.hostConfig.RestartPolicy.Name == "on-failure" && container.ExitCode != 0) {
|
||||
log.Debugf("Starting container %s", container.ID)
|
||||
|
||||
if err := container.Start(); err != nil {
|
||||
|
@ -376,6 +387,10 @@ func (daemon *Daemon) restore() error {
|
|||
}
|
||||
}
|
||||
|
||||
for _, c := range registeredContainers {
|
||||
c.registerVolumes()
|
||||
}
|
||||
|
||||
if !debug {
|
||||
log.Infof(": done.")
|
||||
}
|
||||
|
@ -498,21 +513,46 @@ func (daemon *Daemon) generateHostname(id string, config *runconfig.Config) {
|
|||
}
|
||||
}
|
||||
|
||||
func (daemon *Daemon) getEntrypointAndArgs(config *runconfig.Config) (string, []string) {
|
||||
func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint, configCmd []string) (string, []string) {
|
||||
var (
|
||||
entrypoint string
|
||||
args []string
|
||||
)
|
||||
if len(config.Entrypoint) != 0 {
|
||||
entrypoint = config.Entrypoint[0]
|
||||
args = append(config.Entrypoint[1:], config.Cmd...)
|
||||
if len(configEntrypoint) != 0 {
|
||||
entrypoint = configEntrypoint[0]
|
||||
args = append(configEntrypoint[1:], configCmd...)
|
||||
} else {
|
||||
entrypoint = config.Cmd[0]
|
||||
args = config.Cmd[1:]
|
||||
entrypoint = configCmd[0]
|
||||
args = configCmd[1:]
|
||||
}
|
||||
return entrypoint, args
|
||||
}
|
||||
|
||||
func parseSecurityOpt(container *Container, config *runconfig.Config) error {
|
||||
var (
|
||||
label_opts []string
|
||||
err error
|
||||
)
|
||||
|
||||
for _, opt := range config.SecurityOpt {
|
||||
con := strings.SplitN(opt, ":", 2)
|
||||
if len(con) == 1 {
|
||||
return fmt.Errorf("Invalid --security-opt: %q", opt)
|
||||
}
|
||||
switch con[0] {
|
||||
case "label":
|
||||
label_opts = append(label_opts, con[1])
|
||||
case "apparmor":
|
||||
container.AppArmorProfile = con[1]
|
||||
default:
|
||||
return fmt.Errorf("Invalid --security-opt: %q", opt)
|
||||
}
|
||||
}
|
||||
|
||||
container.ProcessLabel, container.MountLabel, err = label.InitLabels(label_opts)
|
||||
return err
|
||||
}
|
||||
|
||||
func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *image.Image) (*Container, error) {
|
||||
var (
|
||||
id string
|
||||
|
@ -524,7 +564,7 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *i
|
|||
}
|
||||
|
||||
daemon.generateHostname(id, config)
|
||||
entrypoint, args := daemon.getEntrypointAndArgs(config)
|
||||
entrypoint, args := daemon.getEntrypointAndArgs(config.Entrypoint, config.Cmd)
|
||||
|
||||
container := &Container{
|
||||
// FIXME: we should generate the ID here instead of receiving it as an argument
|
||||
|
@ -540,13 +580,11 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *i
|
|||
Driver: daemon.driver.String(),
|
||||
ExecDriver: daemon.execDriver.Name(),
|
||||
State: NewState(),
|
||||
execCommands: newExecStore(),
|
||||
}
|
||||
container.root = daemon.containerRoot(container.ID)
|
||||
|
||||
if container.ProcessLabel, container.MountLabel, err = label.GenLabels(""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return container, nil
|
||||
err = parseSecurityOpt(container, config)
|
||||
return container, err
|
||||
}
|
||||
|
||||
func (daemon *Daemon) createRootfs(container *Container, img *image.Image) error {
|
||||
|
@ -623,6 +661,15 @@ func (daemon *Daemon) Children(name string) (map[string]*Container, error) {
|
|||
return children, nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) Parents(name string) ([]string, error) {
|
||||
name, err := GetFullContainerName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return daemon.containerGraph.Parents(name)
|
||||
}
|
||||
|
||||
func (daemon *Daemon) RegisterLink(parent, child *Container, alias string) error {
|
||||
fullName := path.Join(parent.Name, alias)
|
||||
if !daemon.containerGraph.Exists(fullName) {
|
||||
|
@ -683,8 +730,10 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
|
|||
if !config.EnableIptables && !config.InterContainerCommunication {
|
||||
return nil, fmt.Errorf("You specified --iptables=false with --icc=false. ICC uses iptables to function. Please set --icc or --iptables to true.")
|
||||
}
|
||||
// FIXME: DisableNetworkBidge doesn't need to be public anymore
|
||||
config.DisableNetwork = config.BridgeIface == DisableNetworkBridge
|
||||
if !config.EnableIptables && config.EnableIpMasq {
|
||||
return nil, fmt.Errorf("You specified --iptables=false with --ipmasq=true. IP masquerading uses iptables to function. Please set --ipmasq to false or --iptables to true.")
|
||||
}
|
||||
config.DisableNetwork = config.BridgeIface == disableNetworkBridge
|
||||
|
||||
// Claim the pidfile first, to avoid any and all unexpected race conditions.
|
||||
// Some of the init doesn't need a pidfile lock - but let's not try to be smart.
|
||||
|
@ -699,25 +748,24 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
|
|||
}
|
||||
|
||||
// Check that the system is supported and we have sufficient privileges
|
||||
// FIXME: return errors instead of calling Fatal
|
||||
if runtime.GOOS != "linux" {
|
||||
log.Fatalf("The Docker daemon is only supported on linux")
|
||||
return nil, fmt.Errorf("The Docker daemon is only supported on linux")
|
||||
}
|
||||
if os.Geteuid() != 0 {
|
||||
log.Fatalf("The Docker daemon needs to be run as root")
|
||||
return nil, fmt.Errorf("The Docker daemon needs to be run as root")
|
||||
}
|
||||
if err := checkKernelAndArch(); err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// set up the TempDir to use a canonical path
|
||||
tmp, err := utils.TempDir(config.Root)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to get the TempDir under %s: %s", config.Root, err)
|
||||
return nil, fmt.Errorf("Unable to get the TempDir under %s: %s", config.Root, err)
|
||||
}
|
||||
realTmp, err := utils.ReadSymlinkedDirectory(tmp)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to get the full path to the TempDir (%s): %s", tmp, err)
|
||||
return nil, fmt.Errorf("Unable to get the full path to the TempDir (%s): %s", tmp, err)
|
||||
}
|
||||
os.Setenv("TMPDIR", realTmp)
|
||||
if !config.EnableSelinuxSupport {
|
||||
|
@ -731,7 +779,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
|
|||
} else {
|
||||
realRoot, err = utils.ReadSymlinkedDirectory(config.Root)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to get the full path to root (%s): %s", config.Root, err)
|
||||
return nil, fmt.Errorf("Unable to get the full path to root (%s): %s", config.Root, err)
|
||||
}
|
||||
}
|
||||
config.Root = realRoot
|
||||
|
@ -751,7 +799,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
|
|||
log.Debugf("Using graph driver %s", driver)
|
||||
|
||||
// As Docker on btrfs and SELinux are incompatible at present, error on both being enabled
|
||||
if config.EnableSelinuxSupport && driver.String() == "btrfs" {
|
||||
if selinuxEnabled() && config.EnableSelinuxSupport && driver.String() == "btrfs" {
|
||||
return nil, fmt.Errorf("SELinux is not supported with the BTRFS graph driver!")
|
||||
}
|
||||
|
||||
|
@ -772,31 +820,41 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// We don't want to use a complex driver like aufs or devmapper
|
||||
// for volumes, just a plain filesystem
|
||||
volumesDriver, err := graphdriver.GetDriver("vfs", config.Root, config.GraphOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("Creating volumes graph")
|
||||
volumes, err := graph.NewGraph(path.Join(config.Root, "volumes"), volumesDriver)
|
||||
|
||||
volumes, err := volumes.NewRepository(path.Join(config.Root, "volumes"), volumesDriver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugf("Creating repository list")
|
||||
repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g)
|
||||
repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g, config.Mirrors)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't create Tag store: %s", err)
|
||||
}
|
||||
|
||||
trustDir := path.Join(config.Root, "trust")
|
||||
if err := os.MkdirAll(trustDir, 0700); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
t, err := trust.NewTrustStore(trustDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create trust store: %s", err)
|
||||
}
|
||||
|
||||
if !config.DisableNetwork {
|
||||
job := eng.Job("init_networkdriver")
|
||||
|
||||
job.SetenvBool("EnableIptables", config.EnableIptables)
|
||||
job.SetenvBool("InterContainerCommunication", config.InterContainerCommunication)
|
||||
job.SetenvBool("EnableIpForward", config.EnableIpForward)
|
||||
job.SetenvBool("EnableIpMasq", config.EnableIpMasq)
|
||||
job.Setenv("BridgeIface", config.BridgeIface)
|
||||
job.Setenv("BridgeIP", config.BridgeIP)
|
||||
job.Setenv("FixedCIDR", config.FixedCIDR)
|
||||
job.Setenv("DefaultBindingIP", config.DefaultIp.String())
|
||||
|
||||
if err := job.Run(); err != nil {
|
||||
|
@ -839,6 +897,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
|
|||
daemon := &Daemon{
|
||||
repository: daemonRepo,
|
||||
containers: &contStore{s: make(map[string]*Container)},
|
||||
execCommands: newExecStore(),
|
||||
graph: g,
|
||||
repositories: repositories,
|
||||
idIndex: truncindex.NewTruncIndex([]string{}),
|
||||
|
@ -850,9 +909,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
|
|||
sysInitPath: sysInitPath,
|
||||
execDriver: ed,
|
||||
eng: eng,
|
||||
}
|
||||
if err := daemon.checkLocaldns(); err != nil {
|
||||
return nil, err
|
||||
trustStore: t,
|
||||
}
|
||||
if err := daemon.restore(); err != nil {
|
||||
return nil, err
|
||||
|
@ -885,7 +942,7 @@ func (daemon *Daemon) shutdown() error {
|
|||
log.Debugf("starting clean shutdown of all containers...")
|
||||
for _, container := range daemon.List() {
|
||||
c := container
|
||||
if c.State.IsRunning() {
|
||||
if c.IsRunning() {
|
||||
log.Debugf("stopping %s", c.ID)
|
||||
group.Add(1)
|
||||
|
||||
|
@ -894,7 +951,7 @@ func (daemon *Daemon) shutdown() error {
|
|||
if err := c.KillSig(15); err != nil {
|
||||
log.Debugf("kill 15 error for %s - %s", c.ID, err)
|
||||
}
|
||||
c.State.WaitStop(-1 * time.Second)
|
||||
c.WaitStop(-1 * time.Second)
|
||||
log.Debugf("container stopped %s", c.ID)
|
||||
}()
|
||||
}
|
||||
|
@ -924,46 +981,13 @@ func (daemon *Daemon) Unmount(container *Container) error {
|
|||
}
|
||||
|
||||
func (daemon *Daemon) Changes(container *Container) ([]archive.Change, error) {
|
||||
if differ, ok := daemon.driver.(graphdriver.Differ); ok {
|
||||
return differ.Changes(container.ID)
|
||||
}
|
||||
cDir, err := daemon.driver.Get(container.ID, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error getting container rootfs %s from driver %s: %s", container.ID, container.daemon.driver, err)
|
||||
}
|
||||
defer daemon.driver.Put(container.ID)
|
||||
initDir, err := daemon.driver.Get(container.ID+"-init", "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error getting container init rootfs %s from driver %s: %s", container.ID, container.daemon.driver, err)
|
||||
}
|
||||
defer daemon.driver.Put(container.ID + "-init")
|
||||
return archive.ChangesDirs(cDir, initDir)
|
||||
initID := fmt.Sprintf("%s-init", container.ID)
|
||||
return daemon.driver.Changes(container.ID, initID)
|
||||
}
|
||||
|
||||
func (daemon *Daemon) Diff(container *Container) (archive.Archive, error) {
|
||||
if differ, ok := daemon.driver.(graphdriver.Differ); ok {
|
||||
return differ.Diff(container.ID)
|
||||
}
|
||||
|
||||
changes, err := daemon.Changes(container)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cDir, err := daemon.driver.Get(container.ID, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error getting container rootfs %s from driver %s: %s", container.ID, container.daemon.driver, err)
|
||||
}
|
||||
|
||||
archive, err := archive.ExportChanges(cDir, changes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return utils.NewReadCloserWrapper(archive, func() error {
|
||||
err := archive.Close()
|
||||
daemon.driver.Put(container.ID)
|
||||
return err
|
||||
}), nil
|
||||
initID := fmt.Sprintf("%s-init", container.ID)
|
||||
return daemon.driver.Diff(container.ID, initID)
|
||||
}
|
||||
|
||||
func (daemon *Daemon) Run(c *Container, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
|
||||
|
@ -974,7 +998,7 @@ func (daemon *Daemon) Pause(c *Container) error {
|
|||
if err := daemon.execDriver.Pause(c.command); err != nil {
|
||||
return err
|
||||
}
|
||||
c.State.SetPaused()
|
||||
c.SetPaused()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -982,7 +1006,7 @@ func (daemon *Daemon) Unpause(c *Container) error {
|
|||
if err := daemon.execDriver.Unpause(c.command); err != nil {
|
||||
return err
|
||||
}
|
||||
c.State.SetUnpaused()
|
||||
c.SetUnpaused()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -1042,26 +1066,10 @@ func (daemon *Daemon) ExecutionDriver() execdriver.Driver {
|
|||
return daemon.execDriver
|
||||
}
|
||||
|
||||
func (daemon *Daemon) Volumes() *graph.Graph {
|
||||
return daemon.volumes
|
||||
}
|
||||
|
||||
func (daemon *Daemon) ContainerGraph() *graphdb.Database {
|
||||
return daemon.containerGraph
|
||||
}
|
||||
|
||||
func (daemon *Daemon) checkLocaldns() error {
|
||||
resolvConf, err := resolvconf.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(daemon.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
|
||||
log.Infof("Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : %v", DefaultDns)
|
||||
daemon.config.Dns = DefaultDns
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) ImageGetCached(imgID string, config *runconfig.Config) (*image.Image, error) {
|
||||
// Retrieve all images
|
||||
images, err := daemon.Graph().Map()
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package daemon
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/runconfig"
|
||||
)
|
||||
|
||||
func TestParseSecurityOpt(t *testing.T) {
|
||||
container := &Container{}
|
||||
config := &runconfig.Config{}
|
||||
|
||||
// test apparmor
|
||||
config.SecurityOpt = []string{"apparmor:test_profile"}
|
||||
if err := parseSecurityOpt(container, config); err != nil {
|
||||
t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
|
||||
}
|
||||
if container.AppArmorProfile != "test_profile" {
|
||||
t.Fatalf("Unexpected AppArmorProfile, expected: \"test_profile\", got %q", container.AppArmorProfile)
|
||||
}
|
||||
|
||||
// test valid label
|
||||
config.SecurityOpt = []string{"label:user:USER"}
|
||||
if err := parseSecurityOpt(container, config); err != nil {
|
||||
t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
|
||||
}
|
||||
|
||||
// test invalid label
|
||||
config.SecurityOpt = []string{"label"}
|
||||
if err := parseSecurityOpt(container, config); err == nil {
|
||||
t.Fatal("Expected parseSecurityOpt error, got nil")
|
||||
}
|
||||
|
||||
// test invalid opt
|
||||
config.SecurityOpt = []string{"test"}
|
||||
if err := parseSecurityOpt(container, config); err == nil {
|
||||
t.Fatal("Expected parseSecurityOpt error, got nil")
|
||||
}
|
||||
}
|
|
@ -4,15 +4,12 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/pkg/log"
|
||||
)
|
||||
|
||||
// FIXME: rename to ContainerRemove for consistency with the CLI command.
|
||||
func (daemon *Daemon) ContainerDestroy(job *engine.Job) engine.Status {
|
||||
func (daemon *Daemon) ContainerRm(job *engine.Job) engine.Status {
|
||||
if len(job.Args) != 1 {
|
||||
return job.Errorf("Not enough arguments. Usage: %s CONTAINER\n", job.Name)
|
||||
}
|
||||
|
@ -22,10 +19,11 @@ func (daemon *Daemon) ContainerDestroy(job *engine.Job) engine.Status {
|
|||
forceRemove := job.GetenvBool("forceRemove")
|
||||
container := daemon.Get(name)
|
||||
|
||||
if container == nil {
|
||||
return job.Errorf("No such container: %s", name)
|
||||
}
|
||||
|
||||
if removeLink {
|
||||
if container == nil {
|
||||
return job.Errorf("No such link: %s", name)
|
||||
}
|
||||
name, err := GetFullContainerName(name)
|
||||
if err != nil {
|
||||
job.Error(err)
|
||||
|
@ -51,7 +49,7 @@ func (daemon *Daemon) ContainerDestroy(job *engine.Job) engine.Status {
|
|||
}
|
||||
|
||||
if container != nil {
|
||||
if container.State.IsRunning() {
|
||||
if container.IsRunning() {
|
||||
if forceRemove {
|
||||
if err := container.Kill(); err != nil {
|
||||
return job.Errorf("Could not kill running container, cannot remove - %v", err)
|
||||
|
@ -64,73 +62,22 @@ func (daemon *Daemon) ContainerDestroy(job *engine.Job) engine.Status {
|
|||
return job.Errorf("Cannot destroy container %s: %s", name, err)
|
||||
}
|
||||
container.LogEvent("destroy")
|
||||
|
||||
if removeVolume {
|
||||
var (
|
||||
volumes = make(map[string]struct{})
|
||||
binds = make(map[string]struct{})
|
||||
usedVolumes = make(map[string]*Container)
|
||||
)
|
||||
|
||||
// the volume id is always the base of the path
|
||||
getVolumeId := func(p string) string {
|
||||
return filepath.Base(strings.TrimSuffix(p, "/layer"))
|
||||
}
|
||||
|
||||
// populate bind map so that they can be skipped and not removed
|
||||
for _, bind := range container.HostConfig().Binds {
|
||||
source := strings.Split(bind, ":")[0]
|
||||
// TODO: refactor all volume stuff, all of it
|
||||
// it is very important that we eval the link or comparing the keys to container.Volumes will not work
|
||||
//
|
||||
// eval symlink can fail, ref #5244 if we receive an is not exist error we can ignore it
|
||||
p, err := filepath.EvalSymlinks(source)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return job.Error(err)
|
||||
}
|
||||
if p != "" {
|
||||
source = p
|
||||
}
|
||||
binds[source] = struct{}{}
|
||||
}
|
||||
|
||||
// Store all the deleted containers volumes
|
||||
for _, volumeId := range container.Volumes {
|
||||
// Skip the volumes mounted from external
|
||||
// bind mounts here will will be evaluated for a symlink
|
||||
if _, exists := binds[volumeId]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
volumeId = getVolumeId(volumeId)
|
||||
volumes[volumeId] = struct{}{}
|
||||
}
|
||||
|
||||
// Retrieve all volumes from all remaining containers
|
||||
for _, container := range daemon.List() {
|
||||
for _, containerVolumeId := range container.Volumes {
|
||||
containerVolumeId = getVolumeId(containerVolumeId)
|
||||
usedVolumes[containerVolumeId] = container
|
||||
}
|
||||
}
|
||||
|
||||
for volumeId := range volumes {
|
||||
// If the requested volu
|
||||
if c, exists := usedVolumes[volumeId]; exists {
|
||||
log.Infof("The volume %s is used by the container %s. Impossible to remove it. Skipping.", volumeId, c.ID)
|
||||
continue
|
||||
}
|
||||
if err := daemon.Volumes().Delete(volumeId); err != nil {
|
||||
return job.Errorf("Error calling volumes.Delete(%q): %v", volumeId, err)
|
||||
}
|
||||
}
|
||||
daemon.DeleteVolumes(container.VolumePaths())
|
||||
}
|
||||
} else {
|
||||
return job.Errorf("No such container: %s", name)
|
||||
}
|
||||
return engine.StatusOK
|
||||
}
|
||||
|
||||
func (daemon *Daemon) DeleteVolumes(volumeIDs map[string]struct{}) {
|
||||
for id := range volumeIDs {
|
||||
if err := daemon.volumes.Delete(id); err != nil {
|
||||
log.Infof("%s", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy unregisters a container from the daemon and cleanly removes its contents from the filesystem.
|
||||
// FIXME: rename to Rm for consistency with the CLI command
|
||||
func (daemon *Daemon) Destroy(container *Container) error {
|
||||
|
@ -150,7 +97,7 @@ func (daemon *Daemon) Destroy(container *Container) error {
|
|||
// Deregister the container before removing its directory, to avoid race conditions
|
||||
daemon.idIndex.Delete(container.ID)
|
||||
daemon.containers.Delete(container.ID)
|
||||
|
||||
container.derefVolumes()
|
||||
if _, err := daemon.containerGraph.Purge(container.ID); err != nil {
|
||||
log.Debugf("Unable to remove container from link graph: %s", err)
|
||||
}
|
||||
|
@ -168,6 +115,10 @@ func (daemon *Daemon) Destroy(container *Container) error {
|
|||
return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err)
|
||||
}
|
||||
|
||||
if err := daemon.execDriver.Clean(container.ID); err != nil {
|
||||
return fmt.Errorf("Unable to remove execdriver data for %s: %s", container.ID, err)
|
||||
}
|
||||
|
||||
selinuxFreeLxcContexts(container.ProcessLabel)
|
||||
|
||||
return nil
|
||||
|
|
|
@ -0,0 +1,301 @@
|
|||
// build linux
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/docker/docker/daemon/execdriver/lxc"
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/pkg/broadcastwriter"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/pkg/log"
|
||||
"github.com/docker/docker/pkg/promise"
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/docker/docker/utils"
|
||||
)
|
||||
|
||||
type execConfig struct {
|
||||
sync.Mutex
|
||||
ID string
|
||||
Running bool
|
||||
ProcessConfig execdriver.ProcessConfig
|
||||
StreamConfig
|
||||
OpenStdin bool
|
||||
OpenStderr bool
|
||||
OpenStdout bool
|
||||
Container *Container
|
||||
}
|
||||
|
||||
type execStore struct {
|
||||
s map[string]*execConfig
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func newExecStore() *execStore {
|
||||
return &execStore{s: make(map[string]*execConfig, 0)}
|
||||
}
|
||||
|
||||
func (e *execStore) Add(id string, execConfig *execConfig) {
|
||||
e.Lock()
|
||||
e.s[id] = execConfig
|
||||
e.Unlock()
|
||||
}
|
||||
|
||||
func (e *execStore) Get(id string) *execConfig {
|
||||
e.Lock()
|
||||
res := e.s[id]
|
||||
e.Unlock()
|
||||
return res
|
||||
}
|
||||
|
||||
func (e *execStore) Delete(id string) {
|
||||
e.Lock()
|
||||
delete(e.s, id)
|
||||
e.Unlock()
|
||||
}
|
||||
|
||||
func (execConfig *execConfig) Resize(h, w int) error {
|
||||
return execConfig.ProcessConfig.Terminal.Resize(h, w)
|
||||
}
|
||||
|
||||
func (d *Daemon) registerExecCommand(execConfig *execConfig) {
|
||||
// Storing execs in container inorder to kill them gracefully whenever the container is stopped or removed.
|
||||
execConfig.Container.execCommands.Add(execConfig.ID, execConfig)
|
||||
// Storing execs in daemon for easy access via remote API.
|
||||
d.execCommands.Add(execConfig.ID, execConfig)
|
||||
}
|
||||
|
||||
func (d *Daemon) getExecConfig(name string) (*execConfig, error) {
|
||||
if execConfig := d.execCommands.Get(name); execConfig != nil {
|
||||
if !execConfig.Container.IsRunning() {
|
||||
return nil, fmt.Errorf("Container %s is not running", execConfig.Container.ID)
|
||||
}
|
||||
return execConfig, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("No such exec instance '%s' found in daemon", name)
|
||||
}
|
||||
|
||||
func (d *Daemon) unregisterExecCommand(execConfig *execConfig) {
|
||||
execConfig.Container.execCommands.Delete(execConfig.ID)
|
||||
d.execCommands.Delete(execConfig.ID)
|
||||
}
|
||||
|
||||
func (d *Daemon) getActiveContainer(name string) (*Container, error) {
|
||||
container := d.Get(name)
|
||||
|
||||
if container == nil {
|
||||
return nil, fmt.Errorf("No such container: %s", name)
|
||||
}
|
||||
|
||||
if !container.IsRunning() {
|
||||
return nil, fmt.Errorf("Container %s is not running", name)
|
||||
}
|
||||
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func (d *Daemon) ContainerExecCreate(job *engine.Job) engine.Status {
|
||||
if len(job.Args) != 1 {
|
||||
return job.Errorf("Usage: %s [options] container command [args]", job.Name)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(d.execDriver.Name(), lxc.DriverName) {
|
||||
return job.Error(lxc.ErrExec)
|
||||
}
|
||||
|
||||
var name = job.Args[0]
|
||||
|
||||
container, err := d.getActiveContainer(name)
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
|
||||
config := runconfig.ExecConfigFromJob(job)
|
||||
|
||||
entrypoint, args := d.getEntrypointAndArgs(nil, config.Cmd)
|
||||
|
||||
processConfig := execdriver.ProcessConfig{
|
||||
Privileged: config.Privileged,
|
||||
User: config.User,
|
||||
Tty: config.Tty,
|
||||
Entrypoint: entrypoint,
|
||||
Arguments: args,
|
||||
}
|
||||
|
||||
execConfig := &execConfig{
|
||||
ID: utils.GenerateRandomID(),
|
||||
OpenStdin: config.AttachStdin,
|
||||
OpenStdout: config.AttachStdout,
|
||||
OpenStderr: config.AttachStderr,
|
||||
StreamConfig: StreamConfig{},
|
||||
ProcessConfig: processConfig,
|
||||
Container: container,
|
||||
Running: false,
|
||||
}
|
||||
|
||||
d.registerExecCommand(execConfig)
|
||||
|
||||
job.Printf("%s\n", execConfig.ID)
|
||||
|
||||
return engine.StatusOK
|
||||
}
|
||||
|
||||
func (d *Daemon) ContainerExecStart(job *engine.Job) engine.Status {
|
||||
if len(job.Args) != 1 {
|
||||
return job.Errorf("Usage: %s [options] exec", job.Name)
|
||||
}
|
||||
|
||||
var (
|
||||
cStdin io.ReadCloser
|
||||
cStdout, cStderr io.Writer
|
||||
cStdinCloser io.Closer
|
||||
execName = job.Args[0]
|
||||
)
|
||||
|
||||
execConfig, err := d.getExecConfig(execName)
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
|
||||
func() {
|
||||
execConfig.Lock()
|
||||
defer execConfig.Unlock()
|
||||
if execConfig.Running {
|
||||
err = fmt.Errorf("Error: Exec command %s is already running", execName)
|
||||
}
|
||||
execConfig.Running = true
|
||||
}()
|
||||
if err != nil {
|
||||
return job.Error(err)
|
||||
}
|
||||
|
||||
log.Debugf("starting exec command %s in container %s", execConfig.ID, execConfig.Container.ID)
|
||||
container := execConfig.Container
|
||||
|
||||
if execConfig.OpenStdin {
|
||||
r, w := io.Pipe()
|
||||
go func() {
|
||||
defer w.Close()
|
||||
io.Copy(w, job.Stdin)
|
||||
}()
|
||||
cStdin = r
|
||||
cStdinCloser = job.Stdin
|
||||
}
|
||||
if execConfig.OpenStdout {
|
||||
cStdout = job.Stdout
|
||||
}
|
||||
if execConfig.OpenStderr {
|
||||
cStderr = job.Stderr
|
||||
}
|
||||
|
||||
execConfig.StreamConfig.stderr = broadcastwriter.New()
|
||||
execConfig.StreamConfig.stdout = broadcastwriter.New()
|
||||
// Attach to stdin
|
||||
if execConfig.OpenStdin {
|
||||
execConfig.StreamConfig.stdin, execConfig.StreamConfig.stdinPipe = io.Pipe()
|
||||
} else {
|
||||
execConfig.StreamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin
|
||||
}
|
||||
|
||||
attachErr := d.Attach(&execConfig.StreamConfig, execConfig.OpenStdin, false, execConfig.ProcessConfig.Tty, cStdin, cStdinCloser, cStdout, cStderr)
|
||||
|
||||
execErr := make(chan error)
|
||||
|
||||
// Remove exec from daemon and container.
|
||||
defer d.unregisterExecCommand(execConfig)
|
||||
|
||||
go func() {
|
||||
err := container.Exec(execConfig)
|
||||
if err != nil {
|
||||
execErr <- fmt.Errorf("Cannot run exec command %s in container %s: %s", execName, container.ID, err)
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-attachErr:
|
||||
if err != nil {
|
||||
return job.Errorf("attach failed with error: %s", err)
|
||||
}
|
||||
break
|
||||
case err := <-execErr:
|
||||
return job.Error(err)
|
||||
}
|
||||
|
||||
return engine.StatusOK
|
||||
}
|
||||
|
||||
func (d *Daemon) Exec(c *Container, execConfig *execConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
|
||||
return d.execDriver.Exec(c.command, &execConfig.ProcessConfig, pipes, startCallback)
|
||||
}
|
||||
|
||||
func (container *Container) Exec(execConfig *execConfig) error {
|
||||
container.Lock()
|
||||
defer container.Unlock()
|
||||
|
||||
waitStart := make(chan struct{})
|
||||
|
||||
callback := func(processConfig *execdriver.ProcessConfig, pid int) {
|
||||
if processConfig.Tty {
|
||||
// The callback is called after the process Start()
|
||||
// so we are in the parent process. In TTY mode, stdin/out/err is the PtySlave
|
||||
// which we close here.
|
||||
if c, ok := processConfig.Stdout.(io.Closer); ok {
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
close(waitStart)
|
||||
}
|
||||
|
||||
// We use a callback here instead of a goroutine and an chan for
|
||||
// syncronization purposes
|
||||
cErr := promise.Go(func() error { return container.monitorExec(execConfig, callback) })
|
||||
|
||||
// Exec should not return until the process is actually running
|
||||
select {
|
||||
case <-waitStart:
|
||||
case err := <-cErr:
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (container *Container) monitorExec(execConfig *execConfig, callback execdriver.StartCallback) error {
|
||||
var (
|
||||
err error
|
||||
exitCode int
|
||||
)
|
||||
|
||||
pipes := execdriver.NewPipes(execConfig.StreamConfig.stdin, execConfig.StreamConfig.stdout, execConfig.StreamConfig.stderr, execConfig.OpenStdin)
|
||||
exitCode, err = container.daemon.Exec(container, execConfig, pipes, callback)
|
||||
if err != nil {
|
||||
log.Errorf("Error running command in existing container %s: %s", container.ID, err)
|
||||
}
|
||||
|
||||
log.Debugf("Exec task in container %s exited with code %d", container.ID, exitCode)
|
||||
if execConfig.OpenStdin {
|
||||
if err := execConfig.StreamConfig.stdin.Close(); err != nil {
|
||||
log.Errorf("Error closing stdin while running in %s: %s", container.ID, err)
|
||||
}
|
||||
}
|
||||
if err := execConfig.StreamConfig.stdout.Clean(); err != nil {
|
||||
log.Errorf("Error closing stdout while running in %s: %s", container.ID, err)
|
||||
}
|
||||
if err := execConfig.StreamConfig.stderr.Clean(); err != nil {
|
||||
log.Errorf("Error closing stderr while running in %s: %s", container.ID, err)
|
||||
}
|
||||
if execConfig.ProcessConfig.Terminal != nil {
|
||||
if err := execConfig.ProcessConfig.Terminal.Close(); err != nil {
|
||||
log.Errorf("Error closing terminal while running in container %s: %s", container.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
|
@ -20,7 +20,7 @@ var (
|
|||
ErrDriverNotFound = errors.New("The requested docker init has not been found")
|
||||
)
|
||||
|
||||
type StartCallback func(*Command)
|
||||
type StartCallback func(*ProcessConfig, int)
|
||||
|
||||
// Driver specific information based on
|
||||
// processes registered with the driver
|
||||
|
@ -42,6 +42,8 @@ type TtyTerminal interface {
|
|||
|
||||
type Driver interface {
|
||||
Run(c *Command, pipes *Pipes, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code
|
||||
// Exec executes the process in an existing container, blocks until the process exits and returns the exit code
|
||||
Exec(c *Command, processConfig *ProcessConfig, pipes *Pipes, startCallback StartCallback) (int, error)
|
||||
Kill(c *Command, sig int) error
|
||||
Pause(c *Command) error
|
||||
Unpause(c *Command) error
|
||||
|
@ -49,6 +51,7 @@ type Driver interface {
|
|||
Info(id string) Info // "temporary" hack (until we move state from core to plugins)
|
||||
GetPidsForContainer(id string) ([]int, error) // Returns a list of pids for the given container.
|
||||
Terminate(c *Command) error // kill it with fire
|
||||
Clean(id string) error // clean all traces of container exec
|
||||
}
|
||||
|
||||
// Network settings of the container
|
||||
|
@ -62,8 +65,9 @@ type Network struct {
|
|||
type NetworkInterface struct {
|
||||
Gateway string `json:"gateway"`
|
||||
IPAddress string `json:"ip"`
|
||||
Bridge string `json:"bridge"`
|
||||
IPPrefixLen int `json:"ip_prefix_len"`
|
||||
MacAddress string `json:"mac_address"`
|
||||
Bridge string `json:"bridge"`
|
||||
}
|
||||
|
||||
type Resources struct {
|
||||
|
@ -78,38 +82,40 @@ type Mount struct {
|
|||
Destination string `json:"destination"`
|
||||
Writable bool `json:"writable"`
|
||||
Private bool `json:"private"`
|
||||
Slave bool `json:"slave"`
|
||||
}
|
||||
|
||||
// Describes a process that will be run inside a container.
|
||||
type ProcessConfig struct {
|
||||
exec.Cmd `json:"-"`
|
||||
|
||||
Privileged bool `json:"privileged"`
|
||||
User string `json:"user"`
|
||||
Tty bool `json:"tty"`
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
Arguments []string `json:"arguments"`
|
||||
Terminal Terminal `json:"-"` // standard or tty terminal
|
||||
Console string `json:"-"` // dev/console path
|
||||
}
|
||||
|
||||
// Process wrapps an os/exec.Cmd to add more metadata
|
||||
type Command struct {
|
||||
exec.Cmd `json:"-"`
|
||||
|
||||
ID string `json:"id"`
|
||||
Privileged bool `json:"privileged"`
|
||||
User string `json:"user"`
|
||||
Rootfs string `json:"rootfs"` // root fs of the container
|
||||
InitPath string `json:"initpath"` // dockerinit
|
||||
Entrypoint string `json:"entrypoint"`
|
||||
Arguments []string `json:"arguments"`
|
||||
WorkingDir string `json:"working_dir"`
|
||||
ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver
|
||||
Tty bool `json:"tty"`
|
||||
Network *Network `json:"network"`
|
||||
Config map[string][]string `json:"config"` // generic values that specific drivers can consume
|
||||
Resources *Resources `json:"resources"`
|
||||
Mounts []Mount `json:"mounts"`
|
||||
AllowedDevices []*devices.Device `json:"allowed_devices"`
|
||||
AutoCreatedDevices []*devices.Device `json:"autocreated_devices"`
|
||||
CapAdd []string `json:"cap_add"`
|
||||
CapDrop []string `json:"cap_drop"`
|
||||
|
||||
Terminal Terminal `json:"-"` // standard or tty terminal
|
||||
Console string `json:"-"` // dev/console path
|
||||
ContainerPid int `json:"container_pid"` // the pid for the process inside a container
|
||||
}
|
||||
|
||||
// Return the pid of the process
|
||||
// If the process is nil -1 will be returned
|
||||
func (c *Command) Pid() int {
|
||||
return c.ContainerPid
|
||||
ID string `json:"id"`
|
||||
Rootfs string `json:"rootfs"` // root fs of the container
|
||||
InitPath string `json:"initpath"` // dockerinit
|
||||
WorkingDir string `json:"working_dir"`
|
||||
ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver
|
||||
Network *Network `json:"network"`
|
||||
Resources *Resources `json:"resources"`
|
||||
Mounts []Mount `json:"mounts"`
|
||||
AllowedDevices []*devices.Device `json:"allowed_devices"`
|
||||
AutoCreatedDevices []*devices.Device `json:"autocreated_devices"`
|
||||
CapAdd []string `json:"cap_add"`
|
||||
CapDrop []string `json:"cap_drop"`
|
||||
ContainerPid int `json:"container_pid"` // the pid for the process inside a container
|
||||
ProcessConfig ProcessConfig `json:"process_config"` // Describes the init process of the container.
|
||||
ProcessLabel string `json:"process_label"`
|
||||
MountLabel string `json:"mount_label"`
|
||||
LxcConfig []string `json:"lxc_config"`
|
||||
AppArmorProfile string `json:"apparmor_profile"`
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package lxc
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -21,12 +22,13 @@ import (
|
|||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/docker/utils"
|
||||
"github.com/docker/libcontainer/cgroups"
|
||||
"github.com/docker/libcontainer/label"
|
||||
"github.com/docker/libcontainer/mount/nodes"
|
||||
)
|
||||
|
||||
const DriverName = "lxc"
|
||||
|
||||
var ErrExec = errors.New("Unsupported: Exec is not supported by the lxc driver")
|
||||
|
||||
type driver struct {
|
||||
root string // root path for the driver to use
|
||||
initPath string
|
||||
|
@ -59,12 +61,12 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
|||
err error
|
||||
)
|
||||
|
||||
if c.Tty {
|
||||
term, err = NewTtyConsole(c, pipes)
|
||||
if c.ProcessConfig.Tty {
|
||||
term, err = NewTtyConsole(&c.ProcessConfig, pipes)
|
||||
} else {
|
||||
term, err = execdriver.NewStdConsole(c, pipes)
|
||||
term, err = execdriver.NewStdConsole(&c.ProcessConfig, pipes)
|
||||
}
|
||||
c.Terminal = term
|
||||
c.ProcessConfig.Terminal = term
|
||||
|
||||
c.Mounts = append(c.Mounts, execdriver.Mount{
|
||||
Source: d.initPath,
|
||||
|
@ -98,11 +100,11 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
|||
"-mtu", strconv.Itoa(c.Network.Mtu),
|
||||
)
|
||||
|
||||
if c.User != "" {
|
||||
params = append(params, "-u", c.User)
|
||||
if c.ProcessConfig.User != "" {
|
||||
params = append(params, "-u", c.ProcessConfig.User)
|
||||
}
|
||||
|
||||
if c.Privileged {
|
||||
if c.ProcessConfig.Privileged {
|
||||
if d.apparmor {
|
||||
params[0] = path.Join(d.root, "lxc-start-unconfined")
|
||||
|
||||
|
@ -122,8 +124,8 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
|||
params = append(params, fmt.Sprintf("-cap-drop=%s", strings.Join(c.CapDrop, ":")))
|
||||
}
|
||||
|
||||
params = append(params, "--", c.Entrypoint)
|
||||
params = append(params, c.Arguments...)
|
||||
params = append(params, "--", c.ProcessConfig.Entrypoint)
|
||||
params = append(params, c.ProcessConfig.Arguments...)
|
||||
|
||||
if d.sharedRoot {
|
||||
// lxc-start really needs / to be non-shared, or all kinds of stuff break
|
||||
|
@ -149,14 +151,14 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
|||
if err != nil {
|
||||
aname = name
|
||||
}
|
||||
c.Path = aname
|
||||
c.Args = append([]string{name}, arg...)
|
||||
c.ProcessConfig.Path = aname
|
||||
c.ProcessConfig.Args = append([]string{name}, arg...)
|
||||
|
||||
if err := nodes.CreateDeviceNodes(c.Rootfs, c.AutoCreatedDevices); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
if err := c.Start(); err != nil {
|
||||
if err := c.ProcessConfig.Start(); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
|
@ -166,7 +168,7 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
|||
)
|
||||
|
||||
go func() {
|
||||
if err := c.Wait(); err != nil {
|
||||
if err := c.ProcessConfig.Wait(); err != nil {
|
||||
if _, ok := err.(*exec.ExitError); !ok { // Do not propagate the error if it's simply a status code != 0
|
||||
waitErr = err
|
||||
}
|
||||
|
@ -177,9 +179,9 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
|||
// Poll lxc for RUNNING status
|
||||
pid, err := d.waitForStart(c, waitLock)
|
||||
if err != nil {
|
||||
if c.Process != nil {
|
||||
c.Process.Kill()
|
||||
c.Wait()
|
||||
if c.ProcessConfig.Process != nil {
|
||||
c.ProcessConfig.Process.Kill()
|
||||
c.ProcessConfig.Wait()
|
||||
}
|
||||
return -1, err
|
||||
}
|
||||
|
@ -187,7 +189,7 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
|||
c.ContainerPid = pid
|
||||
|
||||
if startCallback != nil {
|
||||
startCallback(c)
|
||||
startCallback(&c.ProcessConfig, pid)
|
||||
}
|
||||
|
||||
<-waitLock
|
||||
|
@ -198,10 +200,10 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba
|
|||
/// Return the exit code of the process
|
||||
// if the process has not exited -1 will be returned
|
||||
func getExitCode(c *execdriver.Command) int {
|
||||
if c.ProcessState == nil {
|
||||
if c.ProcessConfig.ProcessState == nil {
|
||||
return -1
|
||||
}
|
||||
return c.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
|
||||
return c.ProcessConfig.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
|
||||
}
|
||||
|
||||
func (d *driver) Kill(c *execdriver.Command, sig int) error {
|
||||
|
@ -407,42 +409,29 @@ func rootIsShared() bool {
|
|||
}
|
||||
|
||||
func (d *driver) generateLXCConfig(c *execdriver.Command) (string, error) {
|
||||
var (
|
||||
process, mount string
|
||||
root = path.Join(d.root, "containers", c.ID, "config.lxc")
|
||||
labels = c.Config["label"]
|
||||
)
|
||||
root := path.Join(d.root, "containers", c.ID, "config.lxc")
|
||||
|
||||
fo, err := os.Create(root)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer fo.Close()
|
||||
|
||||
if len(labels) > 0 {
|
||||
process, mount, err = label.GenLabels(labels[0])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if err := LxcTemplateCompiled.Execute(fo, struct {
|
||||
*execdriver.Command
|
||||
AppArmor bool
|
||||
ProcessLabel string
|
||||
MountLabel string
|
||||
AppArmor bool
|
||||
}{
|
||||
Command: c,
|
||||
AppArmor: d.apparmor,
|
||||
ProcessLabel: process,
|
||||
MountLabel: mount,
|
||||
Command: c,
|
||||
AppArmor: d.apparmor,
|
||||
}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return root, nil
|
||||
}
|
||||
|
||||
func (d *driver) generateEnvConfig(c *execdriver.Command) error {
|
||||
data, err := json.Marshal(c.Env)
|
||||
data, err := json.Marshal(c.ProcessConfig.Env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -457,12 +446,17 @@ func (d *driver) generateEnvConfig(c *execdriver.Command) error {
|
|||
return ioutil.WriteFile(p, data, 0600)
|
||||
}
|
||||
|
||||
// Clean not implemented for lxc
|
||||
func (d *driver) Clean(id string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type TtyConsole struct {
|
||||
MasterPty *os.File
|
||||
SlavePty *os.File
|
||||
}
|
||||
|
||||
func NewTtyConsole(command *execdriver.Command, pipes *execdriver.Pipes) (*TtyConsole, error) {
|
||||
func NewTtyConsole(processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes) (*TtyConsole, error) {
|
||||
// lxc is special in that we cannot create the master outside of the container without
|
||||
// opening the slave because we have nothing to provide to the cmd. We have to open both then do
|
||||
// the crazy setup on command right now instead of passing the console path to lxc and telling it
|
||||
|
@ -478,12 +472,12 @@ func NewTtyConsole(command *execdriver.Command, pipes *execdriver.Pipes) (*TtyCo
|
|||
SlavePty: ptySlave,
|
||||
}
|
||||
|
||||
if err := tty.AttachPipes(&command.Cmd, pipes); err != nil {
|
||||
if err := tty.AttachPipes(&processConfig.Cmd, pipes); err != nil {
|
||||
tty.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
command.Console = tty.SlavePty.Name()
|
||||
processConfig.Console = tty.SlavePty.Name()
|
||||
|
||||
return tty, nil
|
||||
}
|
||||
|
@ -527,3 +521,7 @@ func (t *TtyConsole) Close() error {
|
|||
t.SlavePty.Close()
|
||||
return t.MasterPty.Close()
|
||||
}
|
||||
|
||||
func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
|
||||
return -1, ErrExec
|
||||
}
|
||||
|
|
|
@ -34,15 +34,11 @@ lxc.pts = 1024
|
|||
|
||||
# disable the main console
|
||||
lxc.console = none
|
||||
{{if .ProcessLabel}}
|
||||
lxc.se_context = {{ .ProcessLabel}}
|
||||
{{end}}
|
||||
{{$MOUNTLABEL := .MountLabel}}
|
||||
|
||||
# no controlling tty at all
|
||||
lxc.tty = 1
|
||||
|
||||
{{if .Privileged}}
|
||||
{{if .ProcessConfig.Privileged}}
|
||||
lxc.cgroup.devices.allow = a
|
||||
{{else}}
|
||||
# no implicit access to devices
|
||||
|
@ -66,12 +62,12 @@ lxc.pivotdir = lxc_putold
|
|||
lxc.mount.entry = proc {{escapeFstabSpaces $ROOTFS}}/proc proc nosuid,nodev,noexec 0 0
|
||||
lxc.mount.entry = sysfs {{escapeFstabSpaces $ROOTFS}}/sys sysfs nosuid,nodev,noexec 0 0
|
||||
|
||||
{{if .Tty}}
|
||||
lxc.mount.entry = {{.Console}} {{escapeFstabSpaces $ROOTFS}}/dev/console none bind,rw 0 0
|
||||
{{if .ProcessConfig.Tty}}
|
||||
lxc.mount.entry = {{.ProcessConfig.Console}} {{escapeFstabSpaces $ROOTFS}}/dev/console none bind,rw 0 0
|
||||
{{end}}
|
||||
|
||||
lxc.mount.entry = devpts {{escapeFstabSpaces $ROOTFS}}/dev/pts devpts {{formatMountLabel "newinstance,ptmxmode=0666,nosuid,noexec" $MOUNTLABEL}} 0 0
|
||||
lxc.mount.entry = shm {{escapeFstabSpaces $ROOTFS}}/dev/shm tmpfs {{formatMountLabel "size=65536k,nosuid,nodev,noexec" $MOUNTLABEL}} 0 0
|
||||
lxc.mount.entry = devpts {{escapeFstabSpaces $ROOTFS}}/dev/pts devpts {{formatMountLabel "newinstance,ptmxmode=0666,nosuid,noexec" ""}} 0 0
|
||||
lxc.mount.entry = shm {{escapeFstabSpaces $ROOTFS}}/dev/shm tmpfs {{formatMountLabel "size=65536k,nosuid,nodev,noexec" ""}} 0 0
|
||||
|
||||
{{range $value := .Mounts}}
|
||||
{{if $value.Writable}}
|
||||
|
@ -81,7 +77,7 @@ lxc.mount.entry = {{$value.Source}} {{escapeFstabSpaces $ROOTFS}}/{{escapeFstabS
|
|||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{if .Privileged}}
|
||||
{{if .ProcessConfig.Privileged}}
|
||||
{{if .AppArmor}}
|
||||
lxc.aa_profile = unconfined
|
||||
{{else}}
|
||||
|
@ -106,8 +102,8 @@ lxc.cgroup.cpuset.cpus = {{.Resources.Cpuset}}
|
|||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{if .Config.lxc}}
|
||||
{{range $value := .Config.lxc}}
|
||||
{{if .LxcConfig}}
|
||||
{{range $value := .LxcConfig}}
|
||||
lxc.{{$value}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
|
|
@ -52,6 +52,7 @@ func TestLXCConfig(t *testing.T) {
|
|||
Interface: nil,
|
||||
},
|
||||
AllowedDevices: make([]*devices.Device, 0),
|
||||
ProcessConfig: execdriver.ProcessConfig{},
|
||||
}
|
||||
p, err := driver.generateLXCConfig(command)
|
||||
if err != nil {
|
||||
|
@ -77,19 +78,20 @@ func TestCustomLxcConfig(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
command := &execdriver.Command{
|
||||
ID: "1",
|
||||
processConfig := execdriver.ProcessConfig{
|
||||
Privileged: false,
|
||||
Config: map[string][]string{
|
||||
"lxc": {
|
||||
"lxc.utsname = docker",
|
||||
"lxc.cgroup.cpuset.cpus = 0,1",
|
||||
},
|
||||
}
|
||||
command := &execdriver.Command{
|
||||
ID: "1",
|
||||
LxcConfig: []string{
|
||||
"lxc.utsname = docker",
|
||||
"lxc.cgroup.cpuset.cpus = 0,1",
|
||||
},
|
||||
Network: &execdriver.Network{
|
||||
Mtu: 1500,
|
||||
Interface: nil,
|
||||
},
|
||||
ProcessConfig: processConfig,
|
||||
}
|
||||
|
||||
p, err := driver.generateLXCConfig(command)
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче