Commit 37945771 authored by k8s-merge-robot's avatar k8s-merge-robot

Merge pull request #19478 from mfojtik/go-dockerclient-bump

Auto commit by PR queue bot
parents 7d02cf63 118c8c56
...@@ -402,7 +402,7 @@ ...@@ -402,7 +402,7 @@
{ {
"ImportPath": "github.com/fsouza/go-dockerclient", "ImportPath": "github.com/fsouza/go-dockerclient",
"Comment": "0.2.1-728-g1399676", "Comment": "0.2.1-728-g1399676",
"Rev": "1399676f53e6ccf46e0bf00751b21bed329bc60e" "Rev": "299d728486342c894e7fafd68e3a4b89623bef1d"
}, },
{ {
"ImportPath": "github.com/garyburd/redigo/internal", "ImportPath": "github.com/garyburd/redigo/internal",
......
language: go language: go
sudo: false sudo: required
go: go:
- 1.3.3 - 1.3.3
- 1.4.2 - 1.4.2
- 1.5.1 - 1.5.2
- 1.6beta1
- tip - tip
env: env:
- GOARCH=amd64 - GOARCH=amd64 DOCKER_VERSION=1.7.1
- GOARCH=386 - GOARCH=386 DOCKER_VERSION=1.7.1
- GOARCH=amd64 DOCKER_VERSION=1.8.3
- GOARCH=386 DOCKER_VERSION=1.8.3
- GOARCH=amd64 DOCKER_VERSION=1.9.1
- GOARCH=386 DOCKER_VERSION=1.9.1
install:
- make prepare_docker
script: script:
- make test - make test
- DOCKER_HOST=tcp://127.0.0.1:2375 make integration
services:
- docker
# This is the official list of go-dockerclient authors for copyright purposes. # This is the official list of go-dockerclient authors for copyright purposes.
Abhishek Chanda <abhishek.becs@gmail.com>
Adam Bell-Hanssen <adamb@aller.no> Adam Bell-Hanssen <adamb@aller.no>
Adrien Kohlbecker <adrien.kohlbecker@gmail.com> Adrien Kohlbecker <adrien.kohlbecker@gmail.com>
Aldrin Leal <aldrin@leal.eng.br> Aldrin Leal <aldrin@leal.eng.br>
Andreas Jaekle <andreas@jaekle.net> Andreas Jaekle <andreas@jaekle.net>
Andrews Medina <andrewsmedina@gmail.com> Andrews Medina <andrewsmedina@gmail.com>
Artem Sidorenko <artem@2realities.com> Andrey Sibiryov <kobolog@uber.com>
Andy Goldstein <andy.goldstein@redhat.com> Andy Goldstein <andy.goldstein@redhat.com>
Antonio Murdaca <runcom@redhat.com>
Artem Sidorenko <artem@2realities.com>
Ben Marini <ben@remind101.com> Ben Marini <ben@remind101.com>
Ben McCann <benmccann.com> Ben McCann <benmccann.com>
Benno van den Berg <bennovandenberg@gmail.com>
Brendan Fosberry <brendan@codeship.com> Brendan Fosberry <brendan@codeship.com>
Brian Lalor <blalor@bravo5.org> Brian Lalor <blalor@bravo5.org>
Brian P. Hamachek <brian@brianhama.com>
Brian Palmer <brianp@instructure.com> Brian Palmer <brianp@instructure.com>
Bryan Boreham <bjboreham@gmail.com> Bryan Boreham <bjboreham@gmail.com>
Burke Libbey <burke@libbey.me> Burke Libbey <burke@libbey.me>
...@@ -19,17 +24,21 @@ Cesar Wong <cewong@redhat.com> ...@@ -19,17 +24,21 @@ Cesar Wong <cewong@redhat.com>
Cezar Sa Espinola <cezar.sa@corp.globo.com> Cezar Sa Espinola <cezar.sa@corp.globo.com>
Cheah Chu Yeow <chuyeow@gmail.com> Cheah Chu Yeow <chuyeow@gmail.com>
cheneydeng <cheneydeng@qq.com> cheneydeng <cheneydeng@qq.com>
Chris Bednarski <banzaimonkey@gmail.com>
CMGS <ilskdw@gmail.com> CMGS <ilskdw@gmail.com>
Colin Hebert <hebert.colin@gmail.com>
Craig Jellick <craig@rancher.com> Craig Jellick <craig@rancher.com>
Dan Williams <dcbw@redhat.com> Dan Williams <dcbw@redhat.com>
Daniel, Dao Quang Minh <dqminh89@gmail.com> Daniel, Dao Quang Minh <dqminh89@gmail.com>
Daniel Garcia <daniel@danielgarcia.info> Daniel Garcia <daniel@danielgarcia.info>
Daniel Hiltgen <daniel.hiltgen@docker.com>
Darren Shepherd <darren@rancher.com> Darren Shepherd <darren@rancher.com>
Dave Choi <dave.choi@daumkakao.com> Dave Choi <dave.choi@daumkakao.com>
David Huie <dahuie@gmail.com> David Huie <dahuie@gmail.com>
Dawn Chen <dawnchen@google.com> Dawn Chen <dawnchen@google.com>
Dinesh Subhraveti <dinesh@gemini-systems.net> Dinesh Subhraveti <dinesh@gemini-systems.net>
Ed <edrocksit@gmail.com> Ed <edrocksit@gmail.com>
Elias G. Schneevoigt <eliasgs@gmail.com>
Erez Horev <erez.horev@elastifile.com> Erez Horev <erez.horev@elastifile.com>
Eric Anderson <anderson@copperegg.com> Eric Anderson <anderson@copperegg.com>
Ewout Prangsma <ewout@prangsma.net> Ewout Prangsma <ewout@prangsma.net>
...@@ -42,6 +51,7 @@ Guillermo Álvarez Fernández <guillermo@cientifico.net> ...@@ -42,6 +51,7 @@ Guillermo Álvarez Fernández <guillermo@cientifico.net>
He Simei <hesimei@zju.edu.cn> He Simei <hesimei@zju.edu.cn>
Ivan Mikushin <i.mikushin@gmail.com> Ivan Mikushin <i.mikushin@gmail.com>
James Bardin <jbardin@litl.com> James Bardin <jbardin@litl.com>
James Nugent <james@jen20.com>
Jari Kolehmainen <jari.kolehmainen@digia.com> Jari Kolehmainen <jari.kolehmainen@digia.com>
Jason Wilder <jwilder@litl.com> Jason Wilder <jwilder@litl.com>
Jawher Moussa <jawher.moussa@gmail.com> Jawher Moussa <jawher.moussa@gmail.com>
...@@ -49,22 +59,29 @@ Jean-Baptiste Dalido <jeanbaptiste@appgratis.com> ...@@ -49,22 +59,29 @@ Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
Jeff Mitchell <jeffrey.mitchell@gmail.com> Jeff Mitchell <jeffrey.mitchell@gmail.com>
Jeffrey Hulten <jhulten@gmail.com> Jeffrey Hulten <jhulten@gmail.com>
Jen Andre <jandre@gmail.com> Jen Andre <jandre@gmail.com>
Jérôme Laurens <jeromelaurens@gmail.com>
Johan Euphrosine <proppy@google.com> Johan Euphrosine <proppy@google.com>
John Hughes <hughesj@visa.com>
Kamil Domanski <kamil@domanski.co> Kamil Domanski <kamil@domanski.co>
Karan Misra <kidoman@gmail.com> Karan Misra <kidoman@gmail.com>
Ken Herner <chosenken@gmail.com>
Kim, Hirokuni <hirokuni.kim@kvh.co.jp> Kim, Hirokuni <hirokuni.kim@kvh.co.jp>
Kyle Allan <kallan357@gmail.com> Kyle Allan <kallan357@gmail.com>
Liron Levin <levinlir@gmail.com> Liron Levin <levinlir@gmail.com>
Lior Yankovich <lior@twistlock.com>
Liu Peng <vslene@gmail.com> Liu Peng <vslene@gmail.com>
Lorenz Leutgeb <lorenz.leutgeb@gmail.com> Lorenz Leutgeb <lorenz.leutgeb@gmail.com>
Lucas Clemente <lucas@clemente.io> Lucas Clemente <lucas@clemente.io>
Lucas Weiblen <lucasweiblen@gmail.com> Lucas Weiblen <lucasweiblen@gmail.com>
Lyon Hill <lyondhill@gmail.com>
Mantas Matelis <mmatelis@coursera.org> Mantas Matelis <mmatelis@coursera.org>
Martin Sweeney <martin@sweeney.io> Martin Sweeney <martin@sweeney.io>
Máximo Cuadros Ortiz <mcuadros@gmail.com> Máximo Cuadros Ortiz <mcuadros@gmail.com>
Michael Schmatz <michaelschmatz@gmail.com>
Michal Fojtik <mfojtik@redhat.com> Michal Fojtik <mfojtik@redhat.com>
Mike Dillon <mike.dillon@synctree.com> Mike Dillon <mike.dillon@synctree.com>
Mrunal Patel <mrunalp@gmail.com> Mrunal Patel <mrunalp@gmail.com>
Nguyen Sy Thanh Son <sonnst@sigma-solutions.eu>
Nick Ethier <ncethier@gmail.com> Nick Ethier <ncethier@gmail.com>
Omeid Matten <public@omeid.me> Omeid Matten <public@omeid.me>
Orivej Desh <orivej@gmx.fr> Orivej Desh <orivej@gmx.fr>
...@@ -93,10 +110,12 @@ Sunjin Lee <styner32@gmail.com> ...@@ -93,10 +110,12 @@ Sunjin Lee <styner32@gmail.com>
Tarsis Azevedo <tarsis@corp.globo.com> Tarsis Azevedo <tarsis@corp.globo.com>
Tim Schindler <tim@catalyst-zero.com> Tim Schindler <tim@catalyst-zero.com>
Tobi Knaup <tobi@mesosphere.io> Tobi Knaup <tobi@mesosphere.io>
Tom Wilkie <tom.wilkie@gmail.com>
Tonic <tonicbupt@gmail.com> Tonic <tonicbupt@gmail.com>
ttyh061 <ttyh061@gmail.com> ttyh061 <ttyh061@gmail.com>
Victor Marmol <vmarmol@google.com> Victor Marmol <vmarmol@google.com>
Vincenzo Prignano <vincenzo.prignano@gmail.com> Vincenzo Prignano <vincenzo.prignano@gmail.com>
Wiliam Souza <wiliamsouza83@gmail.com> Wiliam Souza <wiliamsouza83@gmail.com>
Ye Yin <eyniy@qq.com> Ye Yin <eyniy@qq.com>
Yu, Zou <zouyu7@huawei.com>
Yuriy Bogdanov <chinsay@gmail.com> Yuriy Bogdanov <chinsay@gmail.com>
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
fmtcheck \ fmtcheck \
pretest \ pretest \
test \ test \
integration \
cov \ cov \
clean clean
...@@ -31,13 +32,25 @@ fmt: ...@@ -31,13 +32,25 @@ fmt:
gofmt -w $(SRCS) gofmt -w $(SRCS)
fmtcheck: fmtcheck:
$(foreach file,$(SRCS),gofmt $(file) | diff -u $(file) - || exit;) $(foreach file,$(SRCS),gofmt -d $(file);)
prepare_docker:
sudo stop docker
sudo rm -rf /var/lib/docker
sudo rm -f `which docker`
sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
echo "deb https://apt.dockerproject.org/repo ubuntu-trusty main" | sudo tee /etc/apt/sources.list.d/docker.list
sudo apt-get update
sudo apt-get install docker-engine=$(DOCKER_VERSION)-0~$(shell lsb_release -cs) -y --force-yes
pretest: lint vet fmtcheck pretest: lint vet fmtcheck
test: pretest test: pretest
$(foreach pkg,$(PKGS),go test $(pkg) || exit;) $(foreach pkg,$(PKGS),go test $(pkg) || exit;)
integration:
go test -tags docker_integration -run TestIntegration -v
cov: cov:
@ go get -v github.com/axw/gocov/gocov @ go get -v github.com/axw/gocov/gocov
@ go get golang.org/x/tools/cmd/cover @ go get golang.org/x/tools/cmd/cover
......
# go-dockerclient # go-dockerclient
[![Drone](https://drone.io/github.com/fsouza/go-dockerclient/status.png)](https://drone.io/github.com/fsouza/go-dockerclient/latest)
[![Travis](https://img.shields.io/travis/fsouza/go-dockerclient.svg?style=flat-square)](https://travis-ci.org/fsouza/go-dockerclient) [![Travis](https://img.shields.io/travis/fsouza/go-dockerclient.svg?style=flat-square)](https://travis-ci.org/fsouza/go-dockerclient)
[![GoDoc](https://img.shields.io/badge/api-Godoc-blue.svg?style=flat-square)](https://godoc.org/github.com/fsouza/go-dockerclient) [![GoDoc](https://img.shields.io/badge/api-Godoc-blue.svg?style=flat-square)](https://godoc.org/github.com/fsouza/go-dockerclient)
...@@ -12,7 +11,7 @@ passthrough to the libnetwork remote API. Note that docker's network API is ...@@ -12,7 +11,7 @@ passthrough to the libnetwork remote API. Note that docker's network API is
only available in docker 1.8 and above, and only enabled in docker if only available in docker 1.8 and above, and only enabled in docker if
DOCKER_EXPERIMENTAL is defined during the docker build process. DOCKER_EXPERIMENTAL is defined during the docker build process.
For more details, check the [remote API documentation](http://docs.docker.com/en/latest/reference/api/docker_remote_api/). For more details, check the [remote API documentation](http://docs.docker.com/engine/reference/api/docker_remote_api/).
## Vendoring ## Vendoring
......
...@@ -106,7 +106,7 @@ func authConfigs(confs map[string]dockerConfig) (*AuthConfigurations, error) { ...@@ -106,7 +106,7 @@ func authConfigs(confs map[string]dockerConfig) (*AuthConfigurations, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
userpass := strings.Split(string(data), ":") userpass := strings.SplitN(string(data), ":", 2)
if len(userpass) != 2 { if len(userpass) != 2 {
return nil, ErrCannotParseDockercfg return nil, ErrCannotParseDockercfg
} }
......
...@@ -83,8 +83,24 @@ type StartExecOptions struct { ...@@ -83,8 +83,24 @@ type StartExecOptions struct {
// //
// See https://goo.gl/iQCnto for more details // See https://goo.gl/iQCnto for more details
func (c *Client) StartExec(id string, opts StartExecOptions) error { func (c *Client) StartExec(id string, opts StartExecOptions) error {
cw, err := c.StartExecNonBlocking(id, opts)
if err != nil {
return err
}
if cw != nil {
return cw.Wait()
}
return nil
}
// StartExecNonBlocking starts a previously set up exec instance id. If opts.Detach is
// true, it returns after starting the exec command. Otherwise, it sets up an
// interactive session with the exec command.
//
// See https://goo.gl/iQCnto for more details
func (c *Client) StartExecNonBlocking(id string, opts StartExecOptions) (CloseWaiter, error) {
if id == "" { if id == "" {
return &NoSuchExec{ID: id} return nil, &NoSuchExec{ID: id}
} }
path := fmt.Sprintf("/exec/%s/start", id) path := fmt.Sprintf("/exec/%s/start", id)
...@@ -93,12 +109,12 @@ func (c *Client) StartExec(id string, opts StartExecOptions) error { ...@@ -93,12 +109,12 @@ func (c *Client) StartExec(id string, opts StartExecOptions) error {
resp, err := c.do("POST", path, doOptions{data: opts}) resp, err := c.do("POST", path, doOptions{data: opts})
if err != nil { if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return &NoSuchExec{ID: id} return nil, &NoSuchExec{ID: id}
} }
return err return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
return nil return nil, nil
} }
return c.hijack("POST", path, hijackOptions{ return c.hijack("POST", path, hijackOptions{
......
# (Unreleased) # 0.9.0 (Unreleased)
logrus/core: improve performance of text formatter by 40% * logrus/text_formatter: don't emit empty msg
logrus/core: expose `LevelHooks` type * logrus/hooks/airbrake: move out of main repository
* logrus/hooks/sentry: move out of main repository
* logrus/hooks/papertrail: move out of main repository
* logrus/hooks/bugsnag: move out of main repository
# 0.8.7
* logrus/core: fix possible race (#216)
* logrus/doc: small typo fixes and doc improvements
# 0.8.6
* hooks/raven: allow passing an initialized client
# 0.8.5
* logrus/core: revert #208
# 0.8.4
* formatter/text: fix data race (#218)
# 0.8.3
* logrus/core: fix entry log level (#208)
* logrus/core: improve performance of text formatter by 40%
* logrus/core: expose `LevelHooks` type
* logrus/core: add support for DragonflyBSD and NetBSD
* formatter/text: print structs more verbosely
# 0.8.2 # 0.8.2
logrus: fix more Fatal family functions * logrus: fix more Fatal family functions
# 0.8.1 # 0.8.1
logrus: fix not exiting on `Fatalf` and `Fatalln` * logrus: fix not exiting on `Fatalf` and `Fatalln`
# 0.8.0 # 0.8.0
logrus: defaults to stderr instead of stdout * logrus: defaults to stderr instead of stdout
hooks/sentry: add special field for `*http.Request` * hooks/sentry: add special field for `*http.Request`
formatter/text: ignore Windows for colors * formatter/text: ignore Windows for colors
# 0.7.3 # 0.7.3
formatter/\*: allow configuration of timestamp layout * formatter/\*: allow configuration of timestamp layout
# 0.7.2 # 0.7.2
formatter/text: Add configuration option for time format (#158) * formatter/text: Add configuration option for time format (#158)
...@@ -75,17 +75,12 @@ package main ...@@ -75,17 +75,12 @@ package main
import ( import (
"os" "os"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/Sirupsen/logrus/hooks/airbrake"
) )
func init() { func init() {
// Log as JSON instead of the default ASCII formatter. // Log as JSON instead of the default ASCII formatter.
log.SetFormatter(&log.JSONFormatter{}) log.SetFormatter(&log.JSONFormatter{})
// Use the Airbrake hook to report errors that have Error severity or above to
// an exception tracker. You can create custom hooks, see the Hooks section.
log.AddHook(airbrake.NewHook("https://example.com", "xyz", "development"))
// Output to stderr instead of stdout, could also be a file. // Output to stderr instead of stdout, could also be a file.
log.SetOutput(os.Stderr) log.SetOutput(os.Stderr)
...@@ -182,13 +177,16 @@ Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in ...@@ -182,13 +177,16 @@ Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
```go ```go
import ( import (
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/Sirupsen/logrus/hooks/airbrake" "gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "aibrake"
logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog" logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog"
"log/syslog" "log/syslog"
) )
func init() { func init() {
log.AddHook(airbrake.NewHook("https://example.com", "xyz", "development"))
// Use the Airbrake hook to report errors that have Error severity or above to
// an exception tracker. You can create custom hooks, see the Hooks section.
log.AddHook(airbrake.NewHook(123, "xyz", "production"))
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
if err != nil { if err != nil {
...@@ -198,25 +196,31 @@ func init() { ...@@ -198,25 +196,31 @@ func init() {
} }
} }
``` ```
Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md).
| Hook | Description | | Hook | Description |
| ----- | ----------- | | ----- | ----------- |
| [Airbrake](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go) | Send errors to an exception tracking service compatible with the Airbrake API. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. | | [Airbrake](https://github.com/gemnasium/logrus-airbrake-hook) | Send errors to the Airbrake API V3. Uses the official [`gobrake`](https://github.com/airbrake/gobrake) behind the scenes. |
| [Papertrail](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go) | Send errors to the Papertrail hosted logging service via UDP. | | [Airbrake "legacy"](https://github.com/gemnasium/logrus-airbrake-legacy-hook) | Send errors to an exception tracking service compatible with the Airbrake API V2. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. |
| [Papertrail](https://github.com/polds/logrus-papertrail-hook) | Send errors to the [Papertrail](https://papertrailapp.com) hosted logging service via UDP. |
| [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. | | [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. |
| [BugSnag](https://github.com/Sirupsen/logrus/blob/master/hooks/bugsnag/bugsnag.go) | Send errors to the Bugsnag exception tracking service. | | [Bugsnag](https://github.com/Shopify/logrus-bugsnag/blob/master/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
| [Sentry](https://github.com/Sirupsen/logrus/blob/master/hooks/sentry/sentry.go) | Send errors to the Sentry error logging and aggregation service. | | [Sentry](https://github.com/evalphobia/logrus_sentry) | Send errors to the Sentry error logging and aggregation service. |
| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. | | [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. |
| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) | | [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. | | [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` | | [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` |
| [Graylog](https://github.com/gemnasium/logrus-hooks/tree/master/graylog) | Hook for logging to [Graylog](http://graylog2.org/) | | [Graylog](https://github.com/gemnasium/logrus-graylog-hook) | Hook for logging to [Graylog](http://graylog2.org/) |
| [Raygun](https://github.com/squirkle/logrus-raygun-hook) | Hook for logging to [Raygun.io](http://raygun.io/) | | [Raygun](https://github.com/squirkle/logrus-raygun-hook) | Hook for logging to [Raygun.io](http://raygun.io/) |
| [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem | | [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem |
| [Honeybadger](https://github.com/agonzalezro/logrus_honeybadger) | Hook for sending exceptions to Honeybadger | | [Honeybadger](https://github.com/agonzalezro/logrus_honeybadger) | Hook for sending exceptions to Honeybadger |
| [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail | | [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail |
| [Rollrus](https://github.com/heroku/rollrus) | Hook for sending errors to rollbar | | [Rollrus](https://github.com/heroku/rollrus) | Hook for sending errors to rollbar |
| [Fluentd](https://github.com/evalphobia/logrus_fluent) | Hook for logging to fluentd |
| [Mongodb](https://github.com/weekface/mgorus) | Hook for logging to mongodb |
| [InfluxDB](https://github.com/Abramovic/logrus_influxdb) | Hook for logging to influxdb |
| [Octokit](https://github.com/dorajistyle/logrus-octokit-hook) | Hook for logging to github via octokit |
| [DeferPanic](https://github.com/deferpanic/dp-logrus) | Hook for logging to DeferPanic |
#### Level logging #### Level logging
...@@ -272,7 +276,7 @@ init() { ...@@ -272,7 +276,7 @@ init() {
// do something here to set environment depending on an environment variable // do something here to set environment depending on an environment variable
// or command-line flag // or command-line flag
if Environment == "production" { if Environment == "production" {
log.SetFormatter(&logrus.JSONFormatter{}) log.SetFormatter(&log.JSONFormatter{})
} else { } else {
// The TextFormatter is default, you don't actually have to do this. // The TextFormatter is default, you don't actually have to do this.
log.SetFormatter(&log.TextFormatter{}) log.SetFormatter(&log.TextFormatter{})
...@@ -294,15 +298,16 @@ The built-in logging formatters are: ...@@ -294,15 +298,16 @@ The built-in logging formatters are:
field to `true`. To force no colored output even if there is a TTY set the field to `true`. To force no colored output even if there is a TTY set the
`DisableColors` field to `true` `DisableColors` field to `true`
* `logrus.JSONFormatter`. Logs fields as JSON. * `logrus.JSONFormatter`. Logs fields as JSON.
* `logrus_logstash.LogstashFormatter`. Logs fields as Logstash Events (http://logstash.net). * `logrus/formatters/logstash.LogstashFormatter`. Logs fields as [Logstash](http://logstash.net) Events.
```go ```go
logrus.SetFormatter(&logrus_logstash.LogstashFormatter{Type: “application_name"}) logrus.SetFormatter(&logstash.LogstashFormatter{Type: "application_name"})
``` ```
Third party logging formatters: Third party logging formatters:
* [`zalgo`](https://github.com/aybabtme/logzalgo): invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦. * [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
You can define your formatter by implementing the `Formatter` interface, You can define your formatter by implementing the `Formatter` interface,
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
...@@ -315,7 +320,7 @@ type MyJSONFormatter struct { ...@@ -315,7 +320,7 @@ type MyJSONFormatter struct {
log.SetFormatter(new(MyJSONFormatter)) log.SetFormatter(new(MyJSONFormatter))
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
// Note this doesn't include Time, Level and Message which are available on // Note this doesn't include Time, Level and Message which are available on
// the Entry. Consult `godoc` on information about those fields or read the // the Entry. Consult `godoc` on information about those fields or read the
// source of the official loggers. // source of the official loggers.
...@@ -351,5 +356,10 @@ Log rotation is not provided with Logrus. Log rotation should be done by an ...@@ -351,5 +356,10 @@ Log rotation is not provided with Logrus. Log rotation should be done by an
external program (like `logrotate(8)`) that can compress and delete old log external program (like `logrotate(8)`) that can compress and delete old log
entries. It should not be a feature of the application-level logger. entries. It should not be a feature of the application-level logger.
#### Tools
| Tool | Description |
| ---- | ----------- |
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will generated with different config at different environment.|
[godoc]: https://godoc.org/github.com/Sirupsen/logrus [godoc]: https://godoc.org/github.com/Sirupsen/logrus
/*
Package logrus is a structured logger for Go, completely API compatible with the standard library logger.
The simplest way to use Logrus is simply the package-level exported logger:
package main
import (
log "github.com/Sirupsen/logrus"
)
func main() {
log.WithFields(log.Fields{
"animal": "walrus",
"number": 1,
"size": 10,
}).Info("A walrus appears")
}
Output:
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
For a full guide visit https://github.com/Sirupsen/logrus
*/
package logrus
...@@ -8,6 +8,9 @@ import ( ...@@ -8,6 +8,9 @@ import (
"time" "time"
) )
// Defines the key when adding errors using WithError.
var ErrorKey = "error"
// An entry is the final or intermediate Logrus logging entry. It contains all // An entry is the final or intermediate Logrus logging entry. It contains all
// the fields passed with WithField{,s}. It's finally logged when Debug, Info, // the fields passed with WithField{,s}. It's finally logged when Debug, Info,
// Warn, Error, Fatal or Panic is called on it. These objects can be reused and // Warn, Error, Fatal or Panic is called on it. These objects can be reused and
...@@ -53,6 +56,11 @@ func (entry *Entry) String() (string, error) { ...@@ -53,6 +56,11 @@ func (entry *Entry) String() (string, error) {
return reader.String(), err return reader.String(), err
} }
// Add an error as single field (using the key defined in ErrorKey) to the Entry.
func (entry *Entry) WithError(err error) *Entry {
return entry.WithField(ErrorKey, err)
}
// Add a single field to the Entry. // Add a single field to the Entry.
func (entry *Entry) WithField(key string, value interface{}) *Entry { func (entry *Entry) WithField(key string, value interface{}) *Entry {
return entry.WithFields(Fields{key: value}) return entry.WithFields(Fields{key: value})
...@@ -70,12 +78,14 @@ func (entry *Entry) WithFields(fields Fields) *Entry { ...@@ -70,12 +78,14 @@ func (entry *Entry) WithFields(fields Fields) *Entry {
return &Entry{Logger: entry.Logger, Data: data} return &Entry{Logger: entry.Logger, Data: data}
} }
func (entry *Entry) log(level Level, msg string) { // This function is not declared with a pointer value because otherwise
// race conditions will occur when using multiple goroutines
func (entry Entry) log(level Level, msg string) {
entry.Time = time.Now() entry.Time = time.Now()
entry.Level = level entry.Level = level
entry.Message = msg entry.Message = msg
if err := entry.Logger.Hooks.Fire(level, entry); err != nil { if err := entry.Logger.Hooks.Fire(level, &entry); err != nil {
entry.Logger.mu.Lock() entry.Logger.mu.Lock()
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err) fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
entry.Logger.mu.Unlock() entry.Logger.mu.Unlock()
...@@ -100,7 +110,7 @@ func (entry *Entry) log(level Level, msg string) { ...@@ -100,7 +110,7 @@ func (entry *Entry) log(level Level, msg string) {
// panic() to use in Entry#Panic(), we avoid the allocation by checking // panic() to use in Entry#Panic(), we avoid the allocation by checking
// directly here. // directly here.
if level <= PanicLevel { if level <= PanicLevel {
panic(entry) panic(&entry)
} }
} }
......
...@@ -48,6 +48,11 @@ func AddHook(hook Hook) { ...@@ -48,6 +48,11 @@ func AddHook(hook Hook) {
std.Hooks.Add(hook) std.Hooks.Add(hook)
} }
// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
func WithError(err error) *Entry {
return std.WithField(ErrorKey, err)
}
// WithField creates an entry from the standard logger and adds a field to // WithField creates an entry from the standard logger and adds a field to
// it. If you want multiple fields, use `WithFields`. // it. If you want multiple fields, use `WithFields`.
// //
......
...@@ -8,7 +8,7 @@ import ( ...@@ -8,7 +8,7 @@ import (
type Logger struct { type Logger struct {
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a // The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
// file, or leave it default which is `os.Stdout`. You can also set this to // file, or leave it default which is `os.Stderr`. You can also set this to
// something more adventorous, such as logging to Kafka. // something more adventorous, such as logging to Kafka.
Out io.Writer Out io.Writer
// Hooks for the logger instance. These allow firing events based on logging // Hooks for the logger instance. These allow firing events based on logging
...@@ -53,7 +53,7 @@ func New() *Logger { ...@@ -53,7 +53,7 @@ func New() *Logger {
// Adds a field to the log entry, note that you it doesn't log until you call // Adds a field to the log entry, note that you it doesn't log until you call
// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry. // Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry.
// Ff you want multiple fields, use `WithFields`. // If you want multiple fields, use `WithFields`.
func (logger *Logger) WithField(key string, value interface{}) *Entry { func (logger *Logger) WithField(key string, value interface{}) *Entry {
return NewEntry(logger).WithField(key, value) return NewEntry(logger).WithField(key, value)
} }
...@@ -64,6 +64,12 @@ func (logger *Logger) WithFields(fields Fields) *Entry { ...@@ -64,6 +64,12 @@ func (logger *Logger) WithFields(fields Fields) *Entry {
return NewEntry(logger).WithFields(fields) return NewEntry(logger).WithFields(fields)
} }
// Add an error as single field to the log entry. All it does is call
// `WithError` for the given `error`.
func (logger *Logger) WithError(err error) *Entry {
return NewEntry(logger).WithError(err)
}
func (logger *Logger) Debugf(format string, args ...interface{}) { func (logger *Logger) Debugf(format string, args ...interface{}) {
if logger.Level >= DebugLevel { if logger.Level >= DebugLevel {
NewEntry(logger).Debugf(format, args...) NewEntry(logger).Debugf(format, args...)
......
...@@ -74,7 +74,11 @@ const ( ...@@ -74,7 +74,11 @@ const (
) )
// Won't compile if StdLogger can't be realized by a log.Logger // Won't compile if StdLogger can't be realized by a log.Logger
var _ StdLogger = &log.Logger{} var (
_ StdLogger = &log.Logger{}
_ StdLogger = &Entry{}
_ StdLogger = &Logger{}
)
// StdLogger is what your logrus-enabled library should take, that way // StdLogger is what your logrus-enabled library should take, that way
// it'll accept a stdlib logger and a logrus logger. There's no standard // it'll accept a stdlib logger and a logrus logger. There's no standard
......
/*
Go 1.2 doesn't include Termios for FreeBSD. This should be added in 1.3 and this could be merged with terminal_darwin.
*/
package logrus
import (
"syscall"
)
const ioctlReadTermios = syscall.TIOCGETA
type Termios struct {
Iflag uint32
Oflag uint32
Cflag uint32
Lflag uint32
Cc [20]uint8
Ispeed uint32
Ospeed uint32
}
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build linux darwin freebsd openbsd // +build linux darwin freebsd openbsd netbsd dragonfly
package logrus package logrus
...@@ -12,9 +12,9 @@ import ( ...@@ -12,9 +12,9 @@ import (
"unsafe" "unsafe"
) )
// IsTerminal returns true if the given file descriptor is a terminal. // IsTerminal returns true if stderr's file descriptor is a terminal.
func IsTerminal() bool { func IsTerminal() bool {
fd := syscall.Stdout fd := syscall.Stderr
var termios Termios var termios Termios
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
return err == 0 return err == 0
......
package logrus
import "syscall"
const ioctlReadTermios = syscall.TIOCGETA
type Termios syscall.Termios
// +build solaris
package logrus
import (
"os"
"github.com/fsouza/go-dockerclient/external/golang.org/x/sys/unix"
)
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal() bool {
_, err := unix.IoctlGetTermios(int(os.Stdout.Fd()), unix.TCGETA)
return err == nil
}
...@@ -18,9 +18,9 @@ var ( ...@@ -18,9 +18,9 @@ var (
procGetConsoleMode = kernel32.NewProc("GetConsoleMode") procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
) )
// IsTerminal returns true if the given file descriptor is a terminal. // IsTerminal returns true if stderr's file descriptor is a terminal.
func IsTerminal() bool { func IsTerminal() bool {
fd := syscall.Stdout fd := syscall.Stderr
var st uint32 var st uint32
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
return r != 0 && e == 0 return r != 0 && e == 0
......
...@@ -73,17 +73,20 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { ...@@ -73,17 +73,20 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
isColorTerminal := isTerminal && (runtime.GOOS != "windows") isColorTerminal := isTerminal && (runtime.GOOS != "windows")
isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors
if f.TimestampFormat == "" { timestampFormat := f.TimestampFormat
f.TimestampFormat = DefaultTimestampFormat if timestampFormat == "" {
timestampFormat = DefaultTimestampFormat
} }
if isColored { if isColored {
f.printColored(b, entry, keys) f.printColored(b, entry, keys, timestampFormat)
} else { } else {
if !f.DisableTimestamp { if !f.DisableTimestamp {
f.appendKeyValue(b, "time", entry.Time.Format(f.TimestampFormat)) f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
} }
f.appendKeyValue(b, "level", entry.Level.String()) f.appendKeyValue(b, "level", entry.Level.String())
f.appendKeyValue(b, "msg", entry.Message) if entry.Message != "" {
f.appendKeyValue(b, "msg", entry.Message)
}
for _, key := range keys { for _, key := range keys {
f.appendKeyValue(b, key, entry.Data[key]) f.appendKeyValue(b, key, entry.Data[key])
} }
...@@ -93,7 +96,7 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { ...@@ -93,7 +96,7 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
return b.Bytes(), nil return b.Bytes(), nil
} }
func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string) { func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) {
var levelColor int var levelColor int
switch entry.Level { switch entry.Level {
case DebugLevel: case DebugLevel:
...@@ -111,11 +114,11 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin ...@@ -111,11 +114,11 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
if !f.FullTimestamp { if !f.FullTimestamp {
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message) fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
} else { } else {
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(f.TimestampFormat), entry.Message) fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
} }
for _, k := range keys { for _, k := range keys {
v := entry.Data[k] v := entry.Data[k]
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%v", levelColor, k, v) fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%+v", levelColor, k, v)
} }
} }
......
...@@ -4,18 +4,22 @@ import ( ...@@ -4,18 +4,22 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"os" "os"
"regexp"
"strings" "strings"
) )
var ( // ParseEnvFile reads a file with environment variables enumerated by lines
// EnvironmentVariableRegexp A regexp to validate correct environment variables //
// Environment variables set by the user must have a name consisting solely of // ``Environment variable names used by the utilities in the Shell and
// alphabetics, numerics, and underscores - the first of which must not be numeric. // Utilities volume of IEEE Std 1003.1-2001 consist solely of uppercase
EnvironmentVariableRegexp = regexp.MustCompile("^[[:alpha:]_][[:alpha:][:digit:]_]*$") // letters, digits, and the '_' (underscore) from the characters defined in
) // Portable Character Set and do not begin with a digit. *But*, other
// characters may be permitted by an implementation; applications shall
// ParseEnvFile Read in a line delimited file with environment variables enumerated // tolerate the presence of such names.''
// -- http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html
//
// As of #16585, it's up to application inside docker to validate or not
// environment variables, that's why we just strip leading whitespace and
// nothing more.
func ParseEnvFile(filename string) ([]string, error) { func ParseEnvFile(filename string) ([]string, error) {
fh, err := os.Open(filename) fh, err := os.Open(filename)
if err != nil { if err != nil {
...@@ -26,17 +30,18 @@ func ParseEnvFile(filename string) ([]string, error) { ...@@ -26,17 +30,18 @@ func ParseEnvFile(filename string) ([]string, error) {
lines := []string{} lines := []string{}
scanner := bufio.NewScanner(fh) scanner := bufio.NewScanner(fh)
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() // trim the line from all leading whitespace first
line := strings.TrimLeft(scanner.Text(), whiteSpaces)
// line is not empty, and not starting with '#' // line is not empty, and not starting with '#'
if len(line) > 0 && !strings.HasPrefix(line, "#") { if len(line) > 0 && !strings.HasPrefix(line, "#") {
data := strings.SplitN(line, "=", 2) data := strings.SplitN(line, "=", 2)
// trim the front of a variable, but nothing else // trim the front of a variable, but nothing else
variable := strings.TrimLeft(data[0], whiteSpaces) variable := strings.TrimLeft(data[0], whiteSpaces)
if strings.ContainsAny(variable, whiteSpaces) {
if !EnvironmentVariableRegexp.MatchString(variable) { return []string{}, ErrBadEnvVariable{fmt.Sprintf("variable '%s' has white spaces", variable)}
return []string{}, ErrBadEnvVariable{fmt.Sprintf("variable '%s' is not a valid environment variable", variable)}
} }
if len(data) > 1 { if len(data) > 1 {
// pass the value through, no trimming // pass the value through, no trimming
......
package opts
import (
"fmt"
"net"
"net/url"
"runtime"
"strconv"
"strings"
)
var (
// DefaultHTTPPort Default HTTP Port used if only the protocol is provided to -H flag e.g. docker daemon -H tcp://
// TODO Windows. DefaultHTTPPort is only used on Windows if a -H parameter
// is not supplied. A better longer term solution would be to use a named
// pipe as the default on the Windows daemon.
// These are the IANA registered port numbers for use with Docker
// see http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=docker
DefaultHTTPPort = 2375 // Default HTTP Port
// DefaultTLSHTTPPort Default HTTP Port used when TLS enabled
DefaultTLSHTTPPort = 2376 // Default TLS encrypted HTTP Port
// DefaultUnixSocket Path for the unix socket.
// Docker daemon by default always listens on the default unix socket
DefaultUnixSocket = "/var/run/docker.sock"
// DefaultTCPHost constant defines the default host string used by docker on Windows
DefaultTCPHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort)
// DefaultTLSHost constant defines the default host string used by docker for TLS sockets
DefaultTLSHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultTLSHTTPPort)
)
// ValidateHost validates that the specified string is a valid host and returns it.
func ValidateHost(val string) (string, error) {
_, err := parseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, "", val)
if err != nil {
return val, err
}
// Note: unlike most flag validators, we don't return the mutated value here
// we need to know what the user entered later (using ParseHost) to adjust for tls
return val, nil
}
// ParseHost and set defaults for a Daemon host string
func ParseHost(defaultHost, val string) (string, error) {
host, err := parseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, defaultHost, val)
if err != nil {
return val, err
}
return host, nil
}
// parseDockerDaemonHost parses the specified address and returns an address that will be used as the host.
// Depending of the address specified, will use the defaultTCPAddr or defaultUnixAddr
// defaultUnixAddr must be a absolute file path (no `unix://` prefix)
// defaultTCPAddr must be the full `tcp://host:port` form
func parseDockerDaemonHost(defaultTCPAddr, defaultTLSHost, defaultUnixAddr, defaultAddr, addr string) (string, error) {
addr = strings.TrimSpace(addr)
if addr == "" {
if defaultAddr == defaultTLSHost {
return defaultTLSHost, nil
}
if runtime.GOOS != "windows" {
return fmt.Sprintf("unix://%s", defaultUnixAddr), nil
}
return defaultTCPAddr, nil
}
addrParts := strings.Split(addr, "://")
if len(addrParts) == 1 {
addrParts = []string{"tcp", addrParts[0]}
}
switch addrParts[0] {
case "tcp":
return parseTCPAddr(addrParts[1], defaultTCPAddr)
case "unix":
return parseUnixAddr(addrParts[1], defaultUnixAddr)
case "fd":
return addr, nil
default:
return "", fmt.Errorf("Invalid bind address format: %s", addr)
}
}
// parseUnixAddr parses and validates that the specified address is a valid UNIX
// socket address. It returns a formatted UNIX socket address, either using the
// address parsed from addr, or the contents of defaultAddr if addr is a blank
// string.
func parseUnixAddr(addr string, defaultAddr string) (string, error) {
addr = strings.TrimPrefix(addr, "unix://")
if strings.Contains(addr, "://") {
return "", fmt.Errorf("Invalid proto, expected unix: %s", addr)
}
if addr == "" {
addr = defaultAddr
}
return fmt.Sprintf("unix://%s", addr), nil
}
// parseTCPAddr parses and validates that the specified address is a valid TCP
// address. It returns a formatted TCP address, either using the address parsed
// from tryAddr, or the contents of defaultAddr if tryAddr is a blank string.
// tryAddr is expected to have already been Trim()'d
// defaultAddr must be in the full `tcp://host:port` form
func parseTCPAddr(tryAddr string, defaultAddr string) (string, error) {
if tryAddr == "" || tryAddr == "tcp://" {
return defaultAddr, nil
}
addr := strings.TrimPrefix(tryAddr, "tcp://")
if strings.Contains(addr, "://") || addr == "" {
return "", fmt.Errorf("Invalid proto, expected tcp: %s", tryAddr)
}
defaultAddr = strings.TrimPrefix(defaultAddr, "tcp://")
defaultHost, defaultPort, err := net.SplitHostPort(defaultAddr)
if err != nil {
return "", err
}
// url.Parse fails for trailing colon on IPv6 brackets on Go 1.5, but
// not 1.4. See https://github.com/golang/go/issues/12200 and
// https://github.com/golang/go/issues/6530.
if strings.HasSuffix(addr, "]:") {
addr += defaultPort
}
u, err := url.Parse("tcp://" + addr)
if err != nil {
return "", err
}
host, port, err := net.SplitHostPort(u.Host)
if err != nil {
return "", fmt.Errorf("Invalid bind address format: %s", tryAddr)
}
if host == "" {
host = defaultHost
}
if port == "" {
port = defaultPort
}
p, err := strconv.Atoi(port)
if err != nil && p == 0 {
return "", fmt.Errorf("Invalid bind address format: %s", tryAddr)
}
return fmt.Sprintf("tcp://%s%s", net.JoinHostPort(host, port), u.Path), nil
}
...@@ -4,4 +4,5 @@ package opts ...@@ -4,4 +4,5 @@ package opts
import "fmt" import "fmt"
// DefaultHost constant defines the default host string used by docker on other hosts than Windows
var DefaultHost = fmt.Sprintf("unix://%s", DefaultUnixSocket) var DefaultHost = fmt.Sprintf("unix://%s", DefaultUnixSocket)
...@@ -2,6 +2,5 @@ ...@@ -2,6 +2,5 @@
package opts package opts
import "fmt" // DefaultHost constant defines the default host string used by docker on Windows
var DefaultHost = DefaultTCPHost
var DefaultHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort)
...@@ -5,20 +5,25 @@ import ( ...@@ -5,20 +5,25 @@ import (
"net" "net"
) )
// IpOpt type that hold an IP // IPOpt holds an IP. It is used to store values from CLI flags.
type IpOpt struct { type IPOpt struct {
*net.IP *net.IP
} }
func NewIpOpt(ref *net.IP, defaultVal string) *IpOpt { // NewIPOpt creates a new IPOpt from a reference net.IP and a
o := &IpOpt{ // string representation of an IP. If the string is not a valid
// IP it will fallback to the specified reference.
func NewIPOpt(ref *net.IP, defaultVal string) *IPOpt {
o := &IPOpt{
IP: ref, IP: ref,
} }
o.Set(defaultVal) o.Set(defaultVal)
return o return o
} }
func (o *IpOpt) Set(val string) error { // Set sets an IPv4 or IPv6 address from a given string. If the given
// string is not parseable as an IP address it returns an error.
func (o *IPOpt) Set(val string) error {
ip := net.ParseIP(val) ip := net.ParseIP(val)
if ip == nil { if ip == nil {
return fmt.Errorf("%s is not an ip address", val) return fmt.Errorf("%s is not an ip address", val)
...@@ -27,7 +32,9 @@ func (o *IpOpt) Set(val string) error { ...@@ -27,7 +32,9 @@ func (o *IpOpt) Set(val string) error {
return nil return nil
} }
func (o *IpOpt) String() string { // String returns the IP address stored in the IPOpt. If stored IP is a
// nil pointer, it returns an empty string.
func (o *IPOpt) String() string {
if *o.IP == nil { if *o.IP == nil {
return "" return ""
} }
......
// +build !windows
package opts
// DefaultHTTPHost Default HTTP Host used if only port is provided to -H flag e.g. docker daemon -H tcp://:8080
const DefaultHTTPHost = "localhost"
package opts
// TODO Windows. Identify bug in GOLang 1.5.1 and/or Windows Server 2016 TP4.
// @jhowardmsft, @swernli.
//
// On Windows, this mitigates a problem with the default options of running
// a docker client against a local docker daemon on TP4.
//
// What was found that if the default host is "localhost", even if the client
// (and daemon as this is local) is not physically on a network, and the DNS
// cache is flushed (ipconfig /flushdns), then the client will pause for
// exactly one second when connecting to the daemon for calls. For example
// using docker run windowsservercore cmd, the CLI will send a create followed
// by an attach. You see the delay between the attach finishing and the attach
// being seen by the daemon.
//
// Here's some daemon debug logs with additional debug spew put in. The
// AfterWriteJSON log is the very last thing the daemon does as part of the
// create call. The POST /attach is the second CLI call. Notice the second
// time gap.
//
// time="2015-11-06T13:38:37.259627400-08:00" level=debug msg="After createRootfs"
// time="2015-11-06T13:38:37.263626300-08:00" level=debug msg="After setHostConfig"
// time="2015-11-06T13:38:37.267631200-08:00" level=debug msg="before createContainerPl...."
// time="2015-11-06T13:38:37.271629500-08:00" level=debug msg=ToDiskLocking....
// time="2015-11-06T13:38:37.275643200-08:00" level=debug msg="loggin event...."
// time="2015-11-06T13:38:37.277627600-08:00" level=debug msg="logged event...."
// time="2015-11-06T13:38:37.279631800-08:00" level=debug msg="In defer func"
// time="2015-11-06T13:38:37.282628100-08:00" level=debug msg="After daemon.create"
// time="2015-11-06T13:38:37.286651700-08:00" level=debug msg="return 2"
// time="2015-11-06T13:38:37.289629500-08:00" level=debug msg="Returned from daemon.ContainerCreate"
// time="2015-11-06T13:38:37.311629100-08:00" level=debug msg="After WriteJSON"
// ... 1 second gap here....
// time="2015-11-06T13:38:38.317866200-08:00" level=debug msg="Calling POST /v1.22/containers/984758282b842f779e805664b2c95d563adc9a979c8a3973e68c807843ee4757/attach"
// time="2015-11-06T13:38:38.326882500-08:00" level=info msg="POST /v1.22/containers/984758282b842f779e805664b2c95d563adc9a979c8a3973e68c807843ee4757/attach?stderr=1&stdin=1&stdout=1&stream=1"
//
// We suspect this is either a bug introduced in GOLang 1.5.1, or that a change
// in GOLang 1.5.1 (from 1.4.3) is exposing a bug in Windows TP4. In theory,
// the Windows networking stack is supposed to resolve "localhost" internally,
// without hitting DNS, or even reading the hosts file (which is why localhost
// is commented out in the hosts file on Windows).
//
// We have validated that working around this using the actual IPv4 localhost
// address does not cause the delay.
//
// This does not occur with the docker client built with 1.4.3 on the same
// Windows TP4 build, regardless of whether the daemon is built using 1.5.1
// or 1.4.3. It does not occur on Linux. We also verified we see the same thing
// on a cross-compiled Windows binary (from Linux).
//
// Final note: This is a mitigation, not a 'real' fix. It is still susceptible
// to the delay in TP4 if a user were to do 'docker run -H=tcp://localhost:2375...'
// explicitly.
// DefaultHTTPHost Default HTTP Host used if only port is provided to -H flag e.g. docker daemon -H tcp://:8080
const DefaultHTTPHost = "127.0.0.1"
package opts
import (
"fmt"
"github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ulimit"
)
type UlimitOpt struct {
values *map[string]*ulimit.Ulimit
}
func NewUlimitOpt(ref *map[string]*ulimit.Ulimit) *UlimitOpt {
if ref == nil {
ref = &map[string]*ulimit.Ulimit{}
}
return &UlimitOpt{ref}
}
func (o *UlimitOpt) Set(val string) error {
l, err := ulimit.Parse(val)
if err != nil {
return err
}
(*o.values)[l.Name] = l
return nil
}
func (o *UlimitOpt) String() string {
var out []string
for _, v := range *o.values {
out = append(out, v.String())
}
return fmt.Sprintf("%v", out)
}
func (o *UlimitOpt) GetList() []*ulimit.Ulimit {
var ulimits []*ulimit.Ulimit
for _, v := range *o.values {
ulimits = append(ulimits, v)
}
return ulimits
}
...@@ -6,11 +6,26 @@ import ( ...@@ -6,11 +6,26 @@ import (
"archive/tar" "archive/tar"
"errors" "errors"
"os" "os"
"path/filepath"
"syscall" "syscall"
"github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system"
) )
// fixVolumePathPrefix does platform specific processing to ensure that if
// the path being passed in is not in a volume path format, convert it to one.
func fixVolumePathPrefix(srcPath string) string {
return srcPath
}
// getWalkRoot calculates the root path when performing a TarWithOptions.
// We use a separate function as this is platform specific. On Linux, we
// can't use filepath.Join(srcPath,include) because this will clean away
// a trailing "." or "/" which may be important.
func getWalkRoot(srcPath string, include string) string {
return srcPath + string(filepath.Separator) + include
}
// CanonicalTarNameForPath returns platform-specific filepath // CanonicalTarNameForPath returns platform-specific filepath
// to canonical posix-style path for tar archival. p is relative // to canonical posix-style path for tar archival. p is relative
// path. // path.
...@@ -25,7 +40,7 @@ func chmodTarEntry(perm os.FileMode) os.FileMode { ...@@ -25,7 +40,7 @@ func chmodTarEntry(perm os.FileMode) os.FileMode {
return perm // noop for unix as golang APIs provide perm bits correctly return perm // noop for unix as golang APIs provide perm bits correctly
} }
func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (nlink uint32, inode uint64, err error) { func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (inode uint64, err error) {
s, ok := stat.(*syscall.Stat_t) s, ok := stat.(*syscall.Stat_t)
if !ok { if !ok {
...@@ -33,10 +48,9 @@ func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, st ...@@ -33,10 +48,9 @@ func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, st
return return
} }
nlink = uint32(s.Nlink)
inode = uint64(s.Ino) inode = uint64(s.Ino)
// Currently go does not fil in the major/minors // Currently go does not fill in the major/minors
if s.Mode&syscall.S_IFBLK != 0 || if s.Mode&syscall.S_IFBLK != 0 ||
s.Mode&syscall.S_IFCHR != 0 { s.Mode&syscall.S_IFCHR != 0 {
hdr.Devmajor = int64(major(uint64(s.Rdev))) hdr.Devmajor = int64(major(uint64(s.Rdev)))
...@@ -46,6 +60,15 @@ func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, st ...@@ -46,6 +60,15 @@ func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, st
return return
} }
func getFileUIDGID(stat interface{}) (int, int, error) {
s, ok := stat.(*syscall.Stat_t)
if !ok {
return -1, -1, errors.New("cannot convert stat value to syscall.Stat_t")
}
return int(s.Uid), int(s.Gid), nil
}
func major(device uint64) uint64 { func major(device uint64) uint64 {
return (device >> 8) & 0xfff return (device >> 8) & 0xfff
} }
......
...@@ -6,10 +6,25 @@ import ( ...@@ -6,10 +6,25 @@ import (
"archive/tar" "archive/tar"
"fmt" "fmt"
"os" "os"
"path/filepath"
"strings" "strings"
"github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/longpath"
) )
// canonicalTarNameForPath returns platform-specific filepath // fixVolumePathPrefix does platform specific processing to ensure that if
// the path being passed in is not in a volume path format, convert it to one.
func fixVolumePathPrefix(srcPath string) string {
return longpath.AddPrefix(srcPath)
}
// getWalkRoot calculates the root path when performing a TarWithOptions.
// We use a separate function as this is platform specific.
func getWalkRoot(srcPath string, include string) string {
return filepath.Join(srcPath, include)
}
// CanonicalTarNameForPath returns platform-specific filepath
// to canonical posix-style path for tar archival. p is relative // to canonical posix-style path for tar archival. p is relative
// path. // path.
func CanonicalTarNameForPath(p string) (string, error) { func CanonicalTarNameForPath(p string) (string, error) {
...@@ -34,7 +49,7 @@ func chmodTarEntry(perm os.FileMode) os.FileMode { ...@@ -34,7 +49,7 @@ func chmodTarEntry(perm os.FileMode) os.FileMode {
return perm return perm
} }
func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (nlink uint32, inode uint64, err error) { func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (inode uint64, err error) {
// do nothing. no notion of Rdev, Inode, Nlink in stat on Windows // do nothing. no notion of Rdev, Inode, Nlink in stat on Windows
return return
} }
...@@ -48,3 +63,8 @@ func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error { ...@@ -48,3 +63,8 @@ func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error { func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error {
return nil return nil
} }
func getFileUIDGID(stat interface{}) (int, int, error) {
// no notion of file ownership mapping yet on Windows
return 0, 0, nil
}
...@@ -14,34 +14,46 @@ import ( ...@@ -14,34 +14,46 @@ import (
"time" "time"
"github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus" "github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus"
"github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools"
"github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/pools" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/pools"
"github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system"
) )
// ChangeType represents the change type.
type ChangeType int type ChangeType int
const ( const (
// ChangeModify represents the modify operation.
ChangeModify = iota ChangeModify = iota
// ChangeAdd represents the add operation.
ChangeAdd ChangeAdd
// ChangeDelete represents the delete operation.
ChangeDelete ChangeDelete
) )
func (c ChangeType) String() string {
switch c {
case ChangeModify:
return "C"
case ChangeAdd:
return "A"
case ChangeDelete:
return "D"
}
return ""
}
// Change represents a change, it wraps the change type and path.
// It describes changes of the files in the path respect to the
// parent layers. The change could be modify, add, delete.
// This is used for layer diff.
type Change struct { type Change struct {
Path string Path string
Kind ChangeType Kind ChangeType
} }
func (change *Change) String() string { func (change *Change) String() string {
var kind string return fmt.Sprintf("%s %s", change.Kind, change.Path)
switch change.Kind {
case ChangeModify:
kind = "C"
case ChangeAdd:
kind = "A"
case ChangeDelete:
kind = "D"
}
return fmt.Sprintf("%s %s", kind, change.Path)
} }
// for sort.Sort // for sort.Sort
...@@ -94,7 +106,7 @@ func Changes(layers []string, rw string) ([]Change, error) { ...@@ -94,7 +106,7 @@ func Changes(layers []string, rw string) ([]Change, error) {
} }
// Skip AUFS metadata // Skip AUFS metadata
if matched, err := filepath.Match(string(os.PathSeparator)+".wh..wh.*", path); err != nil || matched { if matched, err := filepath.Match(string(os.PathSeparator)+WhiteoutMetaPrefix+"*", path); err != nil || matched {
return err return err
} }
...@@ -105,8 +117,8 @@ func Changes(layers []string, rw string) ([]Change, error) { ...@@ -105,8 +117,8 @@ func Changes(layers []string, rw string) ([]Change, error) {
// Find out what kind of modification happened // Find out what kind of modification happened
file := filepath.Base(path) file := filepath.Base(path)
// If there is a whiteout, then the file was removed // If there is a whiteout, then the file was removed
if strings.HasPrefix(file, ".wh.") { if strings.HasPrefix(file, WhiteoutPrefix) {
originalFile := file[len(".wh."):] originalFile := file[len(WhiteoutPrefix):]
change.Path = filepath.Join(filepath.Dir(path), originalFile) change.Path = filepath.Join(filepath.Dir(path), originalFile)
change.Kind = ChangeDelete change.Kind = ChangeDelete
} else { } else {
...@@ -138,7 +150,7 @@ func Changes(layers []string, rw string) ([]Change, error) { ...@@ -138,7 +150,7 @@ func Changes(layers []string, rw string) ([]Change, error) {
// If /foo/bar/file.txt is modified, then /foo/bar must be part of the changed files. // If /foo/bar/file.txt is modified, then /foo/bar must be part of the changed files.
// This block is here to ensure the change is recorded even if the // This block is here to ensure the change is recorded even if the
// modify time, mode and size of the parent directoriy in the rw and ro layers are all equal. // modify time, mode and size of the parent directory in the rw and ro layers are all equal.
// Check https://github.com/docker/docker/pull/13590 for details. // Check https://github.com/docker/docker/pull/13590 for details.
if f.IsDir() { if f.IsDir() {
changedDirs[path] = struct{}{} changedDirs[path] = struct{}{}
...@@ -161,20 +173,22 @@ func Changes(layers []string, rw string) ([]Change, error) { ...@@ -161,20 +173,22 @@ func Changes(layers []string, rw string) ([]Change, error) {
return changes, nil return changes, nil
} }
// FileInfo describes the information of a file.
type FileInfo struct { type FileInfo struct {
parent *FileInfo parent *FileInfo
name string name string
stat *system.Stat_t stat *system.StatT
children map[string]*FileInfo children map[string]*FileInfo
capability []byte capability []byte
added bool added bool
} }
func (root *FileInfo) LookUp(path string) *FileInfo { // LookUp looks up the file information of a file.
func (info *FileInfo) LookUp(path string) *FileInfo {
// As this runs on the daemon side, file paths are OS specific. // As this runs on the daemon side, file paths are OS specific.
parent := root parent := info
if path == string(os.PathSeparator) { if path == string(os.PathSeparator) {
return root return info
} }
pathElements := strings.Split(path, string(os.PathSeparator)) pathElements := strings.Split(path, string(os.PathSeparator))
...@@ -275,6 +289,7 @@ func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) { ...@@ -275,6 +289,7 @@ func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
} }
// Changes add changes to file information.
func (info *FileInfo) Changes(oldInfo *FileInfo) []Change { func (info *FileInfo) Changes(oldInfo *FileInfo) []Change {
var changes []Change var changes []Change
...@@ -316,13 +331,29 @@ func ChangesDirs(newDir, oldDir string) ([]Change, error) { ...@@ -316,13 +331,29 @@ func ChangesDirs(newDir, oldDir string) ([]Change, error) {
// ChangesSize calculates the size in bytes of the provided changes, based on newDir. // ChangesSize calculates the size in bytes of the provided changes, based on newDir.
func ChangesSize(newDir string, changes []Change) int64 { func ChangesSize(newDir string, changes []Change) int64 {
var size int64 var (
size int64
sf = make(map[uint64]struct{})
)
for _, change := range changes { for _, change := range changes {
if change.Kind == ChangeModify || change.Kind == ChangeAdd { if change.Kind == ChangeModify || change.Kind == ChangeAdd {
file := filepath.Join(newDir, change.Path) file := filepath.Join(newDir, change.Path)
fileInfo, _ := os.Lstat(file) fileInfo, err := os.Lstat(file)
if err != nil {
logrus.Errorf("Can not stat %q: %s", file, err)
continue
}
if fileInfo != nil && !fileInfo.IsDir() { if fileInfo != nil && !fileInfo.IsDir() {
size += fileInfo.Size() if hasHardlinks(fileInfo) {
inode := getIno(fileInfo)
if _, ok := sf[inode]; !ok {
size += fileInfo.Size()
sf[inode] = struct{}{}
}
} else {
size += fileInfo.Size()
}
} }
} }
} }
...@@ -330,13 +361,15 @@ func ChangesSize(newDir string, changes []Change) int64 { ...@@ -330,13 +361,15 @@ func ChangesSize(newDir string, changes []Change) int64 {
} }
// ExportChanges produces an Archive from the provided changes, relative to dir. // ExportChanges produces an Archive from the provided changes, relative to dir.
func ExportChanges(dir string, changes []Change) (Archive, error) { func ExportChanges(dir string, changes []Change, uidMaps, gidMaps []idtools.IDMap) (Archive, error) {
reader, writer := io.Pipe() reader, writer := io.Pipe()
go func() { go func() {
ta := &tarAppender{ ta := &tarAppender{
TarWriter: tar.NewWriter(writer), TarWriter: tar.NewWriter(writer),
Buffer: pools.BufioWriter32KPool.Get(nil), Buffer: pools.BufioWriter32KPool.Get(nil),
SeenFiles: make(map[uint64]string), SeenFiles: make(map[uint64]string),
UIDMaps: uidMaps,
GIDMaps: gidMaps,
} }
// this buffer is needed for the duration of this piped stream // this buffer is needed for the duration of this piped stream
defer pools.BufioWriter32KPool.Put(ta.Buffer) defer pools.BufioWriter32KPool.Put(ta.Buffer)
...@@ -351,7 +384,7 @@ func ExportChanges(dir string, changes []Change) (Archive, error) { ...@@ -351,7 +384,7 @@ func ExportChanges(dir string, changes []Change) (Archive, error) {
if change.Kind == ChangeDelete { if change.Kind == ChangeDelete {
whiteOutDir := filepath.Dir(change.Path) whiteOutDir := filepath.Dir(change.Path)
whiteOutBase := filepath.Base(change.Path) whiteOutBase := filepath.Base(change.Path)
whiteOut := filepath.Join(whiteOutDir, ".wh."+whiteOutBase) whiteOut := filepath.Join(whiteOutDir, WhiteoutPrefix+whiteOutBase)
timestamp := time.Now() timestamp := time.Now()
hdr := &tar.Header{ hdr := &tar.Header{
Name: whiteOut[1:], Name: whiteOut[1:],
......
...@@ -3,16 +3,17 @@ ...@@ -3,16 +3,17 @@
package archive package archive
import ( import (
"os"
"syscall" "syscall"
"github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system"
) )
func statDifferent(oldStat *system.Stat_t, newStat *system.Stat_t) bool { func statDifferent(oldStat *system.StatT, newStat *system.StatT) bool {
// Don't look at size for dirs, its not a good measure of change // Don't look at size for dirs, its not a good measure of change
if oldStat.Mode() != newStat.Mode() || if oldStat.Mode() != newStat.Mode() ||
oldStat.Uid() != newStat.Uid() || oldStat.UID() != newStat.UID() ||
oldStat.Gid() != newStat.Gid() || oldStat.GID() != newStat.GID() ||
oldStat.Rdev() != newStat.Rdev() || oldStat.Rdev() != newStat.Rdev() ||
// Don't look at size for dirs, its not a good measure of change // Don't look at size for dirs, its not a good measure of change
(oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR && (oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR &&
...@@ -25,3 +26,11 @@ func statDifferent(oldStat *system.Stat_t, newStat *system.Stat_t) bool { ...@@ -25,3 +26,11 @@ func statDifferent(oldStat *system.Stat_t, newStat *system.Stat_t) bool {
func (info *FileInfo) isDir() bool { func (info *FileInfo) isDir() bool {
return info.parent == nil || info.stat.Mode()&syscall.S_IFDIR != 0 return info.parent == nil || info.stat.Mode()&syscall.S_IFDIR != 0
} }
func getIno(fi os.FileInfo) uint64 {
return uint64(fi.Sys().(*syscall.Stat_t).Ino)
}
func hasHardlinks(fi os.FileInfo) bool {
return fi.Sys().(*syscall.Stat_t).Nlink > 1
}
package archive package archive
import ( import (
"os"
"github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system"
) )
func statDifferent(oldStat *system.Stat_t, newStat *system.Stat_t) bool { func statDifferent(oldStat *system.StatT, newStat *system.StatT) bool {
// Don't look at size for dirs, its not a good measure of change // Don't look at size for dirs, its not a good measure of change
if oldStat.ModTime() != newStat.ModTime() || if oldStat.ModTime() != newStat.ModTime() ||
...@@ -18,3 +20,11 @@ func statDifferent(oldStat *system.Stat_t, newStat *system.Stat_t) bool { ...@@ -18,3 +20,11 @@ func statDifferent(oldStat *system.Stat_t, newStat *system.Stat_t) bool {
func (info *FileInfo) isDir() bool { func (info *FileInfo) isDir() bool {
return info.parent == nil || info.stat.IsDir() return info.parent == nil || info.stat.IsDir()
} }
func getIno(fi os.FileInfo) (inode uint64) {
return
}
func hasHardlinks(fi os.FileInfo) bool {
return false
}
...@@ -9,23 +9,41 @@ import ( ...@@ -9,23 +9,41 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"syscall"
"github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus" "github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus"
"github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools"
"github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/pools" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/pools"
"github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system" "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system"
) )
func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) { // UnpackLayer unpack `layer` to a `dest`. The stream `layer` can be
// compressed or uncompressed.
// Returns the size in bytes of the contents of the layer.
func UnpackLayer(dest string, layer Reader, options *TarOptions) (size int64, err error) {
tr := tar.NewReader(layer) tr := tar.NewReader(layer)
trBuf := pools.BufioReader32KPool.Get(tr) trBuf := pools.BufioReader32KPool.Get(tr)
defer pools.BufioReader32KPool.Put(trBuf) defer pools.BufioReader32KPool.Put(trBuf)
var dirs []*tar.Header var dirs []*tar.Header
unpackedPaths := make(map[string]struct{})
if options == nil {
options = &TarOptions{}
}
if options.ExcludePatterns == nil {
options.ExcludePatterns = []string{}
}
remappedRootUID, remappedRootGID, err := idtools.GetRootUIDGID(options.UIDMaps, options.GIDMaps)
if err != nil {
return 0, err
}
aufsTempdir := "" aufsTempdir := ""
aufsHardlinks := make(map[string]*tar.Header) aufsHardlinks := make(map[string]*tar.Header)
if options == nil {
options = &TarOptions{}
}
// Iterate through the files in the archive. // Iterate through the files in the archive.
for { for {
hdr, err := tr.Next() hdr, err := tr.Next()
...@@ -55,7 +73,7 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) { ...@@ -55,7 +73,7 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) {
// TODO Windows. Once the registry is aware of what images are Windows- // TODO Windows. Once the registry is aware of what images are Windows-
// specific or Linux-specific, this warning should be changed to an error // specific or Linux-specific, this warning should be changed to an error
// to cater for the situation where someone does manage to upload a Linux // to cater for the situation where someone does manage to upload a Linux
// image but have it tagged as Windows inadvertantly. // image but have it tagged as Windows inadvertently.
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
if strings.Contains(hdr.Name, ":") { if strings.Contains(hdr.Name, ":") {
logrus.Warnf("Windows: Ignoring %s (is this a Linux image?)", hdr.Name) logrus.Warnf("Windows: Ignoring %s (is this a Linux image?)", hdr.Name)
...@@ -80,11 +98,11 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) { ...@@ -80,11 +98,11 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) {
} }
// Skip AUFS metadata dirs // Skip AUFS metadata dirs
if strings.HasPrefix(hdr.Name, ".wh..wh.") { if strings.HasPrefix(hdr.Name, WhiteoutMetaPrefix) {
// Regular files inside /.wh..wh.plnk can be used as hardlink targets // Regular files inside /.wh..wh.plnk can be used as hardlink targets
// We don't want this directory, but we need the files in them so that // We don't want this directory, but we need the files in them so that
// such hardlinks can be resolved. // such hardlinks can be resolved.
if strings.HasPrefix(hdr.Name, ".wh..wh.plnk") && hdr.Typeflag == tar.TypeReg { if strings.HasPrefix(hdr.Name, WhiteoutLinkDir) && hdr.Typeflag == tar.TypeReg {
basename := filepath.Base(hdr.Name) basename := filepath.Base(hdr.Name)
aufsHardlinks[basename] = hdr aufsHardlinks[basename] = hdr
if aufsTempdir == "" { if aufsTempdir == "" {
...@@ -97,7 +115,10 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) { ...@@ -97,7 +115,10 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) {
return 0, err return 0, err
} }
} }
continue
if hdr.Name != WhiteoutOpaqueDir {
continue
}
} }
path := filepath.Join(dest, hdr.Name) path := filepath.Join(dest, hdr.Name)
rel, err := filepath.Rel(dest, path) rel, err := filepath.Rel(dest, path)
...@@ -111,11 +132,38 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) { ...@@ -111,11 +132,38 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) {
} }
base := filepath.Base(path) base := filepath.Base(path)
if strings.HasPrefix(base, ".wh.") { if strings.HasPrefix(base, WhiteoutPrefix) {
originalBase := base[len(".wh."):] dir := filepath.Dir(path)
originalPath := filepath.Join(filepath.Dir(path), originalBase) if base == WhiteoutOpaqueDir {
if err := os.RemoveAll(originalPath); err != nil { _, err := os.Lstat(dir)
return 0, err if err != nil {
return 0, err
}
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
if os.IsNotExist(err) {
err = nil // parent was deleted
}
return err
}
if path == dir {
return nil
}
if _, exists := unpackedPaths[path]; !exists {
err := os.RemoveAll(path)
return err
}
return nil
})
if err != nil {
return 0, err
}
} else {
originalBase := base[len(WhiteoutPrefix):]
originalPath := filepath.Join(dir, originalBase)
if err := os.RemoveAll(originalPath); err != nil {
return 0, err
}
} }
} else { } else {
// If path exits we almost always just want to remove and replace it. // If path exits we almost always just want to remove and replace it.
...@@ -136,7 +184,7 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) { ...@@ -136,7 +184,7 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) {
// Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so // Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so
// we manually retarget these into the temporary files we extracted them into // we manually retarget these into the temporary files we extracted them into
if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), ".wh..wh.plnk") { if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), WhiteoutLinkDir) {
linkBasename := filepath.Base(hdr.Linkname) linkBasename := filepath.Base(hdr.Linkname)
srcHdr = aufsHardlinks[linkBasename] srcHdr = aufsHardlinks[linkBasename]
if srcHdr == nil { if srcHdr == nil {
...@@ -150,6 +198,27 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) { ...@@ -150,6 +198,27 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) {
srcData = tmpFile srcData = tmpFile
} }
// if the options contain a uid & gid maps, convert header uid/gid
// entries using the maps such that lchown sets the proper mapped
// uid/gid after writing the file. We only perform this mapping if
// the file isn't already owned by the remapped root UID or GID, as
// that specific uid/gid has no mapping from container -> host, and
// those files already have the proper ownership for inside the
// container.
if srcHdr.Uid != remappedRootUID {
xUID, err := idtools.ToHost(srcHdr.Uid, options.UIDMaps)
if err != nil {
return 0, err
}
srcHdr.Uid = xUID
}
if srcHdr.Gid != remappedRootGID {
xGID, err := idtools.ToHost(srcHdr.Gid, options.GIDMaps)
if err != nil {
return 0, err
}
srcHdr.Gid = xGID
}
if err := createTarFile(path, dest, srcHdr, srcData, true, nil); err != nil { if err := createTarFile(path, dest, srcHdr, srcData, true, nil); err != nil {
return 0, err return 0, err
} }
...@@ -159,13 +228,13 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) { ...@@ -159,13 +228,13 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) {
if hdr.Typeflag == tar.TypeDir { if hdr.Typeflag == tar.TypeDir {
dirs = append(dirs, hdr) dirs = append(dirs, hdr)
} }
unpackedPaths[path] = struct{}{}
} }
} }
for _, hdr := range dirs { for _, hdr := range dirs {
path := filepath.Join(dest, hdr.Name) path := filepath.Join(dest, hdr.Name)
ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} if err := system.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil {
if err := syscall.UtimesNano(path, ts); err != nil {
return 0, err return 0, err
} }
} }
...@@ -177,20 +246,20 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) { ...@@ -177,20 +246,20 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) {
// and applies it to the directory `dest`. The stream `layer` can be // and applies it to the directory `dest`. The stream `layer` can be
// compressed or uncompressed. // compressed or uncompressed.
// Returns the size in bytes of the contents of the layer. // Returns the size in bytes of the contents of the layer.
func ApplyLayer(dest string, layer ArchiveReader) (int64, error) { func ApplyLayer(dest string, layer Reader) (int64, error) {
return applyLayerHandler(dest, layer, true) return applyLayerHandler(dest, layer, &TarOptions{}, true)
} }
// ApplyUncompressedLayer parses a diff in the standard layer format from // ApplyUncompressedLayer parses a diff in the standard layer format from
// `layer`, and applies it to the directory `dest`. The stream `layer` // `layer`, and applies it to the directory `dest`. The stream `layer`
// can only be uncompressed. // can only be uncompressed.
// Returns the size in bytes of the contents of the layer. // Returns the size in bytes of the contents of the layer.
func ApplyUncompressedLayer(dest string, layer ArchiveReader) (int64, error) { func ApplyUncompressedLayer(dest string, layer Reader, options *TarOptions) (int64, error) {
return applyLayerHandler(dest, layer, false) return applyLayerHandler(dest, layer, options, false)
} }
// do the bulk load of ApplyLayer, but allow for not calling DecompressStream // do the bulk load of ApplyLayer, but allow for not calling DecompressStream
func applyLayerHandler(dest string, layer ArchiveReader, decompress bool) (int64, error) { func applyLayerHandler(dest string, layer Reader, options *TarOptions, decompress bool) (int64, error) {
dest = filepath.Clean(dest) dest = filepath.Clean(dest)
// We need to be able to set any perms // We need to be able to set any perms
...@@ -206,5 +275,5 @@ func applyLayerHandler(dest string, layer ArchiveReader, decompress bool) (int64 ...@@ -206,5 +275,5 @@ func applyLayerHandler(dest string, layer ArchiveReader, decompress bool) (int64
return 0, err return 0, err
} }
} }
return UnpackLayer(dest, layer) return UnpackLayer(dest, layer, options)
} }
package archive
// Whiteouts are files with a special meaning for the layered filesystem.
// Docker uses AUFS whiteout files inside exported archives. In other
// filesystems these files are generated/handled on tar creation/extraction.
// WhiteoutPrefix prefix means file is a whiteout. If this is followed by a
// filename this means that file has been removed from the base layer.
const WhiteoutPrefix = ".wh."
// WhiteoutMetaPrefix prefix means whiteout has a special meaning and is not
// for removing an actual file. Normally these files are excluded from exported
// archives.
const WhiteoutMetaPrefix = WhiteoutPrefix + WhiteoutPrefix
// WhiteoutLinkDir is a directory AUFS uses for storing hardlink links to other
// layers. Normally these should not go into exported archives and all changed
// hardlinks should be copied to the top layer.
const WhiteoutLinkDir = WhiteoutMetaPrefix + "plnk"
// WhiteoutOpaqueDir file means directory has been made opaque - meaning
// readdir calls to this directory do not follow to lower layers.
const WhiteoutOpaqueDir = WhiteoutMetaPrefix + ".opq"
...@@ -4,10 +4,11 @@ import ( ...@@ -4,10 +4,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"text/scanner"
"github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus" "github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus"
) )
...@@ -77,7 +78,7 @@ func Matches(file string, patterns []string) (bool, error) { ...@@ -77,7 +78,7 @@ func Matches(file string, patterns []string) (bool, error) {
// OptimizedMatches is basically the same as fileutils.Matches() but optimized for archive.go. // OptimizedMatches is basically the same as fileutils.Matches() but optimized for archive.go.
// It will assume that the inputs have been preprocessed and therefore the function // It will assume that the inputs have been preprocessed and therefore the function
// doen't need to do as much error checking and clean-up. This was done to avoid // doesn't need to do as much error checking and clean-up. This was done to avoid
// repeating these steps on each file being checked during the archive process. // repeating these steps on each file being checked during the archive process.
// The more generic fileutils.Matches() can't make these assumptions. // The more generic fileutils.Matches() can't make these assumptions.
func OptimizedMatches(file string, patterns []string, patDirs [][]string) (bool, error) { func OptimizedMatches(file string, patterns []string, patDirs [][]string) (bool, error) {
...@@ -93,15 +94,15 @@ func OptimizedMatches(file string, patterns []string, patDirs [][]string) (bool, ...@@ -93,15 +94,15 @@ func OptimizedMatches(file string, patterns []string, patDirs [][]string) (bool,
pattern = pattern[1:] pattern = pattern[1:]
} }
match, err := filepath.Match(pattern, file) match, err := regexpMatch(pattern, file)
if err != nil { if err != nil {
return false, err return false, fmt.Errorf("Error in pattern (%s): %s", pattern, err)
} }
if !match && parentPath != "." { if !match && parentPath != "." {
// Check to see if the pattern matches one of our parent dirs. // Check to see if the pattern matches one of our parent dirs.
if len(patDirs[i]) <= len(parentPathDirs) { if len(patDirs[i]) <= len(parentPathDirs) {
match, _ = filepath.Match(strings.Join(patDirs[i], "/"), match, _ = regexpMatch(strings.Join(patDirs[i], "/"),
strings.Join(parentPathDirs[:len(patDirs[i])], "/")) strings.Join(parentPathDirs[:len(patDirs[i])], "/"))
} }
} }
...@@ -118,6 +119,99 @@ func OptimizedMatches(file string, patterns []string, patDirs [][]string) (bool, ...@@ -118,6 +119,99 @@ func OptimizedMatches(file string, patterns []string, patDirs [][]string) (bool,
return matched, nil return matched, nil
} }
// regexpMatch tries to match the logic of filepath.Match but
// does so using regexp logic. We do this so that we can expand the
// wildcard set to include other things, like "**" to mean any number
// of directories. This means that we should be backwards compatible
// with filepath.Match(). We'll end up supporting more stuff, due to
// the fact that we're using regexp, but that's ok - it does no harm.
func regexpMatch(pattern, path string) (bool, error) {
regStr := "^"
// Do some syntax checking on the pattern.
// filepath's Match() has some really weird rules that are inconsistent
// so instead of trying to dup their logic, just call Match() for its
// error state and if there is an error in the pattern return it.
// If this becomes an issue we can remove this since its really only
// needed in the error (syntax) case - which isn't really critical.
if _, err := filepath.Match(pattern, path); err != nil {
return false, err
}
// Go through the pattern and convert it to a regexp.
// We use a scanner so we can support utf-8 chars.
var scan scanner.Scanner
scan.Init(strings.NewReader(pattern))
sl := string(os.PathSeparator)
escSL := sl
if sl == `\` {
escSL += `\`
}
for scan.Peek() != scanner.EOF {
ch := scan.Next()
if ch == '*' {
if scan.Peek() == '*' {
// is some flavor of "**"
scan.Next()
if scan.Peek() == scanner.EOF {
// is "**EOF" - to align with .gitignore just accept all
regStr += ".*"
} else {
// is "**"
regStr += "((.*" + escSL + ")|([^" + escSL + "]*))"
}
// Treat **/ as ** so eat the "/"
if string(scan.Peek()) == sl {
scan.Next()
}
} else {
// is "*" so map it to anything but "/"
regStr += "[^" + escSL + "]*"
}
} else if ch == '?' {
// "?" is any char except "/"
regStr += "[^" + escSL + "]"
} else if strings.Index(".$", string(ch)) != -1 {
// Escape some regexp special chars that have no meaning
// in golang's filepath.Match
regStr += `\` + string(ch)
} else if ch == '\\' {
// escape next char. Note that a trailing \ in the pattern
// will be left alone (but need to escape it)
if sl == `\` {
// On windows map "\" to "\\", meaning an escaped backslash,
// and then just continue because filepath.Match on
// Windows doesn't allow escaping at all
regStr += escSL
continue
}
if scan.Peek() != scanner.EOF {
regStr += `\` + string(scan.Next())
} else {
regStr += `\`
}
} else {
regStr += string(ch)
}
}
regStr += "$"
res, err := regexp.MatchString(regStr, path)
// Map regexp's error to filepath's so no one knows we're not using filepath
if err != nil {
err = filepath.ErrBadPattern
}
return res, err
}
// CopyFile copies from src to dst until either EOF is reached // CopyFile copies from src to dst until either EOF is reached
// on src or an error occurs. It verifies src exists and remove // on src or an error occurs. It verifies src exists and remove
// the dst if it exists. // the dst if it exists.
...@@ -143,17 +237,6 @@ func CopyFile(src, dst string) (int64, error) { ...@@ -143,17 +237,6 @@ func CopyFile(src, dst string) (int64, error) {
return io.Copy(df, sf) return io.Copy(df, sf)
} }
// GetTotalUsedFds Returns the number of used File Descriptors by
// reading it via /proc filesystem.
func GetTotalUsedFds() int {
if fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil {
logrus.Errorf("Error opening /proc/%d/fd: %s", os.Getpid(), err)
} else {
return len(fds)
}
return -1
}
// ReadSymlinkedDirectory returns the target directory of a symlink. // ReadSymlinkedDirectory returns the target directory of a symlink.
// The target of the symbolic link may not be a file. // The target of the symbolic link may not be a file.
func ReadSymlinkedDirectory(path string) (string, error) { func ReadSymlinkedDirectory(path string) (string, error) {
......
// +build linux freebsd
package fileutils
import (
"fmt"
"io/ioutil"
"os"
"github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus"
)
// GetTotalUsedFds Returns the number of used File Descriptors by
// reading it via /proc filesystem.
func GetTotalUsedFds() int {
if fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil {
logrus.Errorf("Error opening /proc/%d/fd: %s", os.Getpid(), err)
} else {
return len(fds)
}
return -1
}
package fileutils
// GetTotalUsedFds Returns the number of used File Descriptors. Not supported
// on Windows.
func GetTotalUsedFds() int {
return -1
}
package idtools
import (
"bufio"
"fmt"
"os"
"sort"
"strconv"
"strings"
)
// IDMap contains a single entry for user namespace range remapping. An array
// of IDMap entries represents the structure that will be provided to the Linux
// kernel for creating a user namespace.
type IDMap struct {
ContainerID int `json:"container_id"`
HostID int `json:"host_id"`
Size int `json:"size"`
}
type subIDRange struct {
Start int
Length int
}
type ranges []subIDRange
func (e ranges) Len() int { return len(e) }
func (e ranges) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
func (e ranges) Less(i, j int) bool { return e[i].Start < e[j].Start }
const (
subuidFileName string = "/etc/subuid"
subgidFileName string = "/etc/subgid"
)
// MkdirAllAs creates a directory (include any along the path) and then modifies
// ownership to the requested uid/gid. If the directory already exists, this
// function will still change ownership to the requested uid/gid pair.
func MkdirAllAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
return mkdirAs(path, mode, ownerUID, ownerGID, true, true)
}
// MkdirAllNewAs creates a directory (include any along the path) and then modifies
// ownership ONLY of newly created directories to the requested uid/gid. If the
// directories along the path exist, no change of ownership will be performed
func MkdirAllNewAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
return mkdirAs(path, mode, ownerUID, ownerGID, true, false)
}
// MkdirAs creates a directory and then modifies ownership to the requested uid/gid.
// If the directory already exists, this function still changes ownership
func MkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
return mkdirAs(path, mode, ownerUID, ownerGID, false, true)
}
// GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps.
// If the maps are empty, then the root uid/gid will default to "real" 0/0
func GetRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) {
var uid, gid int
if uidMap != nil {
xUID, err := ToHost(0, uidMap)
if err != nil {
return -1, -1, err
}
uid = xUID
}
if gidMap != nil {
xGID, err := ToHost(0, gidMap)
if err != nil {
return -1, -1, err
}
gid = xGID
}
return uid, gid, nil
}
// ToContainer takes an id mapping, and uses it to translate a
// host ID to the remapped ID. If no map is provided, then the translation
// assumes a 1-to-1 mapping and returns the passed in id
func ToContainer(hostID int, idMap []IDMap) (int, error) {
if idMap == nil {
return hostID, nil
}
for _, m := range idMap {
if (hostID >= m.HostID) && (hostID <= (m.HostID + m.Size - 1)) {
contID := m.ContainerID + (hostID - m.HostID)
return contID, nil
}
}
return -1, fmt.Errorf("Host ID %d cannot be mapped to a container ID", hostID)
}
// ToHost takes an id mapping and a remapped ID, and translates the
// ID to the mapped host ID. If no map is provided, then the translation
// assumes a 1-to-1 mapping and returns the passed in id #
func ToHost(contID int, idMap []IDMap) (int, error) {
if idMap == nil {
return contID, nil
}
for _, m := range idMap {
if (contID >= m.ContainerID) && (contID <= (m.ContainerID + m.Size - 1)) {
hostID := m.HostID + (contID - m.ContainerID)
return hostID, nil
}
}
return -1, fmt.Errorf("Container ID %d cannot be mapped to a host ID", contID)
}
// CreateIDMappings takes a requested user and group name and
// using the data from /etc/sub{uid,gid} ranges, creates the
// proper uid and gid remapping ranges for that user/group pair
func CreateIDMappings(username, groupname string) ([]IDMap, []IDMap, error) {
subuidRanges, err := parseSubuid(username)
if err != nil {
return nil, nil, err
}
subgidRanges, err := parseSubgid(groupname)
if err != nil {
return nil, nil, err
}
if len(subuidRanges) == 0 {
return nil, nil, fmt.Errorf("No subuid ranges found for user %q", username)
}
if len(subgidRanges) == 0 {
return nil, nil, fmt.Errorf("No subgid ranges found for group %q", groupname)
}
return createIDMap(subuidRanges), createIDMap(subgidRanges), nil
}
func createIDMap(subidRanges ranges) []IDMap {
idMap := []IDMap{}
// sort the ranges by lowest ID first
sort.Sort(subidRanges)
containerID := 0
for _, idrange := range subidRanges {
idMap = append(idMap, IDMap{
ContainerID: containerID,
HostID: idrange.Start,
Size: idrange.Length,
})
containerID = containerID + idrange.Length
}
return idMap
}
func parseSubuid(username string) (ranges, error) {
return parseSubidFile(subuidFileName, username)
}
func parseSubgid(username string) (ranges, error) {
return parseSubidFile(subgidFileName, username)
}
func parseSubidFile(path, username string) (ranges, error) {
var rangeList ranges
subidFile, err := os.Open(path)
if err != nil {
return rangeList, err
}
defer subidFile.Close()
s := bufio.NewScanner(subidFile)
for s.Scan() {
if err := s.Err(); err != nil {
return rangeList, err
}
text := strings.TrimSpace(s.Text())
if text == "" {
continue
}
parts := strings.Split(text, ":")
if len(parts) != 3 {
return rangeList, fmt.Errorf("Cannot parse subuid/gid information: Format not correct for %s file", path)
}
if parts[0] == username {
// return the first entry for a user; ignores potential for multiple ranges per user
startid, err := strconv.Atoi(parts[1])
if err != nil {
return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
}
length, err := strconv.Atoi(parts[2])
if err != nil {
return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
}
rangeList = append(rangeList, subIDRange{startid, length})
}
}
return rangeList, nil
}
// +build !windows
package idtools
import (
"os"
"path/filepath"
"github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system"
)
func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error {
// make an array containing the original path asked for, plus (for mkAll == true)
// all path components leading up to the complete path that don't exist before we MkdirAll
// so that we can chown all of them properly at the end. If chownExisting is false, we won't
// chown the full directory path if it exists
var paths []string
if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
paths = []string{path}
} else if err == nil && chownExisting {
if err := os.Chown(path, ownerUID, ownerGID); err != nil {
return err
}
// short-circuit--we were called with an existing directory and chown was requested
return nil
} else if err == nil {
// nothing to do; directory path fully exists already and chown was NOT requested
return nil
}
if mkAll {
// walk back to "/" looking for directories which do not exist
// and add them to the paths array for chown after creation
dirPath := path
for {
dirPath = filepath.Dir(dirPath)
if dirPath == "/" {
break
}
if _, err := os.Stat(dirPath); err != nil && os.IsNotExist(err) {
paths = append(paths, dirPath)
}
}
if err := system.MkdirAll(path, mode); err != nil && !os.IsExist(err) {
return err
}
} else {
if err := os.Mkdir(path, mode); err != nil && !os.IsExist(err) {
return err
}
}
// even if it existed, we will chown the requested path + any subpaths that
// didn't exist when we called MkdirAll
for _, pathComponent := range paths {
if err := os.Chown(pathComponent, ownerUID, ownerGID); err != nil {
return err
}
}
return nil
}
// +build windows
package idtools
import (
"os"
"github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system"
)
// Platforms such as Windows do not support the UID/GID concept. So make this
// just a wrapper around system.MkdirAll.
func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error {
if err := system.MkdirAll(path, mode); err != nil && !os.IsExist(err) {
return err
}
return nil
}
package idtools
import (
"fmt"
"os/exec"
"path/filepath"
"strings"
"syscall"
)
// add a user and/or group to Linux /etc/passwd, /etc/group using standard
// Linux distribution commands:
// adduser --uid <id> --shell /bin/login --no-create-home --disabled-login --ingroup <groupname> <username>
// useradd -M -u <id> -s /bin/nologin -N -g <groupname> <username>
// addgroup --gid <id> <groupname>
// groupadd -g <id> <groupname>
const baseUID int = 10000
const baseGID int = 10000
const idMAX int = 65534
var (
userCommand string
groupCommand string
cmdTemplates = map[string]string{
"adduser": "--uid %d --shell /bin/false --no-create-home --disabled-login --ingroup %s %s",
"useradd": "-M -u %d -s /bin/false -N -g %s %s",
"addgroup": "--gid %d %s",
"groupadd": "-g %d %s",
}
)
func init() {
// set up which commands are used for adding users/groups dependent on distro
if _, err := resolveBinary("adduser"); err == nil {
userCommand = "adduser"
} else if _, err := resolveBinary("useradd"); err == nil {
userCommand = "useradd"
}
if _, err := resolveBinary("addgroup"); err == nil {
groupCommand = "addgroup"
} else if _, err := resolveBinary("groupadd"); err == nil {
groupCommand = "groupadd"
}
}
func resolveBinary(binname string) (string, error) {
binaryPath, err := exec.LookPath(binname)
if err != nil {
return "", err
}
resolvedPath, err := filepath.EvalSymlinks(binaryPath)
if err != nil {
return "", err
}
//only return no error if the final resolved binary basename
//matches what was searched for
if filepath.Base(resolvedPath) == binname {
return resolvedPath, nil
}
return "", fmt.Errorf("Binary %q does not resolve to a binary of that name in $PATH (%q)", binname, resolvedPath)
}
// AddNamespaceRangesUser takes a name and finds an unused uid, gid pair
// and calls the appropriate helper function to add the group and then
// the user to the group in /etc/group and /etc/passwd respectively.
// This new user's /etc/sub{uid,gid} ranges will be used for user namespace
// mapping ranges in containers.
func AddNamespaceRangesUser(name string) (int, int, error) {
// Find unused uid, gid pair
uid, err := findUnusedUID(baseUID)
if err != nil {
return -1, -1, fmt.Errorf("Unable to find unused UID: %v", err)
}
gid, err := findUnusedGID(baseGID)
if err != nil {
return -1, -1, fmt.Errorf("Unable to find unused GID: %v", err)
}
// First add the group that we will use
if err := addGroup(name, gid); err != nil {
return -1, -1, fmt.Errorf("Error adding group %q: %v", name, err)
}
// Add the user as a member of the group
if err := addUser(name, uid, name); err != nil {
return -1, -1, fmt.Errorf("Error adding user %q: %v", name, err)
}
return uid, gid, nil
}
func addUser(userName string, uid int, groupName string) error {
if userCommand == "" {
return fmt.Errorf("Cannot add user; no useradd/adduser binary found")
}
args := fmt.Sprintf(cmdTemplates[userCommand], uid, groupName, userName)
return execAddCmd(userCommand, args)
}
func addGroup(groupName string, gid int) error {
if groupCommand == "" {
return fmt.Errorf("Cannot add group; no groupadd/addgroup binary found")
}
args := fmt.Sprintf(cmdTemplates[groupCommand], gid, groupName)
// only error out if the error isn't that the group already exists
// if the group exists then our needs are already met
if err := execAddCmd(groupCommand, args); err != nil && !strings.Contains(err.Error(), "already exists") {
return err
}
return nil
}
func execAddCmd(cmd, args string) error {
execCmd := exec.Command(cmd, strings.Split(args, " ")...)
out, err := execCmd.CombinedOutput()
if err != nil {
return fmt.Errorf("Failed to add user/group with error: %v; output: %q", err, string(out))
}
return nil
}
func findUnusedUID(startUID int) (int, error) {
return findUnused("passwd", startUID)
}
func findUnusedGID(startGID int) (int, error) {
return findUnused("group", startGID)
}
func findUnused(file string, id int) (int, error) {
for {
cmdStr := fmt.Sprintf("cat /etc/%s | cut -d: -f3 | grep '^%d$'", file, id)
cmd := exec.Command("sh", "-c", cmdStr)
if err := cmd.Run(); err != nil {
// if a non-zero return code occurs, then we know the ID was not found
// and is usable
if exiterr, ok := err.(*exec.ExitError); ok {
// The program has exited with an exit code != 0
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
if status.ExitStatus() == 1 {
//no match, we can use this ID
return id, nil
}
}
}
return -1, fmt.Errorf("Error looking in /etc/%s for unused ID: %v", file, err)
}
id++
if id > idMAX {
return -1, fmt.Errorf("Maximum id in %q reached with finding unused numeric ID", file)
}
}
}
// +build !linux
package idtools
import "fmt"
// AddNamespaceRangesUser takes a name and finds an unused uid, gid pair
// and calls the appropriate helper function to add the group and then
// the user to the group in /etc/group and /etc/passwd respectively.
func AddNamespaceRangesUser(name string) (int, int, error) {
return -1, -1, fmt.Errorf("No support for adding users or groups on this OS")
}
package ioutils
import (
"errors"
"io"
"sync"
)
// maxCap is the highest capacity to use in byte slices that buffer data.
const maxCap = 1e6
// blockThreshold is the minimum number of bytes in the buffer which will cause
// a write to BytesPipe to block when allocating a new slice.
const blockThreshold = 1e6
// ErrClosed is returned when Write is called on a closed BytesPipe.
var ErrClosed = errors.New("write to closed BytesPipe")
// BytesPipe is io.ReadWriteCloser which works similarly to pipe(queue).
// All written data may be read at most once. Also, BytesPipe allocates
// and releases new byte slices to adjust to current needs, so the buffer
// won't be overgrown after peak loads.
type BytesPipe struct {
mu sync.Mutex
wait *sync.Cond
buf [][]byte // slice of byte-slices of buffered data
lastRead int // index in the first slice to a read point
bufLen int // length of data buffered over the slices
closeErr error // error to return from next Read. set to nil if not closed.
}
// NewBytesPipe creates new BytesPipe, initialized by specified slice.
// If buf is nil, then it will be initialized with slice which cap is 64.
// buf will be adjusted in a way that len(buf) == 0, cap(buf) == cap(buf).
func NewBytesPipe(buf []byte) *BytesPipe {
if cap(buf) == 0 {
buf = make([]byte, 0, 64)
}
bp := &BytesPipe{
buf: [][]byte{buf[:0]},
}
bp.wait = sync.NewCond(&bp.mu)
return bp
}
// Write writes p to BytesPipe.
// It can allocate new []byte slices in a process of writing.
func (bp *BytesPipe) Write(p []byte) (int, error) {
bp.mu.Lock()
defer bp.mu.Unlock()
written := 0
for {
if bp.closeErr != nil {
return written, ErrClosed
}
// write data to the last buffer
b := bp.buf[len(bp.buf)-1]
// copy data to the current empty allocated area
n := copy(b[len(b):cap(b)], p)
// increment buffered data length
bp.bufLen += n
// include written data in last buffer
bp.buf[len(bp.buf)-1] = b[:len(b)+n]
written += n
// if there was enough room to write all then break
if len(p) == n {
break
}
// more data: write to the next slice
p = p[n:]
// block if too much data is still in the buffer
for bp.bufLen >= blockThreshold {
bp.wait.Wait()
}
// allocate slice that has twice the size of the last unless maximum reached
nextCap := 2 * cap(bp.buf[len(bp.buf)-1])
if nextCap > maxCap {
nextCap = maxCap
}
// add new byte slice to the buffers slice and continue writing
bp.buf = append(bp.buf, make([]byte, 0, nextCap))
}
bp.wait.Broadcast()
return written, nil
}
// CloseWithError causes further reads from a BytesPipe to return immediately.
func (bp *BytesPipe) CloseWithError(err error) error {
bp.mu.Lock()
if err != nil {
bp.closeErr = err
} else {
bp.closeErr = io.EOF
}
bp.wait.Broadcast()
bp.mu.Unlock()
return nil
}
// Close causes further reads from a BytesPipe to return immediately.
func (bp *BytesPipe) Close() error {
return bp.CloseWithError(nil)
}
func (bp *BytesPipe) len() int {
return bp.bufLen - bp.lastRead
}
// Read reads bytes from BytesPipe.
// Data could be read only once.
func (bp *BytesPipe) Read(p []byte) (n int, err error) {
bp.mu.Lock()
defer bp.mu.Unlock()
if bp.len() == 0 {
if bp.closeErr != nil {
return 0, bp.closeErr
}
bp.wait.Wait()
if bp.len() == 0 && bp.closeErr != nil {
return 0, bp.closeErr
}
}
for {
read := copy(p, bp.buf[0][bp.lastRead:])
n += read
bp.lastRead += read
if bp.len() == 0 {
// we have read everything. reset to the beginning.
bp.lastRead = 0
bp.bufLen -= len(bp.buf[0])
bp.buf[0] = bp.buf[0][:0]
break
}
// break if everything was read
if len(p) == read {
break
}
// more buffered data and more asked. read from next slice.
p = p[read:]
bp.lastRead = 0
bp.bufLen -= len(bp.buf[0])
bp.buf[0] = nil // throw away old slice
bp.buf = bp.buf[1:] // switch to next
}
bp.wait.Broadcast()
return
}
...@@ -12,3 +12,11 @@ func FprintfIfNotEmpty(w io.Writer, format, value string) (int, error) { ...@@ -12,3 +12,11 @@ func FprintfIfNotEmpty(w io.Writer, format, value string) (int, error) {
} }
return 0, nil return 0, nil
} }
// FprintfIfTrue prints the boolean value if it's true
func FprintfIfTrue(w io.Writer, format string, ok bool) (int, error) {
if ok {
return fmt.Fprintf(w, format, ok)
}
return 0, nil
}
...@@ -53,7 +53,7 @@ func (r *multiReadSeeker) Seek(offset int64, whence int) (int64, error) { ...@@ -53,7 +53,7 @@ func (r *multiReadSeeker) Seek(offset int64, whence int) (int64, error) {
} }
if rdrOffset == s && i != len(r.readers)-1 { if rdrOffset == s && i != len(r.readers)-1 {
idx += 1 idx++
rdrOffset = 0 rdrOffset = 0
} }
r.pos = &pos{idx, rdrOffset} r.pos = &pos{idx, rdrOffset}
......
package ioutils package ioutils
import ( import (
"bytes"
"crypto/rand"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"io" "io"
"math/big"
"sync" "github.com/fsouza/go-dockerclient/external/golang.org/x/net/context"
"time"
) )
type readCloserWrapper struct { type readCloserWrapper struct {
...@@ -20,6 +17,7 @@ func (r *readCloserWrapper) Close() error { ...@@ -20,6 +17,7 @@ func (r *readCloserWrapper) Close() error {
return r.closer() return r.closer()
} }
// NewReadCloserWrapper returns a new io.ReadCloser.
func NewReadCloserWrapper(r io.Reader, closer func() error) io.ReadCloser { func NewReadCloserWrapper(r io.Reader, closer func() error) io.ReadCloser {
return &readCloserWrapper{ return &readCloserWrapper{
Reader: r, Reader: r,
...@@ -40,6 +38,7 @@ func (r *readerErrWrapper) Read(p []byte) (int, error) { ...@@ -40,6 +38,7 @@ func (r *readerErrWrapper) Read(p []byte) (int, error) {
return n, err return n, err
} }
// NewReaderErrWrapper returns a new io.Reader.
func NewReaderErrWrapper(r io.Reader, closer func()) io.Reader { func NewReaderErrWrapper(r io.Reader, closer func()) io.Reader {
return &readerErrWrapper{ return &readerErrWrapper{
reader: r, reader: r,
...@@ -47,178 +46,7 @@ func NewReaderErrWrapper(r io.Reader, closer func()) io.Reader { ...@@ -47,178 +46,7 @@ func NewReaderErrWrapper(r io.Reader, closer func()) io.Reader {
} }
} }
// bufReader allows the underlying reader to continue to produce // HashData returns the sha256 sum of src.
// output by pre-emptively reading from the wrapped reader.
// This is achieved by buffering this data in bufReader's
// expanding buffer.
type bufReader struct {
sync.Mutex
buf *bytes.Buffer
reader io.Reader
err error
wait sync.Cond
drainBuf []byte
reuseBuf []byte
maxReuse int64
resetTimeout time.Duration
bufLenResetThreshold int64
maxReadDataReset int64
}
func NewBufReader(r io.Reader) *bufReader {
var timeout int
if randVal, err := rand.Int(rand.Reader, big.NewInt(120)); err == nil {
timeout = int(randVal.Int64()) + 180
} else {
timeout = 300
}
reader := &bufReader{
buf: &bytes.Buffer{},
drainBuf: make([]byte, 1024),
reuseBuf: make([]byte, 4096),
maxReuse: 1000,
resetTimeout: time.Second * time.Duration(timeout),
bufLenResetThreshold: 100 * 1024,
maxReadDataReset: 10 * 1024 * 1024,
reader: r,
}
reader.wait.L = &reader.Mutex
go reader.drain()
return reader
}
func NewBufReaderWithDrainbufAndBuffer(r io.Reader, drainBuffer []byte, buffer *bytes.Buffer) *bufReader {
reader := &bufReader{
buf: buffer,
drainBuf: drainBuffer,
reader: r,
}
reader.wait.L = &reader.Mutex
go reader.drain()
return reader
}
func (r *bufReader) drain() {
var (
duration time.Duration
lastReset time.Time
now time.Time
reset bool
bufLen int64
dataSinceReset int64
maxBufLen int64
reuseBufLen int64
reuseCount int64
)
reuseBufLen = int64(len(r.reuseBuf))
lastReset = time.Now()
for {
n, err := r.reader.Read(r.drainBuf)
dataSinceReset += int64(n)
r.Lock()
bufLen = int64(r.buf.Len())
if bufLen > maxBufLen {
maxBufLen = bufLen
}
// Avoid unbounded growth of the buffer over time.
// This has been discovered to be the only non-intrusive
// solution to the unbounded growth of the buffer.
// Alternative solutions such as compression, multiple
// buffers, channels and other similar pieces of code
// were reducing throughput, overall Docker performance
// or simply crashed Docker.
// This solution releases the buffer when specific
// conditions are met to avoid the continuous resizing
// of the buffer for long lived containers.
//
// Move data to the front of the buffer if it's
// smaller than what reuseBuf can store
if bufLen > 0 && reuseBufLen >= bufLen {
n, _ := r.buf.Read(r.reuseBuf)
r.buf.Write(r.reuseBuf[0:n])
// Take action if the buffer has been reused too many
// times and if there's data in the buffer.
// The timeout is also used as means to avoid doing
// these operations more often or less often than
// required.
// The various conditions try to detect heavy activity
// in the buffer which might be indicators of heavy
// growth of the buffer.
} else if reuseCount >= r.maxReuse && bufLen > 0 {
now = time.Now()
duration = now.Sub(lastReset)
timeoutReached := duration >= r.resetTimeout
// The timeout has been reached and the
// buffered data couldn't be moved to the front
// of the buffer, so the buffer gets reset.
if timeoutReached && bufLen > reuseBufLen {
reset = true
}
// The amount of buffered data is too high now,
// reset the buffer.
if timeoutReached && maxBufLen >= r.bufLenResetThreshold {
reset = true
}
// Reset the buffer if a certain amount of
// data has gone through the buffer since the
// last reset.
if timeoutReached && dataSinceReset >= r.maxReadDataReset {
reset = true
}
// The buffered data is moved to a fresh buffer,
// swap the old buffer with the new one and
// reset all counters.
if reset {
newbuf := &bytes.Buffer{}
newbuf.ReadFrom(r.buf)
r.buf = newbuf
lastReset = now
reset = false
dataSinceReset = 0
maxBufLen = 0
reuseCount = 0
}
}
if err != nil {
r.err = err
} else {
r.buf.Write(r.drainBuf[0:n])
}
reuseCount++
r.wait.Signal()
r.Unlock()
callSchedulerIfNecessary()
if err != nil {
break
}
}
}
func (r *bufReader) Read(p []byte) (n int, err error) {
r.Lock()
defer r.Unlock()
for {
n, err = r.buf.Read(p)
if n > 0 {
return n, err
}
if r.err != nil {
return 0, r.err
}
r.wait.Wait()
}
}
func (r *bufReader) Close() error {
closer, ok := r.reader.(io.ReadCloser)
if !ok {
return nil
}
return closer.Close()
}
func HashData(src io.Reader) (string, error) { func HashData(src io.Reader) (string, error) {
h := sha256.New() h := sha256.New()
if _, err := io.Copy(h, src); err != nil { if _, err := io.Copy(h, src); err != nil {
...@@ -227,6 +55,8 @@ func HashData(src io.Reader) (string, error) { ...@@ -227,6 +55,8 @@ func HashData(src io.Reader) (string, error) {
return "sha256:" + hex.EncodeToString(h.Sum(nil)), nil return "sha256:" + hex.EncodeToString(h.Sum(nil)), nil
} }
// OnEOFReader wraps a io.ReadCloser and a function
// the function will run at the end of file or close the file.
type OnEOFReader struct { type OnEOFReader struct {
Rc io.ReadCloser Rc io.ReadCloser
Fn func() Fn func()
...@@ -240,6 +70,7 @@ func (r *OnEOFReader) Read(p []byte) (n int, err error) { ...@@ -240,6 +70,7 @@ func (r *OnEOFReader) Read(p []byte) (n int, err error) {
return return
} }
// Close closes the file and run the function.
func (r *OnEOFReader) Close() error { func (r *OnEOFReader) Close() error {
err := r.Rc.Close() err := r.Rc.Close()
r.runFunc() r.runFunc()
...@@ -252,3 +83,72 @@ func (r *OnEOFReader) runFunc() { ...@@ -252,3 +83,72 @@ func (r *OnEOFReader) runFunc() {
r.Fn = nil r.Fn = nil
} }
} }
// cancelReadCloser wraps an io.ReadCloser with a context for cancelling read
// operations.
type cancelReadCloser struct {
cancel func()
pR *io.PipeReader // Stream to read from
pW *io.PipeWriter
}
// NewCancelReadCloser creates a wrapper that closes the ReadCloser when the
// context is cancelled. The returned io.ReadCloser must be closed when it is
// no longer needed.
func NewCancelReadCloser(ctx context.Context, in io.ReadCloser) io.ReadCloser {
pR, pW := io.Pipe()
// Create a context used to signal when the pipe is closed
doneCtx, cancel := context.WithCancel(context.Background())
p := &cancelReadCloser{
cancel: cancel,
pR: pR,
pW: pW,
}
go func() {
_, err := io.Copy(pW, in)
select {
case <-ctx.Done():
// If the context was closed, p.closeWithError
// was already called. Calling it again would
// change the error that Read returns.
default:
p.closeWithError(err)
}
in.Close()
}()
go func() {
for {
select {
case <-ctx.Done():
p.closeWithError(ctx.Err())
case <-doneCtx.Done():
return
}
}
}()
return p
}
// Read wraps the Read method of the pipe that provides data from the wrapped
// ReadCloser.
func (p *cancelReadCloser) Read(buf []byte) (n int, err error) {
return p.pR.Read(buf)
}
// closeWithError closes the wrapper and its underlying reader. It will
// cause future calls to Read to return err.
func (p *cancelReadCloser) closeWithError(err error) {
p.pW.CloseWithError(err)
p.cancel()
}
// Close closes the wrapper its underlying reader. It will cause
// future calls to Read to return io.EOF.
func (p *cancelReadCloser) Close() error {
p.closeWithError(io.EOF)
return nil
}
// +build !windows
package ioutils
import "io/ioutil"
// TempDir on Unix systems is equivalent to ioutil.TempDir.
func TempDir(dir, prefix string) (string, error) {
return ioutil.TempDir(dir, prefix)
}
// +build windows
package ioutils
import (
"io/ioutil"
"github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/longpath"
)
// TempDir is the equivalent of ioutil.TempDir, except that the result is in Windows longpath format.
func TempDir(dir, prefix string) (string, error) {
tempDir, err := ioutil.TempDir(dir, prefix)
if err != nil {
return "", err
}
return longpath.AddPrefix(tempDir), nil
}
package ioutils package ioutils
import ( import (
"errors"
"io" "io"
"net/http" "net/http"
"sync" "sync"
) )
// WriteFlusher wraps the Write and Flush operation ensuring that every write
// is a flush. In addition, the Close method can be called to intercept
// Read/Write calls if the targets lifecycle has already ended.
type WriteFlusher struct { type WriteFlusher struct {
sync.Mutex mu sync.Mutex
w io.Writer w io.Writer
flusher http.Flusher flusher http.Flusher
flushed bool flushed bool
closed error
// TODO(stevvooe): Use channel for closed instead, remove mutex. Using a
// channel will allow one to properly order the operations.
} }
var errWriteFlusherClosed = errors.New("writeflusher: closed")
func (wf *WriteFlusher) Write(b []byte) (n int, err error) { func (wf *WriteFlusher) Write(b []byte) (n int, err error) {
wf.Lock() wf.mu.Lock()
defer wf.Unlock() defer wf.mu.Unlock()
if wf.closed != nil {
return 0, wf.closed
}
n, err = wf.w.Write(b) n, err = wf.w.Write(b)
wf.flushed = true wf.flush() // every write is a flush.
wf.flusher.Flush()
return n, err return n, err
} }
// Flush the stream immediately. // Flush the stream immediately.
func (wf *WriteFlusher) Flush() { func (wf *WriteFlusher) Flush() {
wf.Lock() wf.mu.Lock()
defer wf.Unlock() defer wf.mu.Unlock()
wf.flush()
}
// flush the stream immediately without taking a lock. Used internally.
func (wf *WriteFlusher) flush() {
if wf.closed != nil {
return
}
wf.flushed = true wf.flushed = true
wf.flusher.Flush() wf.flusher.Flush()
} }
// Flushed returns the state of flushed.
// If it's flushed, return true, or else it return false.
func (wf *WriteFlusher) Flushed() bool { func (wf *WriteFlusher) Flushed() bool {
wf.Lock() // BUG(stevvooe): Remove this method. Its use is inherently racy. Seems to
defer wf.Unlock() // be used to detect whether or a response code has been issued or not.
// Another hook should be used instead.
wf.mu.Lock()
defer wf.mu.Unlock()
return wf.flushed return wf.flushed
} }
// Close closes the write flusher, disallowing any further writes to the
// target. After the flusher is closed, all calls to write or flush will
// result in an error.
func (wf *WriteFlusher) Close() error {
wf.mu.Lock()
defer wf.mu.Unlock()
if wf.closed != nil {
return wf.closed
}
wf.closed = errWriteFlusherClosed
return nil
}
// NewWriteFlusher returns a new WriteFlusher.
func NewWriteFlusher(w io.Writer) *WriteFlusher { func NewWriteFlusher(w io.Writer) *WriteFlusher {
var flusher http.Flusher var flusher http.Flusher
if f, ok := w.(http.Flusher); ok { if f, ok := w.(http.Flusher); ok {
......
...@@ -2,6 +2,7 @@ package ioutils ...@@ -2,6 +2,7 @@ package ioutils
import "io" import "io"
// NopWriter represents a type which write operation is nop.
type NopWriter struct{} type NopWriter struct{}
func (*NopWriter) Write(buf []byte) (int, error) { func (*NopWriter) Write(buf []byte) (int, error) {
...@@ -14,12 +15,15 @@ type nopWriteCloser struct { ...@@ -14,12 +15,15 @@ type nopWriteCloser struct {
func (w *nopWriteCloser) Close() error { return nil } func (w *nopWriteCloser) Close() error { return nil }
// NopWriteCloser returns a nopWriteCloser.
func NopWriteCloser(w io.Writer) io.WriteCloser { func NopWriteCloser(w io.Writer) io.WriteCloser {
return &nopWriteCloser{w} return &nopWriteCloser{w}
} }
// NopFlusher represents a type which flush operation is nop.
type NopFlusher struct{} type NopFlusher struct{}
// Flush is a nop operation.
func (f *NopFlusher) Flush() {} func (f *NopFlusher) Flush() {}
type writeCloserWrapper struct { type writeCloserWrapper struct {
...@@ -31,6 +35,7 @@ func (r *writeCloserWrapper) Close() error { ...@@ -31,6 +35,7 @@ func (r *writeCloserWrapper) Close() error {
return r.closer() return r.closer()
} }
// NewWriteCloserWrapper returns a new io.WriteCloser.
func NewWriteCloserWrapper(r io.Writer, closer func() error) io.WriteCloser { func NewWriteCloserWrapper(r io.Writer, closer func() error) io.WriteCloser {
return &writeCloserWrapper{ return &writeCloserWrapper{
Writer: r, Writer: r,
...@@ -38,7 +43,7 @@ func NewWriteCloserWrapper(r io.Writer, closer func() error) io.WriteCloser { ...@@ -38,7 +43,7 @@ func NewWriteCloserWrapper(r io.Writer, closer func() error) io.WriteCloser {
} }
} }
// Wrap a concrete io.Writer and hold a count of the number // WriteCounter wraps a concrete io.Writer and hold a count of the number
// of bytes written to the writer during a "session". // of bytes written to the writer during a "session".
// This can be convenient when write return is masked // This can be convenient when write return is masked
// (e.g., json.Encoder.Encode()) // (e.g., json.Encoder.Encode())
...@@ -47,6 +52,7 @@ type WriteCounter struct { ...@@ -47,6 +52,7 @@ type WriteCounter struct {
Writer io.Writer Writer io.Writer
} }
// NewWriteCounter returns a new WriteCounter.
func NewWriteCounter(w io.Writer) *WriteCounter { func NewWriteCounter(w io.Writer) *WriteCounter {
return &WriteCounter{ return &WriteCounter{
Writer: w, Writer: w,
......
// longpath introduces some constants and helper functions for handling long paths
// in Windows, which are expected to be prepended with `\\?\` and followed by either
// a drive letter, a UNC server\share, or a volume identifier.
package longpath
import (
"strings"
)
// Prefix is the longpath prefix for Windows file paths.
const Prefix = `\\?\`
// AddPrefix will add the Windows long path prefix to the path provided if
// it does not already have it.
func AddPrefix(path string) string {
if !strings.HasPrefix(path, Prefix) {
if strings.HasPrefix(path, `\\`) {
// This is a UNC path, so we need to add 'UNC' to the path as well.
path = Prefix + `UNC` + path[1:]
} else {
path = Prefix + path
}
}
return path
}
Copyright (c) 2014-2015 The Docker & Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Package mflag (aka multiple-flag) implements command-line flag parsing.
It's an **hacky** fork of the [official golang package](http://golang.org/pkg/flag/)
It adds:
* both short and long flag version
`./example -s red` `./example --string blue`
* multiple names for the same option
```
$>./example -h
Usage of example:
-s, --string="": a simple string
```
___
It is very flexible on purpose, so you can do things like:
```
$>./example -h
Usage of example:
-s, -string, --string="": a simple string
```
Or:
```
$>./example -h
Usage of example:
-oldflag, --newflag="": a simple string
```
You can also hide some flags from the usage, so if we want only `--newflag`:
```
$>./example -h
Usage of example:
--newflag="": a simple string
$>./example -oldflag str
str
```
See [example.go](example/example.go) for more details.
// Package parsers provides helper functions to parse and validate different type
// of string. It can be hosts, unix addresses, tcp addresses, filters, kernel
// operating system versions.
package parsers
import (
"fmt"
"net/url"
"path"
"runtime"
"strconv"
"strings"
)
// ParseHost parses the specified address and returns an address that will be used as the host.
// Depending of the address specified, will use the defaultTCPAddr or defaultUnixAddr
// FIXME: Change this not to receive default value as parameter
func ParseHost(defaultTCPAddr, defaultUnixAddr, addr string) (string, error) {
addr = strings.TrimSpace(addr)
if addr == "" {
if runtime.GOOS != "windows" {
addr = fmt.Sprintf("unix://%s", defaultUnixAddr)
} else {
// Note - defaultTCPAddr already includes tcp:// prefix
addr = defaultTCPAddr
}
}
addrParts := strings.Split(addr, "://")
if len(addrParts) == 1 {
addrParts = []string{"tcp", addrParts[0]}
}
switch addrParts[0] {
case "tcp":
return ParseTCPAddr(addrParts[1], defaultTCPAddr)
case "unix":
return ParseUnixAddr(addrParts[1], defaultUnixAddr)
case "fd":
return addr, nil
default:
return "", fmt.Errorf("Invalid bind address format: %s", addr)
}
}
// ParseUnixAddr parses and validates that the specified address is a valid UNIX
// socket address. It returns a formatted UNIX socket address, either using the
// address parsed from addr, or the contents of defaultAddr if addr is a blank
// string.
func ParseUnixAddr(addr string, defaultAddr string) (string, error) {
addr = strings.TrimPrefix(addr, "unix://")
if strings.Contains(addr, "://") {
return "", fmt.Errorf("Invalid proto, expected unix: %s", addr)
}
if addr == "" {
addr = defaultAddr
}
return fmt.Sprintf("unix://%s", addr), nil
}
// ParseTCPAddr parses and validates that the specified address is a valid TCP
// address. It returns a formatted TCP address, either using the address parsed
// from addr, or the contents of defaultAddr if addr is a blank string.
func ParseTCPAddr(addr string, defaultAddr string) (string, error) {
addr = strings.TrimPrefix(addr, "tcp://")
if strings.Contains(addr, "://") || addr == "" {
return "", fmt.Errorf("Invalid proto, expected tcp: %s", addr)
}
u, err := url.Parse("tcp://" + addr)
if err != nil {
return "", err
}
hostParts := strings.Split(u.Host, ":")
if len(hostParts) != 2 {
return "", fmt.Errorf("Invalid bind address format: %s", addr)
}
host := hostParts[0]
if host == "" {
host = defaultAddr
}
p, err := strconv.Atoi(hostParts[1])
if err != nil && p == 0 {
return "", fmt.Errorf("Invalid bind address format: %s", addr)
}
return fmt.Sprintf("tcp://%s:%d%s", host, p, u.Path), nil
}
// ParseRepositoryTag gets a repos name and returns the right reposName + tag|digest
// The tag can be confusing because of a port in a repository name.
// Ex: localhost.localdomain:5000/samalba/hipache:latest
// Digest ex: localhost:5000/foo/bar@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb
func ParseRepositoryTag(repos string) (string, string) {
n := strings.Index(repos, "@")
if n >= 0 {
parts := strings.Split(repos, "@")
return parts[0], parts[1]
}
n = strings.LastIndex(repos, ":")
if n < 0 {
return repos, ""
}
if tag := repos[n+1:]; !strings.Contains(tag, "/") {
return repos[:n], tag
}
return repos, ""
}
// PartParser parses and validates the specified string (data) using the specified template
// e.g. ip:public:private -> 192.168.0.1:80:8000
func PartParser(template, data string) (map[string]string, error) {
// ip:public:private
var (
templateParts = strings.Split(template, ":")
parts = strings.Split(data, ":")
out = make(map[string]string, len(templateParts))
)
if len(parts) != len(templateParts) {
return nil, fmt.Errorf("Invalid format to parse. %s should match template %s", data, template)
}
for i, t := range templateParts {
value := ""
if len(parts) > i {
value = parts[i]
}
out[t] = value
}
return out, nil
}
// ParseKeyValueOpt parses and validates the specified string as a key/value pair (key=value)
func ParseKeyValueOpt(opt string) (string, string, error) {
parts := strings.SplitN(opt, "=", 2)
if len(parts) != 2 {
return "", "", fmt.Errorf("Unable to parse key/value option: %s", opt)
}
return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil
}
// ParsePortRange parses and validates the specified string as a port-range (8000-9000)
func ParsePortRange(ports string) (uint64, uint64, error) {
if ports == "" {
return 0, 0, fmt.Errorf("Empty string specified for ports.")
}
if !strings.Contains(ports, "-") {
start, err := strconv.ParseUint(ports, 10, 16)
end := start
return start, end, err
}
parts := strings.Split(ports, "-")
start, err := strconv.ParseUint(parts[0], 10, 16)
if err != nil {
return 0, 0, err
}
end, err := strconv.ParseUint(parts[1], 10, 16)
if err != nil {
return 0, 0, err
}
if end < start {
return 0, 0, fmt.Errorf("Invalid range specified for the Port: %s", ports)
}
return start, end, nil
}
// ParseLink parses and validates the specified string as a link format (name:alias)
func ParseLink(val string) (string, string, error) {
if val == "" {
return "", "", fmt.Errorf("empty string specified for links")
}
arr := strings.Split(val, ":")
if len(arr) > 2 {
return "", "", fmt.Errorf("bad format for links: %s", val)
}
if len(arr) == 1 {
return val, val, nil
}
// This is kept because we can actually get an HostConfig with links
// from an already created container and the format is not `foo:bar`
// but `/foo:/c1/bar`
if strings.HasPrefix(arr[0], "/") {
_, alias := path.Split(arr[1])
return arr[0][1:], alias, nil
}
return arr[0], arr[1], nil
}
...@@ -9,19 +9,26 @@ import ( ...@@ -9,19 +9,26 @@ import (
) )
const ( const (
StdWriterPrefixLen = 8 stdWriterPrefixLen = 8
StdWriterFdIndex = 0 stdWriterFdIndex = 0
StdWriterSizeIndex = 4 stdWriterSizeIndex = 4
startingBufLen = 32*1024 + stdWriterPrefixLen + 1
) )
type StdType [StdWriterPrefixLen]byte // StdType prefixes type and length to standard stream.
type StdType [stdWriterPrefixLen]byte
var ( var (
Stdin StdType = StdType{0: 0} // Stdin represents standard input stream type.
Stdout StdType = StdType{0: 1} Stdin = StdType{0: 0}
Stderr StdType = StdType{0: 2} // Stdout represents standard output stream type.
Stdout = StdType{0: 1}
// Stderr represents standard error steam type.
Stderr = StdType{0: 2}
) )
// StdWriter is wrapper of io.Writer with extra customized info.
type StdWriter struct { type StdWriter struct {
io.Writer io.Writer
prefix StdType prefix StdType
...@@ -36,10 +43,10 @@ func (w *StdWriter) Write(buf []byte) (n int, err error) { ...@@ -36,10 +43,10 @@ func (w *StdWriter) Write(buf []byte) (n int, err error) {
binary.BigEndian.PutUint32(w.prefix[4:], uint32(len(buf))) binary.BigEndian.PutUint32(w.prefix[4:], uint32(len(buf)))
n1, err = w.Writer.Write(w.prefix[:]) n1, err = w.Writer.Write(w.prefix[:])
if err != nil { if err != nil {
n = n1 - StdWriterPrefixLen n = n1 - stdWriterPrefixLen
} else { } else {
n2, err = w.Writer.Write(buf) n2, err = w.Writer.Write(buf)
n = n1 + n2 - StdWriterPrefixLen n = n1 + n2 - stdWriterPrefixLen
} }
if n < 0 { if n < 0 {
n = 0 n = 0
...@@ -61,7 +68,7 @@ func NewStdWriter(w io.Writer, t StdType) *StdWriter { ...@@ -61,7 +68,7 @@ func NewStdWriter(w io.Writer, t StdType) *StdWriter {
} }
} }
var ErrInvalidStdHeader = errors.New("Unrecognized input header") var errInvalidStdHeader = errors.New("Unrecognized input header")
// StdCopy is a modified version of io.Copy. // StdCopy is a modified version of io.Copy.
// //
...@@ -75,7 +82,7 @@ var ErrInvalidStdHeader = errors.New("Unrecognized input header") ...@@ -75,7 +82,7 @@ var ErrInvalidStdHeader = errors.New("Unrecognized input header")
// `written` will hold the total number of bytes written to `dstout` and `dsterr`. // `written` will hold the total number of bytes written to `dstout` and `dsterr`.
func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) { func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) {
var ( var (
buf = make([]byte, 32*1024+StdWriterPrefixLen+1) buf = make([]byte, startingBufLen)
bufLen = len(buf) bufLen = len(buf)
nr, nw int nr, nw int
er, ew error er, ew error
...@@ -85,12 +92,12 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) ...@@ -85,12 +92,12 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error)
for { for {
// Make sure we have at least a full header // Make sure we have at least a full header
for nr < StdWriterPrefixLen { for nr < stdWriterPrefixLen {
var nr2 int var nr2 int
nr2, er = src.Read(buf[nr:]) nr2, er = src.Read(buf[nr:])
nr += nr2 nr += nr2
if er == io.EOF { if er == io.EOF {
if nr < StdWriterPrefixLen { if nr < stdWriterPrefixLen {
logrus.Debugf("Corrupted prefix: %v", buf[:nr]) logrus.Debugf("Corrupted prefix: %v", buf[:nr])
return written, nil return written, nil
} }
...@@ -103,7 +110,7 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) ...@@ -103,7 +110,7 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error)
} }
// Check the first byte to know where to write // Check the first byte to know where to write
switch buf[StdWriterFdIndex] { switch buf[stdWriterFdIndex] {
case 0: case 0:
fallthrough fallthrough
case 1: case 1:
...@@ -113,30 +120,30 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) ...@@ -113,30 +120,30 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error)
// Write on stderr // Write on stderr
out = dsterr out = dsterr
default: default:
logrus.Debugf("Error selecting output fd: (%d)", buf[StdWriterFdIndex]) logrus.Debugf("Error selecting output fd: (%d)", buf[stdWriterFdIndex])
return 0, ErrInvalidStdHeader return 0, errInvalidStdHeader
} }
// Retrieve the size of the frame // Retrieve the size of the frame
frameSize = int(binary.BigEndian.Uint32(buf[StdWriterSizeIndex : StdWriterSizeIndex+4])) frameSize = int(binary.BigEndian.Uint32(buf[stdWriterSizeIndex : stdWriterSizeIndex+4]))
logrus.Debugf("framesize: %d", frameSize) logrus.Debugf("framesize: %d", frameSize)
// Check if the buffer is big enough to read the frame. // Check if the buffer is big enough to read the frame.
// Extend it if necessary. // Extend it if necessary.
if frameSize+StdWriterPrefixLen > bufLen { if frameSize+stdWriterPrefixLen > bufLen {
logrus.Debugf("Extending buffer cap by %d (was %d)", frameSize+StdWriterPrefixLen-bufLen+1, len(buf)) logrus.Debugf("Extending buffer cap by %d (was %d)", frameSize+stdWriterPrefixLen-bufLen+1, len(buf))
buf = append(buf, make([]byte, frameSize+StdWriterPrefixLen-bufLen+1)...) buf = append(buf, make([]byte, frameSize+stdWriterPrefixLen-bufLen+1)...)
bufLen = len(buf) bufLen = len(buf)
} }
// While the amount of bytes read is less than the size of the frame + header, we keep reading // While the amount of bytes read is less than the size of the frame + header, we keep reading
for nr < frameSize+StdWriterPrefixLen { for nr < frameSize+stdWriterPrefixLen {
var nr2 int var nr2 int
nr2, er = src.Read(buf[nr:]) nr2, er = src.Read(buf[nr:])
nr += nr2 nr += nr2
if er == io.EOF { if er == io.EOF {
if nr < frameSize+StdWriterPrefixLen { if nr < frameSize+stdWriterPrefixLen {
logrus.Debugf("Corrupted frame: %v", buf[StdWriterPrefixLen:nr]) logrus.Debugf("Corrupted frame: %v", buf[stdWriterPrefixLen:nr])
return written, nil return written, nil
} }
break break
...@@ -148,7 +155,7 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) ...@@ -148,7 +155,7 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error)
} }
// Write the retrieved frame (without header) // Write the retrieved frame (without header)
nw, ew = out.Write(buf[StdWriterPrefixLen : frameSize+StdWriterPrefixLen]) nw, ew = out.Write(buf[stdWriterPrefixLen : frameSize+stdWriterPrefixLen])
if ew != nil { if ew != nil {
logrus.Debugf("Error writing frame: %s", ew) logrus.Debugf("Error writing frame: %s", ew)
return 0, ew return 0, ew
...@@ -161,8 +168,8 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) ...@@ -161,8 +168,8 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error)
written += int64(nw) written += int64(nw)
// Move the rest of the buffer to the beginning // Move the rest of the buffer to the beginning
copy(buf, buf[frameSize+StdWriterPrefixLen:]) copy(buf, buf[frameSize+stdWriterPrefixLen:])
// Move the index // Move the index
nr -= frameSize + StdWriterPrefixLen nr -= frameSize + stdWriterPrefixLen
} }
} }
package system
import (
"os"
"syscall"
"time"
"unsafe"
)
var (
maxTime time.Time
)
func init() {
if unsafe.Sizeof(syscall.Timespec{}.Nsec) == 8 {
// This is a 64 bit timespec
// os.Chtimes limits time to the following
maxTime = time.Unix(0, 1<<63-1)
} else {
// This is a 32 bit timespec
maxTime = time.Unix(1<<31-1, 0)
}
}
// Chtimes changes the access time and modified time of a file at the given path
func Chtimes(name string, atime time.Time, mtime time.Time) error {
unixMinTime := time.Unix(0, 0)
unixMaxTime := maxTime
// If the modified time is prior to the Unix Epoch, or after the
// end of Unix Time, os.Chtimes has undefined behavior
// default to Unix Epoch in this case, just in case
if atime.Before(unixMinTime) || atime.After(unixMaxTime) {
atime = unixMinTime
}
if mtime.Before(unixMinTime) || mtime.After(unixMaxTime) {
mtime = unixMinTime
}
if err := os.Chtimes(name, atime, mtime); err != nil {
return err
}
return nil
}
...@@ -5,5 +5,6 @@ import ( ...@@ -5,5 +5,6 @@ import (
) )
var ( var (
// ErrNotSupportedPlatform means the platform is not supported.
ErrNotSupportedPlatform = errors.New("platform and architecture is not supported") ErrNotSupportedPlatform = errors.New("platform and architecture is not supported")
) )
...@@ -8,11 +8,6 @@ import ( ...@@ -8,11 +8,6 @@ import (
"unsafe" "unsafe"
) )
const (
EVENT_ALL_ACCESS = 0x1F0003
EVENT_MODIFY_STATUS = 0x0002
)
var ( var (
procCreateEvent = modkernel32.NewProc("CreateEventW") procCreateEvent = modkernel32.NewProc("CreateEventW")
procOpenEvent = modkernel32.NewProc("OpenEventW") procOpenEvent = modkernel32.NewProc("OpenEventW")
...@@ -21,13 +16,14 @@ var ( ...@@ -21,13 +16,14 @@ var (
procPulseEvent = modkernel32.NewProc("PulseEvent") procPulseEvent = modkernel32.NewProc("PulseEvent")
) )
// CreateEvent implements win32 CreateEventW func in golang. It will create an event object.
func CreateEvent(eventAttributes *syscall.SecurityAttributes, manualReset bool, initialState bool, name string) (handle syscall.Handle, err error) { func CreateEvent(eventAttributes *syscall.SecurityAttributes, manualReset bool, initialState bool, name string) (handle syscall.Handle, err error) {
namep, _ := syscall.UTF16PtrFromString(name) namep, _ := syscall.UTF16PtrFromString(name)
var _p1 uint32 = 0 var _p1 uint32
if manualReset { if manualReset {
_p1 = 1 _p1 = 1
} }
var _p2 uint32 = 0 var _p2 uint32
if initialState { if initialState {
_p2 = 1 _p2 = 1
} }
...@@ -40,9 +36,10 @@ func CreateEvent(eventAttributes *syscall.SecurityAttributes, manualReset bool, ...@@ -40,9 +36,10 @@ func CreateEvent(eventAttributes *syscall.SecurityAttributes, manualReset bool,
return return
} }
// OpenEvent implements win32 OpenEventW func in golang. It opens an event object.
func OpenEvent(desiredAccess uint32, inheritHandle bool, name string) (handle syscall.Handle, err error) { func OpenEvent(desiredAccess uint32, inheritHandle bool, name string) (handle syscall.Handle, err error) {
namep, _ := syscall.UTF16PtrFromString(name) namep, _ := syscall.UTF16PtrFromString(name)
var _p1 uint32 = 0 var _p1 uint32
if inheritHandle { if inheritHandle {
_p1 = 1 _p1 = 1
} }
...@@ -55,14 +52,17 @@ func OpenEvent(desiredAccess uint32, inheritHandle bool, name string) (handle sy ...@@ -55,14 +52,17 @@ func OpenEvent(desiredAccess uint32, inheritHandle bool, name string) (handle sy
return return
} }
// SetEvent implements win32 SetEvent func in golang.
func SetEvent(handle syscall.Handle) (err error) { func SetEvent(handle syscall.Handle) (err error) {
return setResetPulse(handle, procSetEvent) return setResetPulse(handle, procSetEvent)
} }
// ResetEvent implements win32 ResetEvent func in golang.
func ResetEvent(handle syscall.Handle) (err error) { func ResetEvent(handle syscall.Handle) (err error) {
return setResetPulse(handle, procResetEvent) return setResetPulse(handle, procResetEvent)
} }
// PulseEvent implements win32 PulseEvent func in golang.
func PulseEvent(handle syscall.Handle) (err error) { func PulseEvent(handle syscall.Handle) (err error) {
return setResetPulse(handle, procPulseEvent) return setResetPulse(handle, procPulseEvent)
} }
......
...@@ -4,8 +4,16 @@ package system ...@@ -4,8 +4,16 @@ package system
import ( import (
"os" "os"
"path/filepath"
) )
// MkdirAll creates a directory named path along with any necessary parents,
// with permission specified by attribute perm for all dir created.
func MkdirAll(path string, perm os.FileMode) error { func MkdirAll(path string, perm os.FileMode) error {
return os.MkdirAll(path, perm) return os.MkdirAll(path, perm)
} }
// IsAbs is a platform-specific wrapper for filepath.IsAbs.
func IsAbs(path string) bool {
return filepath.IsAbs(path)
}
...@@ -4,7 +4,9 @@ package system ...@@ -4,7 +4,9 @@ package system
import ( import (
"os" "os"
"path/filepath"
"regexp" "regexp"
"strings"
"syscall" "syscall"
) )
...@@ -62,3 +64,19 @@ func MkdirAll(path string, perm os.FileMode) error { ...@@ -62,3 +64,19 @@ func MkdirAll(path string, perm os.FileMode) error {
} }
return nil return nil
} }
// IsAbs is a platform-specific wrapper for filepath.IsAbs. On Windows,
// golang filepath.IsAbs does not consider a path \windows\system32 as absolute
// as it doesn't start with a drive-letter/colon combination. However, in
// docker we need to verify things such as WORKDIR /windows/system32 in
// a Dockerfile (which gets translated to \windows\system32 when being processed
// by the daemon. This SHOULD be treated as absolute from a docker processing
// perspective.
func IsAbs(path string) bool {
if !filepath.IsAbs(path) {
if !strings.HasPrefix(path, string(os.PathSeparator)) {
return false
}
}
return true
}
...@@ -7,10 +7,10 @@ import ( ...@@ -7,10 +7,10 @@ import (
) )
// Lstat takes a path to a file and returns // Lstat takes a path to a file and returns
// a system.Stat_t type pertaining to that file. // a system.StatT type pertaining to that file.
// //
// Throws an error if the file does not exist // Throws an error if the file does not exist
func Lstat(path string) (*Stat_t, error) { func Lstat(path string) (*StatT, error) {
s := &syscall.Stat_t{} s := &syscall.Stat_t{}
if err := syscall.Lstat(path, s); err != nil { if err := syscall.Lstat(path, s); err != nil {
return nil, err return nil, err
......
...@@ -6,21 +6,17 @@ import ( ...@@ -6,21 +6,17 @@ import (
"os" "os"
) )
// Some explanation for my own sanity, and hopefully maintainers in the
// future.
//
// Lstat calls os.Lstat to get a fileinfo interface back. // Lstat calls os.Lstat to get a fileinfo interface back.
// This is then copied into our own locally defined structure. // This is then copied into our own locally defined structure.
// Note the Linux version uses fromStatT to do the copy back, // Note the Linux version uses fromStatT to do the copy back,
// but that not strictly necessary when already in an OS specific module. // but that not strictly necessary when already in an OS specific module.
func Lstat(path string) (*StatT, error) {
func Lstat(path string) (*Stat_t, error) {
fi, err := os.Lstat(path) fi, err := os.Lstat(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Stat_t{ return &StatT{
name: fi.Name(), name: fi.Name(),
size: fi.Size(), size: fi.Size(),
mode: fi.Mode(), mode: fi.Mode(),
......
...@@ -2,17 +2,12 @@ package system ...@@ -2,17 +2,12 @@ package system
import ( import (
"bufio" "bufio"
"errors"
"io" "io"
"os" "os"
"strconv" "strconv"
"strings" "strings"
"github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/units" "github.com/fsouza/go-dockerclient/external/github.com/docker/go-units"
)
var (
ErrMalformed = errors.New("malformed file")
) )
// ReadMemInfo retrieves memory statistics of the host system and returns a // ReadMemInfo retrieves memory statistics of the host system and returns a
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
package system package system
// ReadMemInfo is not supported on platforms other than linux and windows.
func ReadMemInfo() (*MemInfo, error) { func ReadMemInfo() (*MemInfo, error) {
return nil, ErrNotSupportedPlatform return nil, ErrNotSupportedPlatform
} }
...@@ -7,14 +7,16 @@ import ( ...@@ -7,14 +7,16 @@ import (
) )
// Mknod creates a filesystem node (file, device special file or named pipe) named path // Mknod creates a filesystem node (file, device special file or named pipe) named path
// with attributes specified by mode and dev // with attributes specified by mode and dev.
func Mknod(path string, mode uint32, dev int) error { func Mknod(path string, mode uint32, dev int) error {
return syscall.Mknod(path, mode, dev) return syscall.Mknod(path, mode, dev)
} }
// Mkdev is used to build the value of linux devices (in /dev/) which specifies major
// and minor number of the newly created device special file.
// Linux device nodes are a bit weird due to backwards compat with 16 bit device nodes. // Linux device nodes are a bit weird due to backwards compat with 16 bit device nodes.
// They are, from low to high: the lower 8 bits of the minor, then 12 bits of the major, // They are, from low to high: the lower 8 bits of the minor, then 12 bits of the major,
// then the top 12 bits of the minor // then the top 12 bits of the minor.
func Mkdev(major int64, minor int64) uint32 { func Mkdev(major int64, minor int64) uint32 {
return uint32(((minor & 0xfff00) << 12) | ((major & 0xfff) << 8) | (minor & 0xff)) return uint32(((minor & 0xfff00) << 12) | ((major & 0xfff) << 8) | (minor & 0xff))
} }
...@@ -2,10 +2,12 @@ ...@@ -2,10 +2,12 @@
package system package system
// Mknod is not implemented on Windows.
func Mknod(path string, mode uint32, dev int) error { func Mknod(path string, mode uint32, dev int) error {
return ErrNotSupportedPlatform return ErrNotSupportedPlatform
} }
// Mkdev is not implemented on Windows.
func Mkdev(major int64, minor int64) uint32 { func Mkdev(major int64, minor int64) uint32 {
panic("Mkdev not implemented on Windows.") panic("Mkdev not implemented on Windows.")
} }
// +build !windows
package system
// DefaultPathEnv is unix style list of directories to search for
// executables. Each directory is separated from the next by a colon
// ':' character .
const DefaultPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
// +build windows
package system
// DefaultPathEnv is deliberately empty on Windows as the default path will be set by
// the container. Docker has no context of what the default path should be.
const DefaultPathEnv = ""
...@@ -6,9 +6,9 @@ import ( ...@@ -6,9 +6,9 @@ import (
"syscall" "syscall"
) )
// Stat_t type contains status of a file. It contains metadata // StatT type contains status of a file. It contains metadata
// like permission, owner, group, size, etc about a file // like permission, owner, group, size, etc about a file.
type Stat_t struct { type StatT struct {
mode uint32 mode uint32
uid uint32 uid uint32
gid uint32 gid uint32
...@@ -17,30 +17,37 @@ type Stat_t struct { ...@@ -17,30 +17,37 @@ type Stat_t struct {
mtim syscall.Timespec mtim syscall.Timespec
} }
func (s Stat_t) Mode() uint32 { // Mode returns file's permission mode.
func (s StatT) Mode() uint32 {
return s.mode return s.mode
} }
func (s Stat_t) Uid() uint32 { // UID returns file's user id of owner.
func (s StatT) UID() uint32 {
return s.uid return s.uid
} }
func (s Stat_t) Gid() uint32 { // GID returns file's group id of owner.
func (s StatT) GID() uint32 {
return s.gid return s.gid
} }
func (s Stat_t) Rdev() uint64 { // Rdev returns file's device ID (if it's special file).
func (s StatT) Rdev() uint64 {
return s.rdev return s.rdev
} }
func (s Stat_t) Size() int64 { // Size returns file's size.
func (s StatT) Size() int64 {
return s.size return s.size
} }
func (s Stat_t) Mtim() syscall.Timespec { // Mtim returns file's last modification time.
func (s StatT) Mtim() syscall.Timespec {
return s.mtim return s.mtim
} }
func (s Stat_t) GetLastModification() syscall.Timespec { // GetLastModification returns file's last modification time.
func (s StatT) GetLastModification() syscall.Timespec {
return s.Mtim() return s.Mtim()
} }
...@@ -5,8 +5,8 @@ import ( ...@@ -5,8 +5,8 @@ import (
) )
// fromStatT converts a syscall.Stat_t type to a system.Stat_t type // fromStatT converts a syscall.Stat_t type to a system.Stat_t type
func fromStatT(s *syscall.Stat_t) (*Stat_t, error) { func fromStatT(s *syscall.Stat_t) (*StatT, error) {
return &Stat_t{size: s.Size, return &StatT{size: s.Size,
mode: uint32(s.Mode), mode: uint32(s.Mode),
uid: s.Uid, uid: s.Uid,
gid: s.Gid, gid: s.Gid,
...@@ -18,7 +18,7 @@ func fromStatT(s *syscall.Stat_t) (*Stat_t, error) { ...@@ -18,7 +18,7 @@ func fromStatT(s *syscall.Stat_t) (*Stat_t, error) {
// a system.Stat_t type pertaining to that file. // a system.Stat_t type pertaining to that file.
// //
// Throws an error if the file does not exist // Throws an error if the file does not exist
func Stat(path string) (*Stat_t, error) { func Stat(path string) (*StatT, error) {
s := &syscall.Stat_t{} s := &syscall.Stat_t{}
if err := syscall.Stat(path, s); err != nil { if err := syscall.Stat(path, s); err != nil {
return nil, err return nil, err
......
...@@ -5,8 +5,8 @@ import ( ...@@ -5,8 +5,8 @@ import (
) )
// fromStatT converts a syscall.Stat_t type to a system.Stat_t type // fromStatT converts a syscall.Stat_t type to a system.Stat_t type
func fromStatT(s *syscall.Stat_t) (*Stat_t, error) { func fromStatT(s *syscall.Stat_t) (*StatT, error) {
return &Stat_t{size: s.Size, return &StatT{size: s.Size,
mode: s.Mode, mode: s.Mode,
uid: s.Uid, uid: s.Uid,
gid: s.Gid, gid: s.Gid,
...@@ -14,17 +14,17 @@ func fromStatT(s *syscall.Stat_t) (*Stat_t, error) { ...@@ -14,17 +14,17 @@ func fromStatT(s *syscall.Stat_t) (*Stat_t, error) {
mtim: s.Mtim}, nil mtim: s.Mtim}, nil
} }
// FromStatT exists only on linux, and loads a system.Stat_t from a // FromStatT exists only on linux, and loads a system.StatT from a
// syscal.Stat_t. // syscal.Stat_t.
func FromStatT(s *syscall.Stat_t) (*Stat_t, error) { func FromStatT(s *syscall.Stat_t) (*StatT, error) {
return fromStatT(s) return fromStatT(s)
} }
// Stat takes a path to a file and returns // Stat takes a path to a file and returns
// a system.Stat_t type pertaining to that file. // a system.StatT type pertaining to that file.
// //
// Throws an error if the file does not exist // Throws an error if the file does not exist
func Stat(path string) (*Stat_t, error) { func Stat(path string) (*StatT, error) {
s := &syscall.Stat_t{} s := &syscall.Stat_t{}
if err := syscall.Stat(path, s); err != nil { if err := syscall.Stat(path, s); err != nil {
return nil, err return nil, err
......
// +build solaris
package system
import (
"syscall"
)
// fromStatT creates a system.StatT type from a syscall.Stat_t type
func fromStatT(s *syscall.Stat_t) (*StatT, error) {
return &StatT{size: s.Size,
mode: uint32(s.Mode),
uid: s.Uid,
gid: s.Gid,
rdev: uint64(s.Rdev),
mtim: s.Mtim}, nil
}
// +build !linux,!windows,!freebsd // +build !linux,!windows,!freebsd,!solaris
package system package system
...@@ -6,9 +6,9 @@ import ( ...@@ -6,9 +6,9 @@ import (
"syscall" "syscall"
) )
// fromStatT creates a system.Stat_t type from a syscall.Stat_t type // fromStatT creates a system.StatT type from a syscall.Stat_t type
func fromStatT(s *syscall.Stat_t) (*Stat_t, error) { func fromStatT(s *syscall.Stat_t) (*StatT, error) {
return &Stat_t{size: s.Size, return &StatT{size: s.Size,
mode: uint32(s.Mode), mode: uint32(s.Mode),
uid: s.Uid, uid: s.Uid,
gid: s.Gid, gid: s.Gid,
......
...@@ -7,7 +7,9 @@ import ( ...@@ -7,7 +7,9 @@ import (
"time" "time"
) )
type Stat_t struct { // StatT type contains status of a file. It contains metadata
// like name, permission, size, etc about a file.
type StatT struct {
name string name string
size int64 size int64
mode os.FileMode mode os.FileMode
...@@ -15,22 +17,27 @@ type Stat_t struct { ...@@ -15,22 +17,27 @@ type Stat_t struct {
isDir bool isDir bool
} }
func (s Stat_t) Name() string { // Name returns file's name.
func (s StatT) Name() string {
return s.name return s.name
} }
func (s Stat_t) Size() int64 { // Size returns file's size.
func (s StatT) Size() int64 {
return s.size return s.size
} }
func (s Stat_t) Mode() os.FileMode { // Mode returns file's permission mode.
func (s StatT) Mode() os.FileMode {
return s.mode return s.mode
} }
func (s Stat_t) ModTime() time.Time { // ModTime returns file's last modification time.
func (s StatT) ModTime() time.Time {
return s.modTime return s.modTime
} }
func (s Stat_t) IsDir() bool { // IsDir returns whether file is actually a directory.
func (s StatT) IsDir() bool {
return s.isDir return s.isDir
} }
// +build linux freebsd
package system
import "syscall"
// Unmount is a platform-specific helper function to call
// the unmount syscall.
func Unmount(dest string) error {
return syscall.Unmount(dest, 0)
}
package system
import (
"fmt"
"syscall"
)
// OSVersion is a wrapper for Windows version information
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724439(v=vs.85).aspx
type OSVersion struct {
Version uint32
MajorVersion uint8
MinorVersion uint8
Build uint16
}
// GetOSVersion gets the operating system version on Windows. Note that
// docker.exe must be manifested to get the correct version information.
func GetOSVersion() (OSVersion, error) {
var err error
osv := OSVersion{}
osv.Version, err = syscall.GetVersion()
if err != nil {
return osv, fmt.Errorf("Failed to call GetVersion()")
}
osv.MajorVersion = uint8(osv.Version & 0xFF)
osv.MinorVersion = uint8(osv.Version >> 8 & 0xFF)
osv.Build = uint16(osv.Version >> 16)
return osv, nil
}
// Unmount is a platform-specific helper function to call
// the unmount syscall. Not supported on Windows
func Unmount(dest string) error {
return nil
}
...@@ -6,6 +6,8 @@ import ( ...@@ -6,6 +6,8 @@ import (
"syscall" "syscall"
) )
// Umask sets current process's file mode creation mask to newmask
// and return oldmask.
func Umask(newmask int) (oldmask int, err error) { func Umask(newmask int) (oldmask int, err error) {
return syscall.Umask(newmask), nil return syscall.Umask(newmask), nil
} }
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
package system package system
// Umask is not supported on the windows platform.
func Umask(newmask int) (oldmask int, err error) { func Umask(newmask int) (oldmask int, err error) {
// should not be called on cli code path // should not be called on cli code path
return 0, ErrNotSupportedPlatform return 0, ErrNotSupportedPlatform
......
...@@ -2,10 +2,7 @@ package system ...@@ -2,10 +2,7 @@ package system
import "syscall" import "syscall"
// LUtimesNano is not supported by darwin platform.
func LUtimesNano(path string, ts []syscall.Timespec) error { func LUtimesNano(path string, ts []syscall.Timespec) error {
return ErrNotSupportedPlatform return ErrNotSupportedPlatform
} }
func UtimesNano(path string, ts []syscall.Timespec) error {
return syscall.UtimesNano(path, ts)
}
...@@ -5,6 +5,8 @@ import ( ...@@ -5,6 +5,8 @@ import (
"unsafe" "unsafe"
) )
// LUtimesNano is used to change access and modification time of the specified path.
// It's used for symbol link file because syscall.UtimesNano doesn't support a NOFOLLOW flag atm.
func LUtimesNano(path string, ts []syscall.Timespec) error { func LUtimesNano(path string, ts []syscall.Timespec) error {
var _path *byte var _path *byte
_path, err := syscall.BytePtrFromString(path) _path, err := syscall.BytePtrFromString(path)
...@@ -18,7 +20,3 @@ func LUtimesNano(path string, ts []syscall.Timespec) error { ...@@ -18,7 +20,3 @@ func LUtimesNano(path string, ts []syscall.Timespec) error {
return nil return nil
} }
func UtimesNano(path string, ts []syscall.Timespec) error {
return syscall.UtimesNano(path, ts)
}
...@@ -5,10 +5,12 @@ import ( ...@@ -5,10 +5,12 @@ import (
"unsafe" "unsafe"
) )
// LUtimesNano is used to change access and modification time of the specified path.
// It's used for symbol link file because syscall.UtimesNano doesn't support a NOFOLLOW flag atm.
func LUtimesNano(path string, ts []syscall.Timespec) error { func LUtimesNano(path string, ts []syscall.Timespec) error {
// These are not currently available in syscall // These are not currently available in syscall
AT_FDCWD := -100 atFdCwd := -100
AT_SYMLINK_NOFOLLOW := 0x100 atSymLinkNoFollow := 0x100
var _path *byte var _path *byte
_path, err := syscall.BytePtrFromString(path) _path, err := syscall.BytePtrFromString(path)
...@@ -16,13 +18,9 @@ func LUtimesNano(path string, ts []syscall.Timespec) error { ...@@ -16,13 +18,9 @@ func LUtimesNano(path string, ts []syscall.Timespec) error {
return err return err
} }
if _, _, err := syscall.Syscall6(syscall.SYS_UTIMENSAT, uintptr(AT_FDCWD), uintptr(unsafe.Pointer(_path)), uintptr(unsafe.Pointer(&ts[0])), uintptr(AT_SYMLINK_NOFOLLOW), 0, 0); err != 0 && err != syscall.ENOSYS { if _, _, err := syscall.Syscall6(syscall.SYS_UTIMENSAT, uintptr(atFdCwd), uintptr(unsafe.Pointer(_path)), uintptr(unsafe.Pointer(&ts[0])), uintptr(atSymLinkNoFollow), 0, 0); err != 0 && err != syscall.ENOSYS {
return err return err
} }
return nil return nil
} }
func UtimesNano(path string, ts []syscall.Timespec) error {
return syscall.UtimesNano(path, ts)
}
...@@ -4,10 +4,7 @@ package system ...@@ -4,10 +4,7 @@ package system
import "syscall" import "syscall"
// LUtimesNano is not supported on platforms other than linux, freebsd and darwin.
func LUtimesNano(path string, ts []syscall.Timespec) error { func LUtimesNano(path string, ts []syscall.Timespec) error {
return ErrNotSupportedPlatform return ErrNotSupportedPlatform
} }
func UtimesNano(path string, ts []syscall.Timespec) error {
return ErrNotSupportedPlatform
}
...@@ -5,7 +5,9 @@ import ( ...@@ -5,7 +5,9 @@ import (
"unsafe" "unsafe"
) )
// Returns a nil slice and nil error if the xattr is not set // Lgetxattr retrieves the value of the extended attribute identified by attr
// and associated with the given path in the file system.
// It will returns a nil slice and nil error if the xattr is not set.
func Lgetxattr(path string, attr string) ([]byte, error) { func Lgetxattr(path string, attr string) ([]byte, error) {
pathBytes, err := syscall.BytePtrFromString(path) pathBytes, err := syscall.BytePtrFromString(path)
if err != nil { if err != nil {
...@@ -36,6 +38,8 @@ func Lgetxattr(path string, attr string) ([]byte, error) { ...@@ -36,6 +38,8 @@ func Lgetxattr(path string, attr string) ([]byte, error) {
var _zero uintptr var _zero uintptr
// Lsetxattr sets the value of the extended attribute identified by attr
// and associated with the given path in the file system.
func Lsetxattr(path string, attr string, data []byte, flags int) error { func Lsetxattr(path string, attr string, data []byte, flags int) error {
pathBytes, err := syscall.BytePtrFromString(path) pathBytes, err := syscall.BytePtrFromString(path)
if err != nil { if err != nil {
......
...@@ -2,10 +2,12 @@ ...@@ -2,10 +2,12 @@
package system package system
// Lgetxattr is not supported on platforms other than linux.
func Lgetxattr(path string, attr string) ([]byte, error) { func Lgetxattr(path string, attr string) ([]byte, error) {
return nil, ErrNotSupportedPlatform return nil, ErrNotSupportedPlatform
} }
// Lsetxattr is not supported on platforms other than linux.
func Lsetxattr(path string, attr string, data []byte, flags int) error { func Lsetxattr(path string, attr string, data []byte, flags int) error {
return ErrNotSupportedPlatform return ErrNotSupportedPlatform
} }
package volume
// DefaultDriverName is the driver name used for the driver
// implemented in the local package.
const DefaultDriverName string = "local"
// Driver is for creating and removing volumes.
type Driver interface {
// Name returns the name of the volume driver.
Name() string
// Create makes a new volume with the given id.
Create(string) (Volume, error)
// Remove deletes the volume.
Remove(Volume) error
}
// Volume is a place to store data. It is backed by a specific driver, and can be mounted.
type Volume interface {
// Name returns the name of the volume
Name() string
// DriverName returns the name of the driver which owns this volume.
DriverName() string
// Path returns the absolute path to the volume.
Path() string
// Mount mounts the volume and returns the absolute path to
// where it can be consumed.
Mount() (string, error)
// Unmount unmounts the volume when it is no longer in use.
Unmount() error
}
// read-write modes
var rwModes = map[string]bool{
"rw": true,
"rw,Z": true,
"rw,z": true,
"z,rw": true,
"Z,rw": true,
"Z": true,
"z": true,
}
// read-only modes
var roModes = map[string]bool{
"ro": true,
"ro,Z": true,
"ro,z": true,
"z,ro": true,
"Z,ro": true,
}
// ValidateMountMode will make sure the mount mode is valid.
// returns if it's a valid mount mode and if it's read-write or not.
func ValidateMountMode(mode string) (bool, bool) {
return roModes[mode] || rwModes[mode], rwModes[mode]
}
// ReadWrite tells you if a mode string is a valid read-only mode or not.
func ReadWrite(mode string) bool {
return rwModes[mode]
}
# Contributing to go-units
Want to hack on go-units? Awesome! Here are instructions to get you started.
go-units is a part of the [Docker](https://www.docker.com) project, and follows
the same rules and principles. If you're already familiar with the way
Docker does things, you'll feel right at home.
Otherwise, go read Docker's
[contributions guidelines](https://github.com/docker/docker/blob/master/CONTRIBUTING.md),
[issue triaging](https://github.com/docker/docker/blob/master/project/ISSUE-TRIAGE.md),
[review process](https://github.com/docker/docker/blob/master/project/REVIEWING.md) and
[branches and tags](https://github.com/docker/docker/blob/master/project/BRANCHES-AND-TAGS.md).
### Sign your work
The sign-off is a simple line at the end of the explanation for the patch. Your
signature certifies that you wrote the patch or otherwise have the right to pass
it on as an open-source patch. The rules are pretty simple: if you can certify
the below (from [developercertificate.org](http://developercertificate.org/)):
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
Then you just add a line to every git commit message:
Signed-off-by: Joe Smith <joe.smith@email.com>
Use your real name (sorry, no pseudonyms or anonymous contributions.)
If you set your `user.name` and `user.email` git configs, you can sign your
commit automatically with `git commit -s`.
# go-connections maintainers file
#
# This file describes who runs the docker/go-connections project and how.
# This is a living document - if you see something out of date or missing, speak up!
#
# It is structured to be consumable by both humans and programs.
# To extract its contents programmatically, use any TOML-compliant parser.
#
# This file is compiled into the MAINTAINERS file in docker/opensource.
#
[Org]
[Org."Core maintainers"]
people = [
"calavera",
]
[people]
# A reference list of all people associated with the project.
# All other sections should refer to people by their canonical key
# in the people section.
# ADD YOURSELF HERE IN ALPHABETICAL ORDER
[people.calavera]
Name = "David Calavera"
Email = "david.calavera@gmail.com"
GitHub = "calavera"
[![GoDoc](https://godoc.org/github.com/docker/go-units?status.svg)](https://godoc.org/github.com/docker/go-units)
# Introduction
go-units is a library to transform human friendly measurements into machine friendly values.
## Usage
See the [docs in godoc](https://godoc.org/github.com/docker/go-units) for examples and documentation.
## Copyright and license
Copyright © 2015 Docker, Inc. All rights reserved, except as follows. Code
is released under the Apache 2.0 license. The README.md file, and files in the
"docs" folder are licensed under the Creative Commons Attribution 4.0
International License under the terms and conditions set forth in the file
"LICENSE.docs". You may obtain a duplicate copy of the same license, titled
CC-BY-SA-4.0, at http://creativecommons.org/licenses/by/4.0/.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment