From 8116b86e050b23a6b897d90a0d2f8d409e7ec04c Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 24 Apr 2014 22:01:20 +0200 Subject: [PATCH 001/400] devmapper: Maks createSnapDevice a function, not a method No idea why this was a method. Maybe a cut and paste bug. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- daemon/graphdriver/devmapper/deviceset.go | 2 +- daemon/graphdriver/devmapper/devmapper.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 640bebd32..7940c4a5c 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -588,7 +588,7 @@ func (devices *DeviceSet) AddDevice(hash, baseHash string) error { deviceId := devices.allocateDeviceId() - if err := devices.createSnapDevice(devices.getPoolDevName(), deviceId, baseInfo.Name(), baseInfo.DeviceId); err != nil { + if err := createSnapDevice(devices.getPoolDevName(), deviceId, baseInfo.Name(), baseInfo.DeviceId); err != nil { utils.Debugf("Error creating snap device: %s\n", err) return err } diff --git a/daemon/graphdriver/devmapper/devmapper.go b/daemon/graphdriver/devmapper/devmapper.go index 7317118dc..128fce15e 100644 --- a/daemon/graphdriver/devmapper/devmapper.go +++ b/daemon/graphdriver/devmapper/devmapper.go @@ -546,7 +546,7 @@ func activateDevice(poolName string, name string, deviceId int, size uint64) err return nil } -func (devices *DeviceSet) createSnapDevice(poolName string, deviceId int, baseName string, baseDeviceId int) error { +func createSnapDevice(poolName string, deviceId int, baseName string, baseDeviceId int) error { devinfo, _ := getInfo(baseName) doSuspend := devinfo != nil && devinfo.Exists != 0 From 586a511cb5e47127a8fd173159fcd43e1b823fe7 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 24 Apr 2014 22:17:04 +0200 Subject: [PATCH 002/400] devmapper: Move error detection to devmapper.go This moves the EBUSY detection to devmapper.go, and then returns a real ErrBusy that deviceset uses. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- daemon/graphdriver/devmapper/deviceset.go | 9 +-------- daemon/graphdriver/devmapper/devmapper.go | 7 +++++++ daemon/graphdriver/devmapper/devmapper_log.go | 13 ++++++++++++- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 7940c4a5c..01d1a5d31 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -13,7 +13,6 @@ import ( "path" "path/filepath" "strconv" - "strings" "sync" "syscall" "time" @@ -62,7 +61,6 @@ type DeviceSet struct { TransactionId uint64 NewTransactionId uint64 nextFreeDevice int - sawBusy bool } type DiskUsage struct { @@ -387,10 +385,6 @@ func (devices *DeviceSet) log(level int, file string, line int, dmError int, mes return // Ignore _LOG_DEBUG } - if strings.Contains(message, "busy") { - devices.sawBusy = true - } - utils.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message) } @@ -710,12 +704,11 @@ func (devices *DeviceSet) removeDeviceAndWait(devname string) error { var err error for i := 0; i < 1000; i++ { - devices.sawBusy = false err = removeDevice(devname) if err == nil { break } - if !devices.sawBusy { + if err != ErrBusy { return err } diff --git a/daemon/graphdriver/devmapper/devmapper.go b/daemon/graphdriver/devmapper/devmapper.go index 128fce15e..f064f9d61 100644 --- a/daemon/graphdriver/devmapper/devmapper.go +++ b/daemon/graphdriver/devmapper/devmapper.go @@ -62,6 +62,9 @@ var ( ErrInvalidAddNode = errors.New("Invalide AddNoce type") ErrGetLoopbackBackingFile = errors.New("Unable to get loopback backing file") ErrLoopbackSetCapacity = errors.New("Unable set loopback capacity") + ErrBusy = errors.New("Device is Busy") + + dmSawBusy bool ) type ( @@ -512,7 +515,11 @@ func removeDevice(name string) error { if task == nil { return err } + dmSawBusy = false if err = task.Run(); err != nil { + if dmSawBusy { + return ErrBusy + } return fmt.Errorf("Error running removeDevice") } return nil diff --git a/daemon/graphdriver/devmapper/devmapper_log.go b/daemon/graphdriver/devmapper/devmapper_log.go index 18dde7cca..7f78bbf0d 100644 --- a/daemon/graphdriver/devmapper/devmapper_log.go +++ b/daemon/graphdriver/devmapper/devmapper_log.go @@ -4,12 +4,23 @@ package devmapper import "C" +import ( + "strings" +) + // Due to the way cgo works this has to be in a separate file, as devmapper.go has // definitions in the cgo block, which is incompatible with using "//export" //export DevmapperLogCallback func DevmapperLogCallback(level C.int, file *C.char, line C.int, dm_errno_or_class C.int, message *C.char) { + msg := C.GoString(message) + if level < 7 { + if strings.Contains(msg, "busy") { + dmSawBusy = true + } + } + if dmLogger != nil { - dmLogger.log(int(level), C.GoString(file), int(line), int(dm_errno_or_class), C.GoString(message)) + dmLogger.log(int(level), C.GoString(file), int(line), int(dm_errno_or_class), msg) } } From f26203cf544db07ae64dffaa495e4217976c0786 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 24 Apr 2014 22:36:45 +0200 Subject: [PATCH 003/400] devmapper: Simplify thin pool device id allocation Instead of globally keeping track of the free device ids we just start from 0 each run and handle EEXIST error and try the next one. This way we don't need any global state for the device ids, which means we can read device metadata lazily. This is important for multi-process use of the backend. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- daemon/graphdriver/devmapper/deviceset.go | 27 +++--- daemon/graphdriver/devmapper/devmapper.go | 94 ++++++++++++------- daemon/graphdriver/devmapper/devmapper_log.go | 4 + 3 files changed, 73 insertions(+), 52 deletions(-) diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 01d1a5d31..d3a097961 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -60,7 +60,7 @@ type DeviceSet struct { devicePrefix string TransactionId uint64 NewTransactionId uint64 - nextFreeDevice int + nextDeviceId int } type DiskUsage struct { @@ -156,13 +156,6 @@ func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) { return filename, nil } -func (devices *DeviceSet) allocateDeviceId() int { - // TODO: Add smarter reuse of deleted devices - id := devices.nextFreeDevice - devices.nextFreeDevice = devices.nextFreeDevice + 1 - return id -} - func (devices *DeviceSet) allocateTransactionId() uint64 { devices.NewTransactionId = devices.NewTransactionId + 1 return devices.NewTransactionId @@ -299,10 +292,6 @@ func (devices *DeviceSet) loadMetaData() error { d.Hash = hash d.devices = devices - if d.DeviceId >= devices.nextFreeDevice { - devices.nextFreeDevice = d.DeviceId + 1 - } - // If the transaction id is larger than the actual one we lost the device due to some crash if d.TransactionId > devices.TransactionId { utils.Debugf("Removing lost device %s with id %d", hash, d.TransactionId) @@ -328,14 +317,17 @@ func (devices *DeviceSet) setupBaseImage() error { utils.Debugf("Initializing base device-manager snapshot") - id := devices.allocateDeviceId() + id := devices.nextDeviceId // Create initial device - if err := createDevice(devices.getPoolDevName(), id); err != nil { + if err := createDevice(devices.getPoolDevName(), &id); err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } + // Ids are 24bit, so wrap around + devices.nextDeviceId = (id + 1) & 0xffffff + utils.Debugf("Registering base device (id %v) with FS size %v", id, DefaultBaseFsSize) info, err := devices.registerDevice(id, "", DefaultBaseFsSize) if err != nil { @@ -580,13 +572,16 @@ func (devices *DeviceSet) AddDevice(hash, baseHash string) error { return fmt.Errorf("device %s already exists", hash) } - deviceId := devices.allocateDeviceId() + deviceId := devices.nextDeviceId - if err := createSnapDevice(devices.getPoolDevName(), deviceId, baseInfo.Name(), baseInfo.DeviceId); err != nil { + if err := createSnapDevice(devices.getPoolDevName(), &deviceId, baseInfo.Name(), baseInfo.DeviceId); err != nil { utils.Debugf("Error creating snap device: %s\n", err) return err } + // Ids are 24bit, so wrap around + devices.nextDeviceId = (deviceId + 1) & 0xffffff + if _, err := devices.registerDevice(deviceId, hash, baseInfo.Size); err != nil { deleteDevice(devices.getPoolDevName(), deviceId) utils.Debugf("Error registering device: %s\n", err) diff --git a/daemon/graphdriver/devmapper/devmapper.go b/daemon/graphdriver/devmapper/devmapper.go index f064f9d61..86af653b4 100644 --- a/daemon/graphdriver/devmapper/devmapper.go +++ b/daemon/graphdriver/devmapper/devmapper.go @@ -64,7 +64,8 @@ var ( ErrLoopbackSetCapacity = errors.New("Unable set loopback capacity") ErrBusy = errors.New("Device is Busy") - dmSawBusy bool + dmSawBusy bool + dmSawExist bool ) type ( @@ -467,23 +468,33 @@ func resumeDevice(name string) error { return nil } -func createDevice(poolName string, deviceId int) error { - utils.Debugf("[devmapper] createDevice(poolName=%v, deviceId=%v)", poolName, deviceId) - task, err := createTask(DeviceTargetMsg, poolName) - if task == nil { - return err - } +func createDevice(poolName string, deviceId *int) error { + utils.Debugf("[devmapper] createDevice(poolName=%v, deviceId=%v)", poolName, *deviceId) - if err := task.SetSector(0); err != nil { - return fmt.Errorf("Can't set sector") - } + for { + task, err := createTask(DeviceTargetMsg, poolName) + if task == nil { + return err + } - if err := task.SetMessage(fmt.Sprintf("create_thin %d", deviceId)); err != nil { - return fmt.Errorf("Can't set message") - } + if err := task.SetSector(0); err != nil { + return fmt.Errorf("Can't set sector") + } - if err := task.Run(); err != nil { - return fmt.Errorf("Error running createDevice") + if err := task.SetMessage(fmt.Sprintf("create_thin %d", *deviceId)); err != nil { + return fmt.Errorf("Can't set message") + } + + dmSawExist = false + if err := task.Run(); err != nil { + if dmSawExist { + // Already exists, try next id + *deviceId++ + continue + } + return fmt.Errorf("Error running createDevice") + } + break } return nil } @@ -553,7 +564,7 @@ func activateDevice(poolName string, name string, deviceId int, size uint64) err return nil } -func createSnapDevice(poolName string, deviceId int, baseName string, baseDeviceId int) error { +func createSnapDevice(poolName string, deviceId *int, baseName string, baseDeviceId int) error { devinfo, _ := getInfo(baseName) doSuspend := devinfo != nil && devinfo.Exists != 0 @@ -563,33 +574,44 @@ func createSnapDevice(poolName string, deviceId int, baseName string, baseDevice } } - task, err := createTask(DeviceTargetMsg, poolName) - if task == nil { - if doSuspend { - resumeDevice(baseName) + for { + task, err := createTask(DeviceTargetMsg, poolName) + if task == nil { + if doSuspend { + resumeDevice(baseName) + } + return err } - return err - } - if err := task.SetSector(0); err != nil { - if doSuspend { - resumeDevice(baseName) + if err := task.SetSector(0); err != nil { + if doSuspend { + resumeDevice(baseName) + } + return fmt.Errorf("Can't set sector") } - return fmt.Errorf("Can't set sector") - } - if err := task.SetMessage(fmt.Sprintf("create_snap %d %d", deviceId, baseDeviceId)); err != nil { - if doSuspend { - resumeDevice(baseName) + if err := task.SetMessage(fmt.Sprintf("create_snap %d %d", *deviceId, baseDeviceId)); err != nil { + if doSuspend { + resumeDevice(baseName) + } + return fmt.Errorf("Can't set message") } - return fmt.Errorf("Can't set message") - } - if err := task.Run(); err != nil { - if doSuspend { - resumeDevice(baseName) + dmSawExist = false + if err := task.Run(); err != nil { + if dmSawExist { + // Already exists, try next id + *deviceId++ + continue + } + + if doSuspend { + resumeDevice(baseName) + } + return fmt.Errorf("Error running DeviceCreate (createSnapDevice)") } - return fmt.Errorf("Error running DeviceCreate (createSnapDevice)") + + break } if doSuspend { diff --git a/daemon/graphdriver/devmapper/devmapper_log.go b/daemon/graphdriver/devmapper/devmapper_log.go index 7f78bbf0d..cdeaed252 100644 --- a/daemon/graphdriver/devmapper/devmapper_log.go +++ b/daemon/graphdriver/devmapper/devmapper_log.go @@ -18,6 +18,10 @@ func DevmapperLogCallback(level C.int, file *C.char, line C.int, dm_errno_or_cla if strings.Contains(msg, "busy") { dmSawBusy = true } + + if strings.Contains(msg, "File exists") { + dmSawExist = true + } } if dmLogger != nil { From 6d631968fa095734da5e3483c5d7c43fd5b87146 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 24 Apr 2014 23:49:44 +0200 Subject: [PATCH 004/400] devmapper: Store metadata in one file per device This allows multiple instances of the backend in different containers to access devices (although generally only one can modify/create them). Any old metadata is converted on the first run. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- daemon/graphdriver/devmapper/deviceset.go | 186 +++++++++++--------- daemon/graphdriver/devmapper/driver_test.go | 4 - 2 files changed, 107 insertions(+), 83 deletions(-) diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index d3a097961..3faf3d5bf 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -106,7 +106,19 @@ func (devices *DeviceSet) loopbackDir() string { return path.Join(devices.root, "devicemapper") } -func (devices *DeviceSet) jsonFile() string { +func (devices *DeviceSet) metadataDir() string { + return path.Join(devices.root, "metadata") +} + +func (devices *DeviceSet) metadataFile(info *DevInfo) string { + file := info.Hash + if file == "" { + file = "base" + } + return path.Join(devices.metadataDir(), file) +} + +func (devices *DeviceSet) oldMetadataFile() string { return path.Join(devices.loopbackDir(), "json") } @@ -161,14 +173,19 @@ func (devices *DeviceSet) allocateTransactionId() uint64 { return devices.NewTransactionId } -func (devices *DeviceSet) saveMetadata() error { - devices.devicesLock.Lock() - jsonData, err := json.Marshal(devices.MetaData) - devices.devicesLock.Unlock() +func (devices *DeviceSet) removeMetadata(info *DevInfo) error { + if err := osRemoveAll(devices.metadataFile(info)); err != nil { + return fmt.Errorf("Error removing metadata file %s: %s", devices.metadataFile(info), err) + } + return nil +} + +func (devices *DeviceSet) saveMetadata(info *DevInfo) error { + jsonData, err := json.Marshal(info) if err != nil { return fmt.Errorf("Error encoding metadata to json: %s", err) } - tmpFile, err := ioutil.TempFile(filepath.Dir(devices.jsonFile()), ".json") + tmpFile, err := ioutil.TempFile(devices.metadataDir(), ".tmp") if err != nil { return fmt.Errorf("Error creating metadata file: %s", err) } @@ -186,7 +203,7 @@ func (devices *DeviceSet) saveMetadata() error { if err := tmpFile.Close(); err != nil { return fmt.Errorf("Error closing metadata file %s: %s", tmpFile.Name(), err) } - if err := osRename(tmpFile.Name(), devices.jsonFile()); err != nil { + if err := osRename(tmpFile.Name(), devices.metadataFile(info)); err != nil { return fmt.Errorf("Error committing metadata file %s: %s", tmpFile.Name(), err) } @@ -204,7 +221,12 @@ func (devices *DeviceSet) lookupDevice(hash string) (*DevInfo, error) { defer devices.devicesLock.Unlock() info := devices.Devices[hash] if info == nil { - return nil, fmt.Errorf("Unknown device %s", hash) + info = devices.loadMetadata(hash) + if info == nil { + return nil, fmt.Errorf("Unknown device %s", hash) + } + + devices.Devices[hash] = info } return info, nil } @@ -224,7 +246,7 @@ func (devices *DeviceSet) registerDevice(id int, hash string, size uint64) (*Dev devices.Devices[hash] = info devices.devicesLock.Unlock() - if err := devices.saveMetadata(); err != nil { + if err := devices.saveMetadata(info); err != nil { // Try to remove unused device devices.devicesLock.Lock() delete(devices.Devices, hash) @@ -259,9 +281,7 @@ func (devices *DeviceSet) createFilesystem(info *DevInfo) error { return nil } -func (devices *DeviceSet) loadMetaData() error { - utils.Debugf("loadMetadata()") - defer utils.Debugf("loadMetadata END") +func (devices *DeviceSet) initMetaData() error { _, _, _, params, err := getStatus(devices.getPoolName()) if err != nil { utils.Debugf("\n--->Err: %s\n", err) @@ -274,35 +294,64 @@ func (devices *DeviceSet) loadMetaData() error { } devices.NewTransactionId = devices.TransactionId - jsonData, err := ioutil.ReadFile(devices.jsonFile()) + // Migrate old metadatafile + + jsonData, err := ioutil.ReadFile(devices.oldMetadataFile()) if err != nil && !osIsNotExist(err) { utils.Debugf("\n--->Err: %s\n", err) return err } - devices.MetaData.Devices = make(map[string]*DevInfo) if jsonData != nil { - if err := json.Unmarshal(jsonData, &devices.MetaData); err != nil { + m := MetaData{Devices: make(map[string]*DevInfo)} + + if err := json.Unmarshal(jsonData, &m); err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } - } - for hash, d := range devices.Devices { - d.Hash = hash - d.devices = devices + for hash, info := range m.Devices { + info.Hash = hash - // If the transaction id is larger than the actual one we lost the device due to some crash - if d.TransactionId > devices.TransactionId { - utils.Debugf("Removing lost device %s with id %d", hash, d.TransactionId) - delete(devices.Devices, hash) + // If the transaction id is larger than the actual one we lost the device due to some crash + if info.TransactionId <= devices.TransactionId { + devices.saveMetadata(info) + } } + if err := osRename(devices.oldMetadataFile(), devices.oldMetadataFile()+".migrated"); err != nil { + return err + } + } + return nil } +func (devices *DeviceSet) loadMetadata(hash string) *DevInfo { + info := &DevInfo{Hash: hash, devices: devices} + + jsonData, err := ioutil.ReadFile(devices.metadataFile(info)) + if err != nil { + return nil + } + + if err := json.Unmarshal(jsonData, &info); err != nil { + return nil + } + + fmt.Printf("Loaded metadata %v\n", info) + + // If the transaction id is larger than the actual one we lost the device due to some crash + if info.TransactionId > devices.TransactionId { + return nil + } + + return info +} + func (devices *DeviceSet) setupBaseImage() error { oldInfo, _ := devices.lookupDevice("") + utils.Debugf("oldInfo: %p", oldInfo) if oldInfo != nil && oldInfo.Initialized { return nil } @@ -349,7 +398,7 @@ func (devices *DeviceSet) setupBaseImage() error { } info.Initialized = true - if err = devices.saveMetadata(); err != nil { + if err = devices.saveMetadata(info); err != nil { info.Initialized = false utils.Debugf("\n--->Err: %s\n", err) return err @@ -457,29 +506,7 @@ func (devices *DeviceSet) ResizePool(size int64) error { func (devices *DeviceSet) initDevmapper(doInit bool) error { logInit(devices) - // Make sure the sparse images exist in /devicemapper/data and - // /devicemapper/metadata - - hasData := devices.hasImage("data") - hasMetadata := devices.hasImage("metadata") - - if !doInit && !hasData { - return errors.New("Loopback data file not found") - } - - if !doInit && !hasMetadata { - return errors.New("Loopback metadata file not found") - } - - createdLoopback := !hasData || !hasMetadata - data, err := devices.ensureImage("data", DefaultDataLoopbackSize) - if err != nil { - utils.Debugf("Error device ensureImage (data): %s\n", err) - return err - } - metadata, err := devices.ensureImage("metadata", DefaultMetaDataLoopbackSize) - if err != nil { - utils.Debugf("Error device ensureImage (metadata): %s\n", err) + if err := osMkdirAll(devices.metadataDir(), 0700); err != nil && !osIsExist(err) { return err } @@ -512,10 +539,38 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { // so we add this badhack to make sure it closes itself setCloseOnExec("/dev/mapper/control") + // Make sure the sparse images exist in /devicemapper/data and + // /devicemapper/metadata + + createdLoopback := false + // If the pool doesn't exist, create it if info.Exists == 0 { utils.Debugf("Pool doesn't exist. Creating it.") + hasData := devices.hasImage("data") + hasMetadata := devices.hasImage("metadata") + + if !doInit && !hasData { + return errors.New("Loopback data file not found") + } + + if !doInit && !hasMetadata { + return errors.New("Loopback metadata file not found") + } + + createdLoopback = !hasData || !hasMetadata + data, err := devices.ensureImage("data", DefaultDataLoopbackSize) + if err != nil { + utils.Debugf("Error device ensureImage (data): %s\n", err) + return err + } + metadata, err := devices.ensureImage("metadata", DefaultMetaDataLoopbackSize) + if err != nil { + utils.Debugf("Error device ensureImage (metadata): %s\n", err) + return err + } + dataFile, err := attachLoopDevice(data) if err != nil { utils.Debugf("\n--->Err: %s\n", err) @@ -537,9 +592,9 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { } // If we didn't just create the data or metadata image, we need to - // load the metadata from the existing file. + // load the transaction id and migrate old metadata if !createdLoopback { - if err = devices.loadMetaData(); err != nil { + if err = devices.initMetaData(); err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } @@ -608,14 +663,6 @@ func (devices *DeviceSet) deleteDevice(info *DevInfo) error { } } - if info.Initialized { - info.Initialized = false - if err := devices.saveMetadata(); err != nil { - utils.Debugf("Error saving meta data: %s\n", err) - return err - } - } - if err := deleteDevice(devices.getPoolDevName(), info.DeviceId); err != nil { utils.Debugf("Error deleting device: %s\n", err) return err @@ -626,11 +673,11 @@ func (devices *DeviceSet) deleteDevice(info *DevInfo) error { delete(devices.Devices, info.Hash) devices.devicesLock.Unlock() - if err := devices.saveMetadata(); err != nil { + if err := devices.removeMetadata(info); err != nil { devices.devicesLock.Lock() devices.Devices[info.Hash] = info devices.devicesLock.Unlock() - utils.Debugf("Error saving meta data: %s\n", err) + utils.Debugf("Error removing meta data: %s\n", err) return err } @@ -873,7 +920,7 @@ func (devices *DeviceSet) MountDevice(hash, path string, mountLabel string) erro info.mountCount = 1 info.mountPath = path - return devices.setInitialized(info) + return nil } func (devices *DeviceSet) UnmountDevice(hash string) error { @@ -924,14 +971,6 @@ func (devices *DeviceSet) HasDevice(hash string) bool { return info != nil } -func (devices *DeviceSet) HasInitializedDevice(hash string) bool { - devices.Lock() - defer devices.Unlock() - - info, _ := devices.lookupDevice(hash) - return info != nil && info.Initialized -} - func (devices *DeviceSet) HasActivatedDevice(hash string) bool { info, _ := devices.lookupDevice(hash) if info == nil { @@ -948,17 +987,6 @@ func (devices *DeviceSet) HasActivatedDevice(hash string) bool { return devinfo != nil && devinfo.Exists != 0 } -func (devices *DeviceSet) setInitialized(info *DevInfo) error { - info.Initialized = true - if err := devices.saveMetadata(); err != nil { - info.Initialized = false - utils.Debugf("\n--->Err: %s\n", err) - return err - } - - return nil -} - func (devices *DeviceSet) List() []string { devices.Lock() defer devices.Unlock() diff --git a/daemon/graphdriver/devmapper/driver_test.go b/daemon/graphdriver/devmapper/driver_test.go index 77e8a6013..af366ef29 100644 --- a/daemon/graphdriver/devmapper/driver_test.go +++ b/daemon/graphdriver/devmapper/driver_test.go @@ -820,10 +820,6 @@ func TestGetReturnsValidDevice(t *testing.T) { if !d.HasActivatedDevice("1") { t.Fatalf("Expected id 1 to be activated") } - - if !d.HasInitializedDevice("1") { - t.Fatalf("Expected id 1 to be initialized") - } } func TestDriverGetSize(t *testing.T) { From 6893689336e3cc325d1e6bcc6d61955418186936 Mon Sep 17 00:00:00 2001 From: Hobofan Date: Mon, 28 Apr 2014 18:30:43 +0200 Subject: [PATCH 005/400] reapply Cmd value even if CmdAdd returns early Docker-DCO-1.1-Signed-off-by: Maximilian Goisser (github: hobofan) --- server/buildfile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/buildfile.go b/server/buildfile.go index 8466f4290..019c47977 100644 --- a/server/buildfile.go +++ b/server/buildfile.go @@ -482,6 +482,7 @@ func (b *buildFile) CmdAdd(args string) error { cmd := b.config.Cmd b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)} + defer func(cmd []string) { b.config.Cmd = cmd }(cmd) b.config.Image = b.image var ( @@ -617,7 +618,6 @@ func (b *buildFile) CmdAdd(args string) error { if err := b.commit(container.ID, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil { return err } - b.config.Cmd = cmd return nil } From 21b42dfcfd15ee6757bcba57acb511d5ce60a3ba Mon Sep 17 00:00:00 2001 From: Kato Kazuyoshi Date: Tue, 29 Apr 2014 08:31:58 +0900 Subject: [PATCH 006/400] UTIME_OMIT is only available on Linux Docker-DCO-1.1-Signed-off-by: Kato Kazuyoshi (github: kzys) --- archive/diff.go | 10 ---------- archive/time_linux.go | 16 ++++++++++++++++ archive/time_unsupported.go | 16 ++++++++++++++++ 3 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 archive/time_linux.go create mode 100644 archive/time_unsupported.go diff --git a/archive/diff.go b/archive/diff.go index 87e8ac7dc..49d8cb498 100644 --- a/archive/diff.go +++ b/archive/diff.go @@ -9,7 +9,6 @@ import ( "path/filepath" "strings" "syscall" - "time" ) // Linux device nodes are a bit weird due to backwards compat with 16 bit device nodes. @@ -18,15 +17,6 @@ import ( func mkdev(major int64, minor int64) uint32 { return uint32(((minor & 0xfff00) << 12) | ((major & 0xfff) << 8) | (minor & 0xff)) } -func timeToTimespec(time time.Time) (ts syscall.Timespec) { - if time.IsZero() { - // Return UTIME_OMIT special value - ts.Sec = 0 - ts.Nsec = ((1 << 30) - 2) - return - } - return syscall.NsecToTimespec(time.UnixNano()) -} // ApplyLayer parses a diff in the standard layer format from `layer`, and // applies it to the directory `dest`. diff --git a/archive/time_linux.go b/archive/time_linux.go new file mode 100644 index 000000000..3448569b1 --- /dev/null +++ b/archive/time_linux.go @@ -0,0 +1,16 @@ +package archive + +import ( + "syscall" + "time" +) + +func timeToTimespec(time time.Time) (ts syscall.Timespec) { + if time.IsZero() { + // Return UTIME_OMIT special value + ts.Sec = 0 + ts.Nsec = ((1 << 30) - 2) + return + } + return syscall.NsecToTimespec(time.UnixNano()) +} diff --git a/archive/time_unsupported.go b/archive/time_unsupported.go new file mode 100644 index 000000000..e85aac054 --- /dev/null +++ b/archive/time_unsupported.go @@ -0,0 +1,16 @@ +// +build !linux + +package archive + +import ( + "syscall" + "time" +) + +func timeToTimespec(time time.Time) (ts syscall.Timespec) { + nsec := int64(0) + if !time.IsZero() { + nsec = time.UnixNano() + } + return syscall.NsecToTimespec(nsec) +} From 659305085f551e39143636b1cfb8351a63cbab6f Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 28 Apr 2014 23:14:23 -0600 Subject: [PATCH 007/400] Update hack/dind to mount cgroups on "/cgroup" instead of "/sys/fs/cgroup" for better compatibility Fixes #5122 Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/dind | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/dind b/hack/dind index 94147f532..e6ec59b38 100755 --- a/hack/dind +++ b/hack/dind @@ -10,7 +10,7 @@ # Usage: dind CMD [ARG...] # First, make sure that cgroups are mounted correctly. -CGROUP=/sys/fs/cgroup +CGROUP=/cgroup [ -d $CGROUP ] || mkdir $CGROUP From 48dbee082403a57b4dbc344e12b6724016d64b72 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Tue, 29 Apr 2014 14:52:01 +1000 Subject: [PATCH 008/400] modernise the MAINTAINER process documentation to line up with what I understand it to be Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- hack/MAINTAINERS.md | 26 ++++++++++++++++---------- hack/getmaintainer.sh | 10 +++++++--- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/hack/MAINTAINERS.md b/hack/MAINTAINERS.md index be3117c86..9dbdf99d9 100644 --- a/hack/MAINTAINERS.md +++ b/hack/MAINTAINERS.md @@ -53,14 +53,17 @@ All decisions affecting docker, big and small, follow the same 3 steps: * Step 2: Discuss the pull request. Anyone can do this. -* Step 3: Accept or refuse a pull request. The relevant maintainer does this (see below "Who decides what?") +* Step 3: Accept (`LGTM`) or refuse a pull request. The relevant maintainers do +this (see below "Who decides what?") ## Who decides what? -So all decisions are pull requests, and the relevant maintainer makes -the decision by accepting or refusing the pull request. But how do we -identify the relevant maintainer for a given pull request? +All decisions are pull requests, and the relevant maintainers make +decisions by accepting or refusing the pull request. Review and acceptance +by anyone is denoted by adding a comment in the pull request: `LGTM`. +However, only currently listed `MAINTAINERS` are counted towards the required +majority. Docker follows the timeless, highly efficient and totally unfair system known as [Benevolent dictator for @@ -70,19 +73,22 @@ decisions are made by default by Solomon. Since making every decision myself would be highly un-scalable, in practice decisions are spread across multiple maintainers. -The relevant maintainer for a pull request is assigned in 3 steps: +The relevant maintainers for a pull request can be worked out in 2 steps: -* Step 1: Determine the subdirectory affected by the pull request. This +* Step 1: Determine the subdirectories affected by the pull request. This might be `src/registry`, `docs/source/api`, or any other part of the repo. * Step 2: Find the `MAINTAINERS` file which affects this directory. If the directory itself does not have a `MAINTAINERS` file, work your way up the repo hierarchy until you find one. -* Step 3: The first maintainer listed is the primary maintainer. The - pull request is assigned to him. He may assign it to other listed - maintainers, at his discretion. +There is also a `hacks/getmaintainers.sh` script that will print out the +maintainers for a specified directory. +### I'm a maintainer, and I'm going on holiday + +Please let your co-maintainers and other contributors know by raising a pull +request that comments out your `MAINTAINERS` file entry using a `#`. ### I'm a maintainer, should I make pull requests too? @@ -91,7 +97,7 @@ made through a pull request. ### Who assigns maintainers? -Solomon. +Solomon has final `LGTM` approval for all pull requests to `MAINTAINERS` files. ### How is this process changed? diff --git a/hack/getmaintainer.sh b/hack/getmaintainer.sh index 2c24bacc8..ca532d42e 100755 --- a/hack/getmaintainer.sh +++ b/hack/getmaintainer.sh @@ -1,4 +1,5 @@ -#!/bin/sh +#!/usr/bin/env bash +set -e if [ $# -ne 1 ]; then echo >&2 "Usage: $0 PATH" @@ -34,6 +35,7 @@ while true; do fi done; } < MAINTAINERS + break fi if [ -d .git ]; then break @@ -46,13 +48,15 @@ done PRIMARY="${MAINTAINERS[0]}" PRIMARY_FIRSTNAME=$(echo $PRIMARY | cut -d' ' -f1) +LGTM_COUNT=${#MAINTAINERS[@]} +LGTM_COUNT=$((LGTM_COUNT%2 +1)) firstname() { echo $1 | cut -d' ' -f1 } -echo "--- $PRIMARY is the PRIMARY MAINTAINER of $1. Assign pull requests to him." -echo "$(firstname $PRIMARY) may assign pull requests to the following secondary maintainers:" +echo "A pull request in $1 will need $LGTM_COUNT LGTM's to be merged." +echo "--- $PRIMARY is the PRIMARY MAINTAINER of $1." for SECONDARY in "${MAINTAINERS[@]:1}"; do echo "--- $SECONDARY" done From b06ad88ca0a94c63f0c45d00e3478e5e7fd808fa Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Fri, 25 Apr 2014 20:41:48 +1000 Subject: [PATCH 009/400] Don't add yourself to the AUTHORs file - its generated Last time I discussed this with @tianon, the AUTHORS was being regenerated regularly, so we could remove this step. Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- CONTRIBUTING.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0e8b98122..69a578f46 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -104,10 +104,8 @@ same commit so that a revert would remove all traces of the feature or fix. Commits that fix or close an issue should include a reference like `Closes #XXX` or `Fixes #XXX`, which will automatically close the issue when merged. -Add your name to the AUTHORS file, but make sure the list is sorted and your -name and email address match your git configuration. The AUTHORS file is -regenerated occasionally from the git commit history, so a mismatch may result -in your changes being overwritten. +Please do not add yourself to the AUTHORS file, as it is regenerated +regularly from the Git history. ### Merge approval From 91b5fe85029cfab2b7f8c55859e0fae999e9f968 Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Thu, 1 May 2014 15:48:16 -0700 Subject: [PATCH 010/400] Adding Rohit Jnagal and Victor Marmol to pkg/cgroups maintainers. Docker-DCO-1.1-Signed-off-by: Victor Marmol (github: vmarmol) --- pkg/cgroups/MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/cgroups/MAINTAINERS b/pkg/cgroups/MAINTAINERS index 1e998f8ac..30cb8bba2 100644 --- a/pkg/cgroups/MAINTAINERS +++ b/pkg/cgroups/MAINTAINERS @@ -1 +1,3 @@ Michael Crosby (@crosbymichael) +Rohit Jnagal (@rjnagal) +Victor Marmol (@vmarmol) From dcae8c157b4e754917a1ea5901ba02dc226a6d4b Mon Sep 17 00:00:00 2001 From: "O.S.Tezer" Date: Sun, 4 May 2014 13:25:03 +0100 Subject: [PATCH 011/400] Introduce x-axe scrolling to code-blocks. This improvement introduces x-axe scrolling to code-blocks by overriding certain default bootstrap word-wrapping constraints. This PR closes: https://github.com/dotcloud/docker/issues/5279 Docker-DCO-1.1-Signed-off-by: O.S. Tezer (github: ostezer) --- docs/theme/mkdocs/css/base.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/theme/mkdocs/css/base.css b/docs/theme/mkdocs/css/base.css index 999a0dedb..956e17a26 100644 --- a/docs/theme/mkdocs/css/base.css +++ b/docs/theme/mkdocs/css/base.css @@ -59,6 +59,11 @@ h6, padding: 0.5em 0.75em !important; line-height: 1.8em; background: #fff; + overflow-x: auto; +} +#content pre code { + word-wrap: normal; + white-space: pre; } #content blockquote { background: #fff; From 02f95a089cd4f9ff7d8e5ba9c7780607dac78d14 Mon Sep 17 00:00:00 2001 From: "O.S.Tezer" Date: Tue, 6 May 2014 09:53:19 +0100 Subject: [PATCH 012/400] Add code for Pardot implementation to docs Docker-DCO-1.1-Signed-off-by: O.S. Tezer (github: ostezer) --- docs/theme/mkdocs/base.html | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/theme/mkdocs/base.html b/docs/theme/mkdocs/base.html index ca418b3cd..66bb2d3d6 100644 --- a/docs/theme/mkdocs/base.html +++ b/docs/theme/mkdocs/base.html @@ -67,5 +67,19 @@ + From 6f327278446b958b32b02a3baefda26e6c8d219f Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 6 May 2014 14:31:47 -0400 Subject: [PATCH 013/400] registry: adding vbatts to the MAINTAINERS Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- registry/MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/registry/MAINTAINERS b/registry/MAINTAINERS index bf3984f5f..af791fb40 100644 --- a/registry/MAINTAINERS +++ b/registry/MAINTAINERS @@ -1,3 +1,4 @@ Sam Alba (@samalba) Joffrey Fuhrer (@shin-) Ken Cochrane (@kencochrane) +Vincent Batts (@vbatts) From 7549199cc5e1de910f5dbd539831095739524ea6 Mon Sep 17 00:00:00 2001 From: Ryan Aslett Date: Wed, 7 May 2014 00:21:02 -0700 Subject: [PATCH 014/400] The Go template is a better pattern for documentation. --- docs/sources/use/port_redirection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/use/port_redirection.md b/docs/sources/use/port_redirection.md index 9f2ce98ea..19de4141b 100644 --- a/docs/sources/use/port_redirection.md +++ b/docs/sources/use/port_redirection.md @@ -11,7 +11,7 @@ port. When this service runs inside a container, one can connect to the port after finding the IP address of the container as follows: # Find IP address of container with ID - $ docker inspect | grep IPAddress | cut -d '"' -f 4 + $ docker inspect --format='{{.NetworkSettings.IPAddress}}' However, this IP address is local to the host system and the container port is not reachable by the outside world. Furthermore, even if the From 36d913078b7ea0138dbc5d561703d068e7e31934 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 7 May 2014 09:46:41 -0600 Subject: [PATCH 015/400] Add "Should-Start" cgroupfs-mount and cgroup-lite to sysvinit-debian init script It's fine to list both here because "Should-Start" is a loose binding (ie, if the listed service exists, it'll be started first, but otherwise, this one will start without it). Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- contrib/init/sysvinit-debian/docker | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contrib/init/sysvinit-debian/docker b/contrib/init/sysvinit-debian/docker index 67f0d2807..9b50fad44 100755 --- a/contrib/init/sysvinit-debian/docker +++ b/contrib/init/sysvinit-debian/docker @@ -4,6 +4,8 @@ # Provides: docker # Required-Start: $syslog $remote_fs # Required-Stop: $syslog $remote_fs +# Should-Start: cgroupfs-mount cgroup-lite +# Should-Stop: cgroupfs-mount cgroup-lite # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Create lightweight, portable, self-sufficient containers. From 91b7d8ebd31dba64b551de85e70786c568cb402a Mon Sep 17 00:00:00 2001 From: LK4D4 Date: Wed, 9 Apr 2014 18:21:22 +0400 Subject: [PATCH 016/400] Change owner only on copied content Fixes #5110 Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- archive/archive.go | 3 + .../TestAdd/DirContentToExistDir/Dockerfile | 10 ++ .../DirContentToExistDir/test_dir/test_file | 0 .../TestAdd/DirContentToRoot/Dockerfile | 8 ++ .../DirContentToRoot/test_dir/test_file | 0 .../TestAdd/SingleFileToExistDir/Dockerfile | 10 ++ .../TestAdd/SingleFileToExistDir/test_file | 0 .../SingleFileToNonExistDir/Dockerfile | 9 ++ .../TestAdd/SingleFileToNonExistDir/test_file | 0 .../TestAdd/SingleFileToRoot/Dockerfile | 8 ++ .../TestAdd/SingleFileToRoot/test_file | 0 .../TestAdd/WholeDirToRoot/Dockerfile | 9 ++ .../TestAdd/WholeDirToRoot/test_dir/test_file | 0 integration-cli/docker_cli_build_test.go | 96 +++++++++++++++++++ server/buildfile.go | 38 ++++++-- 15 files changed, 185 insertions(+), 6 deletions(-) create mode 100644 integration-cli/build_tests/TestAdd/DirContentToExistDir/Dockerfile create mode 100644 integration-cli/build_tests/TestAdd/DirContentToExistDir/test_dir/test_file create mode 100644 integration-cli/build_tests/TestAdd/DirContentToRoot/Dockerfile create mode 100644 integration-cli/build_tests/TestAdd/DirContentToRoot/test_dir/test_file create mode 100644 integration-cli/build_tests/TestAdd/SingleFileToExistDir/Dockerfile create mode 100644 integration-cli/build_tests/TestAdd/SingleFileToExistDir/test_file create mode 100644 integration-cli/build_tests/TestAdd/SingleFileToNonExistDir/Dockerfile create mode 100644 integration-cli/build_tests/TestAdd/SingleFileToNonExistDir/test_file create mode 100644 integration-cli/build_tests/TestAdd/SingleFileToRoot/Dockerfile create mode 100644 integration-cli/build_tests/TestAdd/SingleFileToRoot/test_file create mode 100644 integration-cli/build_tests/TestAdd/WholeDirToRoot/Dockerfile create mode 100644 integration-cli/build_tests/TestAdd/WholeDirToRoot/test_dir/test_file diff --git a/archive/archive.go b/archive/archive.go index 2fac18e99..e21b10ca0 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -418,6 +418,9 @@ func Untar(archive io.Reader, dest string, options *TarOptions) error { // the layer is also a directory. Then we want to merge them (i.e. // just apply the metadata from the layer). if fi, err := os.Lstat(path); err == nil { + if fi.IsDir() && hdr.Name == "." { + continue + } if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) { if err := os.RemoveAll(path); err != nil { return err diff --git a/integration-cli/build_tests/TestAdd/DirContentToExistDir/Dockerfile b/integration-cli/build_tests/TestAdd/DirContentToExistDir/Dockerfile new file mode 100644 index 000000000..6ab0e98f4 --- /dev/null +++ b/integration-cli/build_tests/TestAdd/DirContentToExistDir/Dockerfile @@ -0,0 +1,10 @@ +FROM busybox +RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd +RUN echo 'dockerio:x:1001:' >> /etc/group +RUN mkdir /exists +RUN touch /exists/exists_file +RUN chown -R dockerio.dockerio /exists +ADD test_dir/ /exists/ +RUN [ $(ls -l / | grep exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] +RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' ] +RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ] diff --git a/integration-cli/build_tests/TestAdd/DirContentToExistDir/test_dir/test_file b/integration-cli/build_tests/TestAdd/DirContentToExistDir/test_dir/test_file new file mode 100644 index 000000000..e69de29bb diff --git a/integration-cli/build_tests/TestAdd/DirContentToRoot/Dockerfile b/integration-cli/build_tests/TestAdd/DirContentToRoot/Dockerfile new file mode 100644 index 000000000..03a9c052f --- /dev/null +++ b/integration-cli/build_tests/TestAdd/DirContentToRoot/Dockerfile @@ -0,0 +1,8 @@ +FROM busybox +RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd +RUN echo 'dockerio:x:1001:' >> /etc/group +RUN touch /exists +RUN chown dockerio.dockerio exists +ADD test_dir / +RUN [ $(ls -l /test_file | awk '{print $3":"$4}') = 'root:root' ] +RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] diff --git a/integration-cli/build_tests/TestAdd/DirContentToRoot/test_dir/test_file b/integration-cli/build_tests/TestAdd/DirContentToRoot/test_dir/test_file new file mode 100644 index 000000000..e69de29bb diff --git a/integration-cli/build_tests/TestAdd/SingleFileToExistDir/Dockerfile b/integration-cli/build_tests/TestAdd/SingleFileToExistDir/Dockerfile new file mode 100644 index 000000000..fefbd09f0 --- /dev/null +++ b/integration-cli/build_tests/TestAdd/SingleFileToExistDir/Dockerfile @@ -0,0 +1,10 @@ +FROM busybox +RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd +RUN echo 'dockerio:x:1001:' >> /etc/group +RUN mkdir /exists +RUN touch /exists/exists_file +RUN chown -R dockerio.dockerio /exists +ADD test_file /exists/ +RUN [ $(ls -l / | grep exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] +RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ] +RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' ] diff --git a/integration-cli/build_tests/TestAdd/SingleFileToExistDir/test_file b/integration-cli/build_tests/TestAdd/SingleFileToExistDir/test_file new file mode 100644 index 000000000..e69de29bb diff --git a/integration-cli/build_tests/TestAdd/SingleFileToNonExistDir/Dockerfile b/integration-cli/build_tests/TestAdd/SingleFileToNonExistDir/Dockerfile new file mode 100644 index 000000000..661990b7f --- /dev/null +++ b/integration-cli/build_tests/TestAdd/SingleFileToNonExistDir/Dockerfile @@ -0,0 +1,9 @@ +FROM busybox +RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd +RUN echo 'dockerio:x:1001:' >> /etc/group +RUN touch /exists +RUN chown dockerio.dockerio /exists +ADD test_file /test_dir/ +RUN [ $(ls -l / | grep test_dir | awk '{print $3":"$4}') = 'root:root' ] +RUN [ $(ls -l /test_dir/test_file | awk '{print $3":"$4}') = 'root:root' ] +RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] diff --git a/integration-cli/build_tests/TestAdd/SingleFileToNonExistDir/test_file b/integration-cli/build_tests/TestAdd/SingleFileToNonExistDir/test_file new file mode 100644 index 000000000..e69de29bb diff --git a/integration-cli/build_tests/TestAdd/SingleFileToRoot/Dockerfile b/integration-cli/build_tests/TestAdd/SingleFileToRoot/Dockerfile new file mode 100644 index 000000000..d6375debe --- /dev/null +++ b/integration-cli/build_tests/TestAdd/SingleFileToRoot/Dockerfile @@ -0,0 +1,8 @@ +FROM busybox +RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd +RUN echo 'dockerio:x:1001:' >> /etc/group +RUN touch /exists +RUN chown dockerio.dockerio /exists +ADD test_file / +RUN [ $(ls -l /test_file | awk '{print $3":"$4}') = 'root:root' ] +RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] diff --git a/integration-cli/build_tests/TestAdd/SingleFileToRoot/test_file b/integration-cli/build_tests/TestAdd/SingleFileToRoot/test_file new file mode 100644 index 000000000..e69de29bb diff --git a/integration-cli/build_tests/TestAdd/WholeDirToRoot/Dockerfile b/integration-cli/build_tests/TestAdd/WholeDirToRoot/Dockerfile new file mode 100644 index 000000000..3db6d3fd9 --- /dev/null +++ b/integration-cli/build_tests/TestAdd/WholeDirToRoot/Dockerfile @@ -0,0 +1,9 @@ +FROM busybox +RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd +RUN echo 'dockerio:x:1001:' >> /etc/group +RUN touch /exists +RUN chown dockerio.dockerio exists +ADD test_dir /test_dir +RUN [ $(ls -l / | grep test_dir | awk '{print $3":"$4}') = 'root:root' ] +RUN [ $(ls -l /test_dir/test_file | awk '{print $3":"$4}') = 'root:root' ] +RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] diff --git a/integration-cli/build_tests/TestAdd/WholeDirToRoot/test_dir/test_file b/integration-cli/build_tests/TestAdd/WholeDirToRoot/test_dir/test_file new file mode 100644 index 000000000..e69de29bb diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 7cd42dc69..cd3e06d60 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -23,6 +23,102 @@ func TestBuildSixtySteps(t *testing.T) { logDone("build - build an image with sixty build steps") } +func TestAddSingleFileToRoot(t *testing.T) { + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestAdd") + buildCmd := exec.Command(dockerBinary, "build", "-t", "testaddimg", "SingleFileToRoot") + buildCmd.Dir = buildDirectory + out, exitCode, err := runCommandWithOutput(buildCmd) + errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) + + if err != nil || exitCode != 0 { + t.Fatal("failed to build the image") + } + + deleteImages("testaddimg") + + logDone("build - add single file to root") +} + +func TestAddSingleFileToExistDir(t *testing.T) { + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestAdd") + buildCmd := exec.Command(dockerBinary, "build", "-t", "testaddimg", "SingleFileToExistDir") + buildCmd.Dir = buildDirectory + out, exitCode, err := runCommandWithOutput(buildCmd) + errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) + + if err != nil || exitCode != 0 { + t.Fatal("failed to build the image") + } + + deleteImages("testaddimg") + + logDone("build - add single file to existing dir") +} + +func TestAddSingleFileToNonExistDir(t *testing.T) { + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestAdd") + buildCmd := exec.Command(dockerBinary, "build", "-t", "testaddimg", "SingleFileToNonExistDir") + buildCmd.Dir = buildDirectory + out, exitCode, err := runCommandWithOutput(buildCmd) + errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) + + if err != nil || exitCode != 0 { + t.Fatal("failed to build the image") + } + + deleteImages("testaddimg") + + logDone("build - add single file to non-existing dir") +} + +func TestAddDirContentToRoot(t *testing.T) { + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestAdd") + buildCmd := exec.Command(dockerBinary, "build", "-t", "testaddimg", "DirContentToRoot") + buildCmd.Dir = buildDirectory + out, exitCode, err := runCommandWithOutput(buildCmd) + errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) + + if err != nil || exitCode != 0 { + t.Fatal("failed to build the image") + } + + deleteImages("testaddimg") + + logDone("build - add directory contents to root") +} + +func TestAddDirContentToExistDir(t *testing.T) { + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestAdd") + buildCmd := exec.Command(dockerBinary, "build", "-t", "testaddimg", "DirContentToExistDir") + buildCmd.Dir = buildDirectory + out, exitCode, err := runCommandWithOutput(buildCmd) + errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) + + if err != nil || exitCode != 0 { + t.Fatal("failed to build the image") + } + + deleteImages("testaddimg") + + logDone("build - add directory contents to existing dir") +} + +func TestAddWholeDirToRoot(t *testing.T) { + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestAdd") + buildCmd := exec.Command(dockerBinary, "build", "-t", "testaddimg", "WholeDirToRoot") + buildCmd.Dir = buildDirectory + out, exitCode, err := runCommandWithOutput(buildCmd) + errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) + + if err != nil || exitCode != 0 { + t.Fatal("failed to build the image") + } + + deleteImages("testaddimg") + + logDone("build - add whole directory to root") +} + // TODO: TestCaching // TODO: TestADDCacheInvalidation diff --git a/server/buildfile.go b/server/buildfile.go index 24b0b58f2..253993528 100644 --- a/server/buildfile.go +++ b/server/buildfile.go @@ -386,9 +386,10 @@ func (b *buildFile) checkPathForAddition(orig string) error { func (b *buildFile) addContext(container *daemon.Container, orig, dest string, remote bool) error { var ( - err error - origPath = path.Join(b.contextPath, orig) - destPath = path.Join(container.RootfsPath(), dest) + err error + destExists = true + origPath = path.Join(b.contextPath, orig) + destPath = path.Join(container.RootfsPath(), dest) ) if destPath != container.RootfsPath() { @@ -402,6 +403,14 @@ func (b *buildFile) addContext(container *daemon.Container, orig, dest string, r if strings.HasSuffix(dest, "/") { destPath = destPath + "/" } + destStat, err := os.Stat(destPath) + if err != nil { + if os.IsNotExist(err) { + destExists = false + } else { + return err + } + } fi, err := os.Stat(origPath) if err != nil { if os.IsNotExist(err) { @@ -423,8 +432,20 @@ func (b *buildFile) addContext(container *daemon.Container, orig, dest string, r if err := archive.CopyWithTar(origPath, destPath); err != nil { return err } - if err := chownR(destPath, 0, 0); err != nil { - return err + if destExists { + files, err := ioutil.ReadDir(origPath) + if err != nil { + return err + } + for _, file := range files { + if err := chownR(filepath.Join(destPath, file.Name()), 0, 0); err != nil { + return err + } + } + } else { + if err := chownR(destPath, 0, 0); err != nil { + return err + } } return nil } @@ -456,7 +477,12 @@ func (b *buildFile) addContext(container *daemon.Container, orig, dest string, r return err } - if err := chownR(destPath, 0, 0); err != nil { + resPath := destPath + if destExists && destStat.IsDir() { + resPath = path.Join(destPath, path.Base(origPath)) + } + + if err := chownR(resPath, 0, 0); err != nil { return err } return nil From 0ed9bba1ee48ff925ade710658fecd2270f8a223 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 7 May 2014 18:48:38 -0700 Subject: [PATCH 017/400] Change version to v0.11.1-dev Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index af88ba824..72ab694d3 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.11.1 +0.11.1-dev From fdccfaf72a614872aeeb79e9cee7f42abcaacf6d Mon Sep 17 00:00:00 2001 From: Bryan Murphy Date: Thu, 1 May 2014 20:09:39 +0000 Subject: [PATCH 018/400] move Table to a separate file and add additional unit tests Docker-DCO-1.1-Signed-off-by: Bryan Murphy (github: bmurphy1976) --- engine/env.go | 133 ---------------------------------------- engine/table.go | 141 +++++++++++++++++++++++++++++++++++++++++++ engine/table_test.go | 84 ++++++++++++++++++++++++++ 3 files changed, 225 insertions(+), 133 deletions(-) create mode 100644 engine/table.go diff --git a/engine/env.go b/engine/env.go index f96795f48..94d8c3d8a 100644 --- a/engine/env.go +++ b/engine/env.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "io" - "sort" "strconv" "strings" ) @@ -251,135 +250,3 @@ func (env *Env) Map() map[string]string { } return m } - -type Table struct { - Data []*Env - sortKey string - Chan chan *Env -} - -func NewTable(sortKey string, sizeHint int) *Table { - return &Table{ - make([]*Env, 0, sizeHint), - sortKey, - make(chan *Env), - } -} - -func (t *Table) SetKey(sortKey string) { - t.sortKey = sortKey -} - -func (t *Table) Add(env *Env) { - t.Data = append(t.Data, env) -} - -func (t *Table) Len() int { - return len(t.Data) -} - -func (t *Table) Less(a, b int) bool { - return t.lessBy(a, b, t.sortKey) -} - -func (t *Table) lessBy(a, b int, by string) bool { - keyA := t.Data[a].Get(by) - keyB := t.Data[b].Get(by) - intA, errA := strconv.ParseInt(keyA, 10, 64) - intB, errB := strconv.ParseInt(keyB, 10, 64) - if errA == nil && errB == nil { - return intA < intB - } - return keyA < keyB -} - -func (t *Table) Swap(a, b int) { - tmp := t.Data[a] - t.Data[a] = t.Data[b] - t.Data[b] = tmp -} - -func (t *Table) Sort() { - sort.Sort(t) -} - -func (t *Table) ReverseSort() { - sort.Sort(sort.Reverse(t)) -} - -func (t *Table) WriteListTo(dst io.Writer) (n int64, err error) { - if _, err := dst.Write([]byte{'['}); err != nil { - return -1, err - } - n = 1 - for i, env := range t.Data { - bytes, err := env.WriteTo(dst) - if err != nil { - return -1, err - } - n += bytes - if i != len(t.Data)-1 { - if _, err := dst.Write([]byte{','}); err != nil { - return -1, err - } - n += 1 - } - } - if _, err := dst.Write([]byte{']'}); err != nil { - return -1, err - } - return n + 1, nil -} - -func (t *Table) ToListString() (string, error) { - buffer := bytes.NewBuffer(nil) - if _, err := t.WriteListTo(buffer); err != nil { - return "", err - } - return buffer.String(), nil -} - -func (t *Table) WriteTo(dst io.Writer) (n int64, err error) { - for _, env := range t.Data { - bytes, err := env.WriteTo(dst) - if err != nil { - return -1, err - } - n += bytes - } - return n, nil -} - -func (t *Table) ReadListFrom(src []byte) (n int64, err error) { - var array []interface{} - - if err := json.Unmarshal(src, &array); err != nil { - return -1, err - } - - for _, item := range array { - if m, ok := item.(map[string]interface{}); ok { - env := &Env{} - for key, value := range m { - env.SetAuto(key, value) - } - t.Add(env) - } - } - - return int64(len(src)), nil -} - -func (t *Table) ReadFrom(src io.Reader) (n int64, err error) { - decoder := NewDecoder(src) - for { - env, err := decoder.Decode() - if err == io.EOF { - return 0, nil - } else if err != nil { - return -1, err - } - t.Add(env) - } - return 0, nil -} diff --git a/engine/table.go b/engine/table.go new file mode 100644 index 000000000..292c4ed67 --- /dev/null +++ b/engine/table.go @@ -0,0 +1,141 @@ +package engine + +import ( + "bytes" + "encoding/json" + "io" + "sort" + "strconv" +) + +type Table struct { + Data []*Env + sortKey string + Chan chan *Env +} + +func NewTable(sortKey string, sizeHint int) *Table { + return &Table{ + make([]*Env, 0, sizeHint), + sortKey, + make(chan *Env), + } +} + +func (t *Table) SetKey(sortKey string) { + t.sortKey = sortKey +} + +func (t *Table) Add(env *Env) { + t.Data = append(t.Data, env) +} + +func (t *Table) Len() int { + return len(t.Data) +} + +func (t *Table) Less(a, b int) bool { + return t.lessBy(a, b, t.sortKey) +} + +func (t *Table) lessBy(a, b int, by string) bool { + keyA := t.Data[a].Get(by) + keyB := t.Data[b].Get(by) + intA, errA := strconv.ParseInt(keyA, 10, 64) + intB, errB := strconv.ParseInt(keyB, 10, 64) + if errA == nil && errB == nil { + return intA < intB + } + return keyA < keyB +} + +func (t *Table) Swap(a, b int) { + tmp := t.Data[a] + t.Data[a] = t.Data[b] + t.Data[b] = tmp +} + +func (t *Table) Sort() { + sort.Sort(t) +} + +func (t *Table) ReverseSort() { + sort.Sort(sort.Reverse(t)) +} + +func (t *Table) WriteListTo(dst io.Writer) (n int64, err error) { + if _, err := dst.Write([]byte{'['}); err != nil { + return -1, err + } + n = 1 + for i, env := range t.Data { + bytes, err := env.WriteTo(dst) + if err != nil { + return -1, err + } + n += bytes + if i != len(t.Data)-1 { + if _, err := dst.Write([]byte{','}); err != nil { + return -1, err + } + n += 1 + } + } + if _, err := dst.Write([]byte{']'}); err != nil { + return -1, err + } + return n + 1, nil +} + +func (t *Table) ToListString() (string, error) { + buffer := bytes.NewBuffer(nil) + if _, err := t.WriteListTo(buffer); err != nil { + return "", err + } + return buffer.String(), nil +} + +func (t *Table) WriteTo(dst io.Writer) (n int64, err error) { + for _, env := range t.Data { + bytes, err := env.WriteTo(dst) + if err != nil { + return -1, err + } + n += bytes + } + return n, nil +} + +func (t *Table) ReadListFrom(src []byte) (n int64, err error) { + var array []interface{} + + if err := json.Unmarshal(src, &array); err != nil { + return -1, err + } + + for _, item := range array { + if m, ok := item.(map[string]interface{}); ok { + env := &Env{} + for key, value := range m { + env.SetAuto(key, value) + } + t.Add(env) + } + } + + return int64(len(src)), nil +} + +func (t *Table) ReadFrom(src io.Reader) (n int64, err error) { + decoder := NewDecoder(src) + for { + env, err := decoder.Decode() + if err == io.EOF { + return 0, nil + } else if err != nil { + return -1, err + } + t.Add(env) + } + return 0, nil +} diff --git a/engine/table_test.go b/engine/table_test.go index 3e8e4ff1b..9a32ac9cd 100644 --- a/engine/table_test.go +++ b/engine/table_test.go @@ -26,3 +26,87 @@ func TestTableWriteTo(t *testing.T) { t.Fatalf("Inccorect output: %v", output) } } + +func TestTableSortStringValue(t *testing.T) { + table := NewTable("Key", 0) + + e := &Env{} + e.Set("Key", "A") + table.Add(e) + + e = &Env{} + e.Set("Key", "D") + table.Add(e) + + e = &Env{} + e.Set("Key", "B") + table.Add(e) + + e = &Env{} + e.Set("Key", "C") + table.Add(e) + + table.Sort() + + if len := table.Len(); len != 4 { + t.Fatalf("Expected 4, got %d", len) + } + + if value := table.Data[0].Get("Key"); value != "A" { + t.Fatalf("Expected A, got %s", value) + } + + if value := table.Data[1].Get("Key"); value != "B" { + t.Fatalf("Expected B, got %s", value) + } + + if value := table.Data[2].Get("Key"); value != "C" { + t.Fatalf("Expected C, got %s", value) + } + + if value := table.Data[3].Get("Key"); value != "D" { + t.Fatalf("Expected D, got %s", value) + } +} + +func TestTableReverseSortStringValue(t *testing.T) { + table := NewTable("Key", 0) + + e := &Env{} + e.Set("Key", "A") + table.Add(e) + + e = &Env{} + e.Set("Key", "D") + table.Add(e) + + e = &Env{} + e.Set("Key", "B") + table.Add(e) + + e = &Env{} + e.Set("Key", "C") + table.Add(e) + + table.ReverseSort() + + if len := table.Len(); len != 4 { + t.Fatalf("Expected 4, got %d", len) + } + + if value := table.Data[0].Get("Key"); value != "D" { + t.Fatalf("Expected D, got %s", value) + } + + if value := table.Data[1].Get("Key"); value != "C" { + t.Fatalf("Expected B, got %s", value) + } + + if value := table.Data[2].Get("Key"); value != "B" { + t.Fatalf("Expected C, got %s", value) + } + + if value := table.Data[3].Get("Key"); value != "A" { + t.Fatalf("Expected A, got %s", value) + } +} From d60301edb88a4e182a10cd2becb3795b2dd13fab Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 8 May 2014 01:03:45 -0600 Subject: [PATCH 019/400] Update restrict.Restrict to both show the error message when failing to mount /dev/null over /proc/kcore, and to ignore "not exists" errors while doing so (for when CONFIG_PROC_KCORE=n in the kernel) Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- pkg/libcontainer/security/restrict/restrict.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/libcontainer/security/restrict/restrict.go b/pkg/libcontainer/security/restrict/restrict.go index e1296b1d7..361d0764a 100644 --- a/pkg/libcontainer/security/restrict/restrict.go +++ b/pkg/libcontainer/security/restrict/restrict.go @@ -4,6 +4,7 @@ package restrict import ( "fmt" + "os" "syscall" "github.com/dotcloud/docker/pkg/system" @@ -18,8 +19,8 @@ func Restrict(mounts ...string) error { return fmt.Errorf("unable to remount %s readonly: %s", dest, err) } } - if err := system.Mount("/dev/null", "/proc/kcore", "", syscall.MS_BIND, ""); err != nil { - return fmt.Errorf("unable to bind-mount /dev/null over /proc/kcore") + if err := system.Mount("/dev/null", "/proc/kcore", "", syscall.MS_BIND, ""); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("unable to bind-mount /dev/null over /proc/kcore: %s", err) } return nil } From 5d39175c22b01cb31ebabb0c52de913a5541cd8b Mon Sep 17 00:00:00 2001 From: Mason Malone Date: Thu, 8 May 2014 12:49:50 -0400 Subject: [PATCH 020/400] Fix link to daemon/execdriver/lxc/lxc_template.go --- docs/sources/reference/run.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index 09c2b642a..0f72679ce 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -235,7 +235,7 @@ By default, Docker containers are "unprivileged" and cannot, for example, run a Docker daemon inside a Docker container. This is because by default a container is not allowed to access any devices, but a "privileged" container is given access to all devices (see [lxc-template.go]( -https://github.com/dotcloud/docker/blob/master/execdriver/lxc/lxc_template.go) +https://github.com/dotcloud/docker/blob/master/daemon/execdriver/lxc/lxc_template.go) and documentation on [cgroups devices]( https://www.kernel.org/doc/Documentation/cgroups/devices.txt)). @@ -250,7 +250,7 @@ If the Docker daemon was started using the `lxc` exec-driver (`docker -d --exec-driver=lxc`) then the operator can also specify LXC options using one or more `--lxc-conf` parameters. These can be new parameters or override existing parameters from the [lxc-template.go]( -https://github.com/dotcloud/docker/blob/master/execdriver/lxc/lxc_template.go). +https://github.com/dotcloud/docker/blob/master/daemon/execdriver/lxc/lxc_template.go). Note that in the future, a given host's docker daemon may not use LXC, so this is an implementation-specific configuration meant for operators already familiar with using LXC directly. From ea049a2ebc072819920c42140221ce4ab7bab22e Mon Sep 17 00:00:00 2001 From: Tony Daws Date: Thu, 8 May 2014 10:05:00 -0700 Subject: [PATCH 021/400] Fixed typo in docs - intro - working with docker - working with images. It read that you can use both images used by others and images used by others, in a run-on sentence. --- docs/sources/introduction/working-with-docker.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/sources/introduction/working-with-docker.md b/docs/sources/introduction/working-with-docker.md index 8d946e684..1b5b43fec 100644 --- a/docs/sources/introduction/working-with-docker.md +++ b/docs/sources/introduction/working-with-docker.md @@ -137,11 +137,10 @@ You will get an output with all available options: ### Docker Images As we've discovered a Docker image is a read-only template that we build -containers from. Every Docker container is launched from an image and -you can use both images provided by others, for example we've discovered -the base `ubuntu` image provided by Docker, as well as images built by -others. For example we can build an image that runs Apache and our own -web application as a starting point to launch containers. +containers from. Every Docker container is launched from an image. You can +use both images provided by Docker, such as the base `ubuntu` image, +as well as images built by others. For example we can build an image that +runs Apache and our own web application as a starting point to launch containers. ### Searching for images From bff0c4f3dc560109ed2d5dc6cae12453c9bc2747 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 8 May 2014 19:25:47 +0200 Subject: [PATCH 022/400] devmapper: Remove accidental debug spew Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- daemon/graphdriver/devmapper/deviceset.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index f734f5520..79563c108 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -340,8 +340,6 @@ func (devices *DeviceSet) loadMetadata(hash string) *DevInfo { return nil } - fmt.Printf("Loaded metadata %v\n", info) - // If the transaction id is larger than the actual one we lost the device due to some crash if info.TransactionId > devices.TransactionId { return nil @@ -352,7 +350,6 @@ func (devices *DeviceSet) loadMetadata(hash string) *DevInfo { func (devices *DeviceSet) setupBaseImage() error { oldInfo, _ := devices.lookupDevice("") - utils.Debugf("oldInfo: %p", oldInfo) if oldInfo != nil && oldInfo.Initialized { return nil } From 170e4d2e19258c4e45b975177618640cb554c32b Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 8 May 2014 12:57:19 -0700 Subject: [PATCH 023/400] Cast Input and Output to closer Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- engine/streams.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/streams.go b/engine/streams.go index 48f031de8..6cc10d346 100644 --- a/engine/streams.go +++ b/engine/streams.go @@ -118,7 +118,7 @@ func (o *Output) Close() error { defer o.Unlock() var firstErr error for _, dst := range o.dests { - if closer, ok := dst.(io.WriteCloser); ok { + if closer, ok := dst.(io.Closer); ok { err := closer.Close() if err != nil && firstErr == nil { firstErr = err @@ -154,7 +154,7 @@ func (i *Input) Read(p []byte) (n int, err error) { // Not thread safe on purpose func (i *Input) Close() error { if i.src != nil { - if closer, ok := i.src.(io.WriteCloser); ok { + if closer, ok := i.src.(io.Closer); ok { return closer.Close() } } From ca33d2589f69381719b456352fdaaaa3fe54d5ac Mon Sep 17 00:00:00 2001 From: Benjamin Atkin Date: Thu, 8 May 2014 21:18:03 -0700 Subject: [PATCH 024/400] make higher level heading specify that instructions are for manual install Docker-DCO-1.1-Signed-off-by: Benjamin Atkin (github: benatkin) --- docs/sources/installation/mac.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/installation/mac.md b/docs/sources/installation/mac.md index c30e0b644..816536256 100644 --- a/docs/sources/installation/mac.md +++ b/docs/sources/installation/mac.md @@ -41,9 +41,9 @@ and install VirtualBox. > Do not simply copy the package without running the > installer. -## Installing boot2docker +## Installing boot2docker manually -### Installing manually +### Downloading the boot2docker script [boot2docker](https://github.com/boot2docker/boot2docker) provides a handy script to manage the VM running the Docker daemon. It also takes From 4c2b9d732446adb472667c2a7017ba3e8cc58c0d Mon Sep 17 00:00:00 2001 From: unclejack Date: Fri, 9 May 2014 10:09:33 +0300 Subject: [PATCH 025/400] rename goruntime import to runtime This renames the goruntime import of the runtime package back to runtime. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- api/client/commands.go | 4 ++-- integration/runtime_test.go | 4 ++-- server/server.go | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index 89f9b0a4c..46fec3f28 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -13,7 +13,7 @@ import ( "os" "os/exec" "path" - goruntime "runtime" + "runtime" "strconv" "strings" "syscall" @@ -359,7 +359,7 @@ func (cli *DockerCli) CmdVersion(args ...string) error { fmt.Fprintf(cli.out, "Client version: %s\n", dockerversion.VERSION) } fmt.Fprintf(cli.out, "Client API version: %s\n", api.APIVERSION) - fmt.Fprintf(cli.out, "Go version (client): %s\n", goruntime.Version()) + fmt.Fprintf(cli.out, "Go version (client): %s\n", runtime.Version()) if dockerversion.GITCOMMIT != "" { fmt.Fprintf(cli.out, "Git commit (client): %s\n", dockerversion.GITCOMMIT) } diff --git a/integration/runtime_test.go b/integration/runtime_test.go index c84ea5bed..07207c9b5 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -16,7 +16,7 @@ import ( "net/url" "os" "path/filepath" - goruntime "runtime" + "runtime" "strconv" "strings" "syscall" @@ -127,7 +127,7 @@ func init() { spawnGlobalDaemon() spawnLegitHttpsDaemon() spawnRogueHttpsDaemon() - startFds, startGoroutines = utils.GetTotalUsedFds(), goruntime.NumGoroutine() + startFds, startGoroutines = utils.GetTotalUsedFds(), runtime.NumGoroutine() } func setupBaseImage() { diff --git a/server/server.go b/server/server.go index 3763f87dd..30e5ef1f6 100644 --- a/server/server.go +++ b/server/server.go @@ -34,7 +34,7 @@ import ( gosignal "os/signal" "path" "path/filepath" - goruntime "runtime" + "runtime" "strconv" "strings" "sync" @@ -789,7 +789,7 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status { v.SetBool("IPv4Forwarding", !srv.daemon.SystemConfig().IPv4ForwardingDisabled) v.SetBool("Debug", os.Getenv("DEBUG") != "") v.SetInt("NFd", utils.GetTotalUsedFds()) - v.SetInt("NGoroutines", goruntime.NumGoroutine()) + v.SetInt("NGoroutines", runtime.NumGoroutine()) v.Set("ExecutionDriver", srv.daemon.ExecutionDriver().Name()) v.SetInt("NEventsListener", len(srv.listeners)) v.Set("KernelVersion", kernelVersion) @@ -807,9 +807,9 @@ func (srv *Server) DockerVersion(job *engine.Job) engine.Status { v.Set("Version", dockerversion.VERSION) v.SetJson("ApiVersion", api.APIVERSION) v.Set("GitCommit", dockerversion.GITCOMMIT) - v.Set("GoVersion", goruntime.Version()) - v.Set("Os", goruntime.GOOS) - v.Set("Arch", goruntime.GOARCH) + v.Set("GoVersion", runtime.Version()) + v.Set("Os", runtime.GOOS) + v.Set("Arch", runtime.GOARCH) if kernelVersion, err := utils.GetKernelVersion(); err == nil { v.Set("KernelVersion", kernelVersion.String()) } From fd5672349490f9cd18933a2508e1890c9e026af5 Mon Sep 17 00:00:00 2001 From: "O.S.Tezer" Date: Mon, 5 May 2014 18:17:58 +0100 Subject: [PATCH 026/400] Rewrite and update the MongoDB service article MongoDB article had some fundemental issues. - Outdated Dockerfile - Insufficient / unclear instructions - Unnecessary comments - Failed to explain the role of Docker.io - Did not have a complete Dockerfile sample - Lacked a "learn more" section / link to Trusted Builds This update aims to address all these issues with a complete re-write. It also: - Corrects the label under which this article is/was listed on the menu Docker-DCO-1.1-Signed-off-by: O.S. Tezer (github: ostezer) - First run at amending after the initial review process. - Make the Dockerfile generic. - Revision. - Fixes --- docs/mkdocs.yml | 2 +- docs/sources/examples/mongodb.md | 191 ++++++++++++++++------- docs/sources/examples/mongodb/Dockerfile | 24 +++ 3 files changed, 158 insertions(+), 59 deletions(-) create mode 100644 docs/sources/examples/mongodb/Dockerfile diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index c16436e89..32d085279 100755 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -57,7 +57,7 @@ pages: - ['examples/hello_world.md', 'Examples', 'Hello World'] - ['examples/nodejs_web_app.md', 'Examples', 'Node.js web application'] - ['examples/python_web_app.md', 'Examples', 'Python web application'] -- ['examples/mongodb.md', 'Examples', 'MongoDB service'] +- ['examples/mongodb.md', 'Examples', 'Dockerizing MongoDB'] - ['examples/running_redis_service.md', 'Examples', 'Redis service'] - ['examples/postgresql_service.md', 'Examples', 'PostgreSQL service'] - ['examples/running_riak_service.md', 'Examples', 'Running a Riak service'] diff --git a/docs/sources/examples/mongodb.md b/docs/sources/examples/mongodb.md index 4b5f95d02..e397cb0e5 100644 --- a/docs/sources/examples/mongodb.md +++ b/docs/sources/examples/mongodb.md @@ -1,89 +1,164 @@ -page_title: Building a Docker Image with MongoDB -page_description: How to build a Docker image with MongoDB pre-installed -page_keywords: docker, example, package installation, networking, mongodb +page_title: Dockerizing MongoDB +page_description: Creating a Docker image with MongoDB pre-installed using a Dockerfile and sharing the image on Docker.io +page_keywords: docker, dockerize, dockerizing, article, example, docker.io, platform, package, installation, networking, mongodb, containers, images, image, sharing, dockerfile, build, auto-building, virtualization, framework -# Building an Image with MongoDB +# Dockerizing MongoDB -> **Note**: +## Introduction + +In this example, we are going to learn how to build a Docker image +with MongoDB pre-installed. +We'll also see how to `push` that image to the [Docker.io registry]( +https://index.docker.io) and share it with others! + +Using Docker and containers for deploying [MongoDB](https://www.mongodb.org/) +instances will bring several benefits, such as: + + - Easy to maintain, highly configurable MongoDB instances; + - Ready to run and start working within milliseconds; + - Based on globally accessible and shareable images. + +> **Note:** > -> - This example assumes you have Docker running in daemon mode. For -> more information please see [*Check your Docker -> install*](../hello_world/#running-examples). -> - **If you don't like sudo** then see [*Giving non-root -> access*](/installation/binaries/#dockergroup) +> This example assumes you have Docker running in daemon mode. To verify, +> try running `sudo docker info`. +> For more information, please see: [*Check your Docker installation*]( +> /examples/hello_world/#running-examples). -The goal of this example is to show how you can build your own Docker -images with MongoDB pre-installed. We will do that by constructing a -Dockerfile that downloads a base image, adds an -apt source and installs the database software on Ubuntu. +> **Note:** +> +> If you do **_not_** like `sudo`, you might want to check out: +> [*Giving non-root access*](installation/binaries/#giving-non-root-access). -## Creating a Dockerfile +## Creating a Dockerfile for MongoDB -Create an empty file called Dockerfile: +Let's create our `Dockerfile` and start building it: - $ touch Dockerfile + $ nano Dockerfile -Next, define the parent image you want to use to build your own image on -top of. Here, we'll use [Ubuntu](https://index.docker.io/_/ubuntu/) -(tag: `latest`) available on the [docker -index](http://index.docker.io): +Although optional, it is handy to have comments at the beginning of a +`Dockerfile` explaining its purpose: - FROM ubuntu:latest + # Dockerizing MongoDB: Dockerfile for building MongoDB images + # Based on ubuntu:latest, installs MongoDB following the instructions from: + # http://docs.mongodb.org/manual/tutorial/install-mongodb-on-ubuntu/ -Since we want to be running the latest version of MongoDB we'll need to -add the 10gen repo to our apt sources list. +> **Tip:** `Dockerfile`s are flexible. However, they need to follow a certain +> format. The first item to be defined is the name of an image, which becomes +> the *parent* of your *Dockerized MongoDB* image. - # Add 10gen official apt source to the sources list +We will build our image using the latest version of Ubuntu from the +[Docker.io Ubuntu](https://index.docker.io/_/ubuntu/) repository. + + # Format: FROM repository[:version] + FROM ubuntu:latest + +Continuing, we will declare the `MAINTAINER` of the `Dockerfile`: + + # Format: MAINTAINER Name + MAINTAINER M.Y. Name + +> **Note:** Although Ubuntu systems have MongoDB packages, they are likely to +> be outdated. Therefore in this example, we will use the official MongoDB +> packages. + +We will begin with importing the MongoDB public GPG key. We will also create +a MongoDB repository file for the package manager. + + # Installation: + # Import MongoDB public GPG key AND create a MongoDB list file RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 RUN echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | tee /etc/apt/sources.list.d/10gen.list -Then, we don't want Ubuntu to complain about init not being available so -we'll divert `/sbin/initctl` to -`/bin/true` so it thinks everything is working. +After this initial preparation we can update our packages and install MongoDB. - # Hack for initctl not being available in Ubuntu - RUN dpkg-divert --local --rename --add /sbin/initctl - RUN ln -s /bin/true /sbin/initctl - -Afterwards we'll be able to update our apt repositories and install -MongoDB - - # Install MongoDB + # Update apt-get sources AND install MongoDB RUN apt-get update - RUN apt-get install mongodb-10gen + RUN apt-get install -y -q mongodb-org -To run MongoDB we'll have to create the default data directory (because -we want it to run without needing to provide a special configuration -file) +> **Tip:** You can install a specific version of MongoDB by using a list +> of required packages with versions, e.g.: +> +> RUN apt-get install -y -q mongodb-org=2.6.1 mongodb-org-server=2.6.1 mongodb-org-shell=2.6.1 mongodb-org-mongos=2.6.1 mongodb-org-tools=2.6.1 + +MongoDB requires a data directory. Let's create it as the final step of our +installation instructions. # Create the MongoDB data directory RUN mkdir -p /data/db -Finally, we'll expose the standard port that MongoDB runs on, 27107, as -well as define an `ENTRYPOINT` instruction for the -container. +Lastly we set the `ENTRYPOINT` which will tell Docker to run `mongod` inside +the containers launched from our MongoDB image. And for ports, we will use +the `EXPOSE` instruction. + # Expose port 27017 from the container to the host EXPOSE 27017 - ENTRYPOINT ["usr/bin/mongod"] -Now, lets build the image which will go through the -Dockerfile we made and run all of the commands. + # Set usr/bin/mongod as the dockerized entry-point application + ENTRYPOINT usr/bin/mongod - $ sudo docker build -t /mongodb . +Now save the file and let's build our image. -Now you should be able to run `mongod` as a daemon -and be able to connect on the local port! +> **Note:** +> +> The full version of this `Dockerfile` can be found [here](/ +> /examples/mongodb/Dockerfile). - # Regular style - $ MONGO_ID=$(sudo docker run -d /mongodb) +## Building the MongoDB Docker image - # Lean and mean - $ MONGO_ID=$(sudo docker run -d /mongodb --noprealloc --smallfiles) +With our `Dockerfile`, we can now build the MongoDB image using Docker. Unless +experimenting, it is always a good practice to tag Docker images by passing the +`--tag` option to `docker build` command. - # Check the logs out - $ sudo docker logs $MONGO_ID + # Format: sudo docker build --tag/-t / . + # Example: + $ sudo docker build --tag my/repo . - # Connect and play around - $ mongo --port +Once this command is issued, Docker will go through the `Dockerfile` and build +the image. The final image will be tagged `my/repo`. -Sweet! +## Pushing the MongoDB image to Docker.io + +All Docker image repositories can be hosted and shared on +[Docker.io](https://index.docker.io) with the `docker push` command. For this, +you need to be logged-in. + + # Log-in + $ sudo docker login + Username: + .. + + # Push the image + # Format: sudo docker push / + $ sudo docker push my/repo + The push refers to a repository [my/repo] (len: 1) + Sending image list + Pushing repository my/repo (1 tags) + .. + +## Using the MongoDB image + +Using the MongoDB image we created, we can run one or more MongoDB instances +as daemon process(es). + + # Basic way + # Usage: sudo docker run --name -d / + $ sudo docker run --name mongo_instance_001 -d my/repo + + # Dockerized MongoDB, lean and mean! + # Usage: sudo docker run --name -d / --noprealloc --smallfiles + $ sudo docker run --name mongo_instance_001 -d my/repo --noprealloc --smallfiles + + # Checking out the logs of a MongoDB container + # Usage: sudo docker logs + $ sudo docker logs mongo_instance_001 + + # Playing with MongoDB + # Usage: mongo --port + $ mongo --port 12345 + +## Learn more + + - [Linking containers](/use/working_with_links_names/) + - [Cross-host linking containers](/use/ambassador_pattern_linking/) + - [Creating a Trusted Build](/docker-io/builds/#trusted-builds) diff --git a/docs/sources/examples/mongodb/Dockerfile b/docs/sources/examples/mongodb/Dockerfile new file mode 100644 index 000000000..e7acc0fd8 --- /dev/null +++ b/docs/sources/examples/mongodb/Dockerfile @@ -0,0 +1,24 @@ +# Dockerizing MongoDB: Dockerfile for building MongoDB images +# Based on ubuntu:latest, installs MongoDB following the instructions from: +# http://docs.mongodb.org/manual/tutorial/install-mongodb-on-ubuntu/ + +FROM ubuntu:latest +MAINTAINER Docker + +# Installation: +# Import MongoDB public GPG key AND create a MongoDB list file +RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 +RUN echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | tee /etc/apt/sources.list.d/10gen.list + +# Update apt-get sources AND install MongoDB +RUN apt-get update +RUN apt-get install -y -q mongodb-org + +# Create the MongoDB data directory +RUN mkdir -p /data/db + +# Expose port #27017 from the container to the host +EXPOSE 27017 + +# Set usr/bin/mongod as the dockerized entry-point application +ENTRYPOINT usr/bin/mongod From 47c79870ea529099cca635f53da870e0cea5652a Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 30 Apr 2014 11:16:06 +0200 Subject: [PATCH 027/400] devmapper: Properly restore mocked functions after test Currently the tests that mocks or denies functions leave this state around for the next test. This is no good if we want to actually test the devicemapper code in later tests. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- daemon/graphdriver/devmapper/driver_test.go | 52 ++++++++++++++++++--- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/daemon/graphdriver/devmapper/driver_test.go b/daemon/graphdriver/devmapper/driver_test.go index e53dfaa75..54e75e2a0 100644 --- a/daemon/graphdriver/devmapper/driver_test.go +++ b/daemon/graphdriver/devmapper/driver_test.go @@ -20,8 +20,16 @@ func init() { DefaultBaseFsSize = 300 * 1024 * 1024 } +// We use assignment here to get the right type +var ( + oldMounted = Mounted + oldExecRun = execRun +) + // denyAllDevmapper mocks all calls to libdevmapper in the unit tests, and denies them by default func denyAllDevmapper() { + oldExecRun = execRun + // Hijack all calls to libdevmapper with default panics. // Authorized calls are selectively hijacked in each tests. DmTaskCreate = func(t int) *CDmTask { @@ -77,7 +85,29 @@ func denyAllDevmapper() { } } +func restoreAllDevmapper() { + DmGetLibraryVersion = dmGetLibraryVersionFct + DmGetNextTarget = dmGetNextTargetFct + DmLogInitVerbose = dmLogInitVerboseFct + DmSetDevDir = dmSetDevDirFct + DmTaskAddTarget = dmTaskAddTargetFct + DmTaskCreate = dmTaskCreateFct + DmTaskDestroy = dmTaskDestroyFct + DmTaskGetInfo = dmTaskGetInfoFct + DmTaskRun = dmTaskRunFct + DmTaskSetAddNode = dmTaskSetAddNodeFct + DmTaskSetCookie = dmTaskSetCookieFct + DmTaskSetMessage = dmTaskSetMessageFct + DmTaskSetName = dmTaskSetNameFct + DmTaskSetRo = dmTaskSetRoFct + DmTaskSetSector = dmTaskSetSectorFct + DmUdevWait = dmUdevWaitFct + LogWithErrnoInit = logWithErrnoInitFct + execRun = oldExecRun +} + func denyAllSyscall() { + oldMounted = Mounted sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) { panic("sysMount: this method should not be called here") } @@ -110,6 +140,14 @@ func denyAllSyscall() { // } } +func restoreAllSyscall() { + sysMount = syscall.Mount + sysUnmount = syscall.Unmount + sysCloseOnExec = syscall.CloseOnExec + sysSyscall = syscall.Syscall + Mounted = oldMounted +} + func mkTestDirectory(t *testing.T) string { dir, err := ioutil.TempDir("", "docker-test-devmapper-") if err != nil { @@ -160,8 +198,10 @@ func TestInit(t *testing.T) { ) defer osRemoveAll(home) + denyAllDevmapper() + defer restoreAllDevmapper() + func() { - denyAllDevmapper() DmSetDevDir = func(dir string) int { calls["DmSetDevDir"] = true expectedDir := "/dev" @@ -398,7 +438,7 @@ func mockAllDevmapper(calls Set) { func TestDriverName(t *testing.T) { denyAllDevmapper() - defer denyAllDevmapper() + defer restoreAllDevmapper() oldInit := fakeInit() defer restoreInit(oldInit) @@ -412,8 +452,8 @@ func TestDriverName(t *testing.T) { func TestDriverCreate(t *testing.T) { denyAllDevmapper() denyAllSyscall() - defer denyAllSyscall() - defer denyAllDevmapper() + defer restoreAllSyscall() + defer restoreAllDevmapper() calls := make(Set) mockAllDevmapper(calls) @@ -524,8 +564,8 @@ func TestDriverCreate(t *testing.T) { func TestDriverRemove(t *testing.T) { denyAllDevmapper() denyAllSyscall() - defer denyAllSyscall() - defer denyAllDevmapper() + defer restoreAllSyscall() + defer restoreAllDevmapper() calls := make(Set) mockAllDevmapper(calls) From 84f19a09ac1bb6221aeafd858306b097203aa974 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 30 Apr 2014 11:59:26 +0200 Subject: [PATCH 028/400] vfs graphdriver: Make root dir mode 755 This matches the other backends. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- daemon/graphdriver/vfs/driver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/graphdriver/vfs/driver.go b/daemon/graphdriver/vfs/driver.go index 765b21cde..7473aa659 100644 --- a/daemon/graphdriver/vfs/driver.go +++ b/daemon/graphdriver/vfs/driver.go @@ -47,7 +47,7 @@ func (d *Driver) Create(id, parent string) error { if err := os.MkdirAll(path.Dir(dir), 0700); err != nil { return err } - if err := os.Mkdir(dir, 0700); err != nil { + if err := os.Mkdir(dir, 0755); err != nil { return err } if parent == "" { From 27744062aa96f5f16d615ed829bc0d06b7df381d Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 30 Apr 2014 14:43:51 +0200 Subject: [PATCH 029/400] graphdriver: Add generic test framework for graph drivers This adds daemon/graphdriver/graphtest/graphtest which has a few generic tests for all graph drivers, and then uses these from the btrs, devicemapper and vfs backends. I've not yet added the aufs backend, because i can't test that here atm. It should work though. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- daemon/graphdriver/btrfs/btrfs_test.go | 28 +++ daemon/graphdriver/devmapper/driver_test.go | 23 ++ daemon/graphdriver/graphtest/graphtest.go | 225 ++++++++++++++++++++ daemon/graphdriver/vfs/vfs_test.go | 28 +++ 4 files changed, 304 insertions(+) create mode 100644 daemon/graphdriver/btrfs/btrfs_test.go create mode 100644 daemon/graphdriver/graphtest/graphtest.go create mode 100644 daemon/graphdriver/vfs/vfs_test.go diff --git a/daemon/graphdriver/btrfs/btrfs_test.go b/daemon/graphdriver/btrfs/btrfs_test.go new file mode 100644 index 000000000..3069a9855 --- /dev/null +++ b/daemon/graphdriver/btrfs/btrfs_test.go @@ -0,0 +1,28 @@ +package btrfs + +import ( + "github.com/dotcloud/docker/daemon/graphdriver/graphtest" + "testing" +) + +// This avoids creating a new driver for each test if all tests are run +// Make sure to put new tests between TestBtrfsSetup and TestBtrfsTeardown +func TestBtrfsSetup(t *testing.T) { + graphtest.GetDriver(t, "btrfs") +} + +func TestBtrfsCreateEmpty(t *testing.T) { + graphtest.DriverTestCreateEmpty(t, "btrfs") +} + +func TestBtrfsCreateBase(t *testing.T) { + graphtest.DriverTestCreateBase(t, "btrfs") +} + +func TestBtrfsCreateSnap(t *testing.T) { + graphtest.DriverTestCreateSnap(t, "btrfs") +} + +func TestBtrfsTeardown(t *testing.T) { + graphtest.PutDriver(t) +} diff --git a/daemon/graphdriver/devmapper/driver_test.go b/daemon/graphdriver/devmapper/driver_test.go index 54e75e2a0..0602f123a 100644 --- a/daemon/graphdriver/devmapper/driver_test.go +++ b/daemon/graphdriver/devmapper/driver_test.go @@ -5,6 +5,7 @@ package devmapper import ( "fmt" "github.com/dotcloud/docker/daemon/graphdriver" + "github.com/dotcloud/docker/daemon/graphdriver/graphtest" "io/ioutil" "path" "runtime" @@ -914,3 +915,25 @@ func assertMap(t *testing.T, m map[string]bool, keys ...string) { t.Fatalf("Unexpected keys: %v", m) } } + +// This avoids creating a new driver for each test if all tests are run +// Make sure to put new tests between TestDevmapperSetup and TestDevmapperTeardown +func TestDevmapperSetup(t *testing.T) { + graphtest.GetDriver(t, "devicemapper") +} + +func TestDevmapperCreateEmpty(t *testing.T) { + graphtest.DriverTestCreateEmpty(t, "devicemapper") +} + +func TestDevmapperCreateBase(t *testing.T) { + graphtest.DriverTestCreateBase(t, "devicemapper") +} + +func TestDevmapperCreateSnap(t *testing.T) { + graphtest.DriverTestCreateSnap(t, "devicemapper") +} + +func TestDevmapperTeardown(t *testing.T) { + graphtest.PutDriver(t) +} diff --git a/daemon/graphdriver/graphtest/graphtest.go b/daemon/graphdriver/graphtest/graphtest.go new file mode 100644 index 000000000..c83c840bc --- /dev/null +++ b/daemon/graphdriver/graphtest/graphtest.go @@ -0,0 +1,225 @@ +package graphtest + +import ( + "github.com/dotcloud/docker/daemon/graphdriver" + "io/ioutil" + "os" + "path" + "syscall" + "testing" +) + +var ( + drv *Driver +) + +type Driver struct { + graphdriver.Driver + root string + refCount int +} + +func newDriver(t *testing.T, name string) *Driver { + root, err := ioutil.TempDir("/var/tmp", "docker-graphtest-") + if err != nil { + t.Fatal(err) + } + + if err := os.MkdirAll(root, 0755); err != nil { + t.Fatal(err) + } + + d, err := graphdriver.GetDriver(name, root) + if err != nil { + t.Fatal(err) + } + return &Driver{d, root, 1} +} + +func cleanup(t *testing.T, d *Driver) { + if err := drv.Cleanup(); err != nil { + t.Fatal(err) + } + os.RemoveAll(d.root) +} + +func GetDriver(t *testing.T, name string) graphdriver.Driver { + if drv == nil { + drv = newDriver(t, name) + } else { + drv.refCount++ + } + return drv +} + +func PutDriver(t *testing.T) { + if drv == nil { + t.Fatal("No driver to put!") + } + drv.refCount-- + if drv.refCount == 0 { + cleanup(t, drv) + drv = nil + } +} + +func verifyFile(t *testing.T, path string, mode os.FileMode, uid, gid uint32) { + fi, err := os.Stat(path) + if err != nil { + t.Fatal(err) + } + + if fi.Mode()&os.ModeType != mode&os.ModeType { + t.Fatalf("Expected %s type 0x%x, got 0x%x", path, mode&os.ModeType, fi.Mode()&os.ModeType) + } + + if fi.Mode()&os.ModePerm != mode&os.ModePerm { + t.Fatalf("Expected %s mode %o, got %o", path, mode&os.ModePerm, fi.Mode()&os.ModePerm) + } + + if fi.Mode()&os.ModeSticky != mode&os.ModeSticky { + t.Fatalf("Expected %s sticky 0x%x, got 0x%x", path, mode&os.ModeSticky, fi.Mode()&os.ModeSticky) + } + + if fi.Mode()&os.ModeSetuid != mode&os.ModeSetuid { + t.Fatalf("Expected %s setuid 0x%x, got 0x%x", path, mode&os.ModeSetuid, fi.Mode()&os.ModeSetuid) + } + + if fi.Mode()&os.ModeSetgid != mode&os.ModeSetgid { + t.Fatalf("Expected %s setgid 0x%x, got 0x%x", path, mode&os.ModeSetgid, fi.Mode()&os.ModeSetgid) + } + + if stat, ok := fi.Sys().(*syscall.Stat_t); ok { + if stat.Uid != uid { + t.Fatal("%s no owned by uid %d", path, uid) + } + if stat.Gid != gid { + t.Fatal("%s not owned by gid %d", path, gid) + } + } + +} + +// Creates an new image and verifies it is empty and the right metadata +func DriverTestCreateEmpty(t *testing.T, drivername string) { + driver := GetDriver(t, drivername) + defer PutDriver(t) + + if err := driver.Create("empty", ""); err != nil { + t.Fatal(err) + } + + if !driver.Exists("empty") { + t.Fatal("Newly created image doesn't exist") + } + + dir, err := driver.Get("empty", "") + if err != nil { + t.Fatal(err) + } + + verifyFile(t, dir, 0755|os.ModeDir, 0, 0) + + // Verify that the directory is empty + fis, err := ioutil.ReadDir(dir) + if err != nil { + t.Fatal(err) + } + + if len(fis) != 0 { + t.Fatal("New directory not empty") + } + + driver.Put("empty") + + if err := driver.Remove("empty"); err != nil { + t.Fatal(err) + } + +} + +func createBase(t *testing.T, driver graphdriver.Driver, name string) { + // We need to be able to set any perms + oldmask := syscall.Umask(0) + defer syscall.Umask(oldmask) + + if err := driver.Create(name, ""); err != nil { + t.Fatal(err) + } + + dir, err := driver.Get(name, "") + if err != nil { + t.Fatal(err) + } + defer driver.Put(name) + + subdir := path.Join(dir, "a subdir") + if err := os.Mkdir(subdir, 0705|os.ModeSticky); err != nil { + t.Fatal(err) + } + if err := os.Chown(subdir, 1, 2); err != nil { + t.Fatal(err) + } + + file := path.Join(dir, "a file") + if err := ioutil.WriteFile(file, []byte("Some data"), 0222|os.ModeSetuid); err != nil { + t.Fatal(err) + } +} + +func verifyBase(t *testing.T, driver graphdriver.Driver, name string) { + dir, err := driver.Get(name, "") + if err != nil { + t.Fatal(err) + } + defer driver.Put(name) + + subdir := path.Join(dir, "a subdir") + verifyFile(t, subdir, 0705|os.ModeDir|os.ModeSticky, 1, 2) + + file := path.Join(dir, "a file") + verifyFile(t, file, 0222|os.ModeSetuid, 0, 0) + + fis, err := ioutil.ReadDir(dir) + if err != nil { + t.Fatal(err) + } + + if len(fis) != 2 { + t.Fatal("Unexpected files in base image") + } + +} + +func DriverTestCreateBase(t *testing.T, drivername string) { + driver := GetDriver(t, drivername) + defer PutDriver(t) + + createBase(t, driver, "Base") + verifyBase(t, driver, "Base") + + if err := driver.Remove("Base"); err != nil { + t.Fatal(err) + } +} + +func DriverTestCreateSnap(t *testing.T, drivername string) { + driver := GetDriver(t, drivername) + defer PutDriver(t) + + createBase(t, driver, "Base") + + if err := driver.Create("Snap", "Base"); err != nil { + t.Fatal(err) + } + + verifyBase(t, driver, "Snap") + + if err := driver.Remove("Snap"); err != nil { + t.Fatal(err) + } + + if err := driver.Remove("Base"); err != nil { + t.Fatal(err) + } +} diff --git a/daemon/graphdriver/vfs/vfs_test.go b/daemon/graphdriver/vfs/vfs_test.go new file mode 100644 index 000000000..e79f93c91 --- /dev/null +++ b/daemon/graphdriver/vfs/vfs_test.go @@ -0,0 +1,28 @@ +package vfs + +import ( + "github.com/dotcloud/docker/daemon/graphdriver/graphtest" + "testing" +) + +// This avoids creating a new driver for each test if all tests are run +// Make sure to put new tests between TestVfsSetup and TestVfsTeardown +func TestVfsSetup(t *testing.T) { + graphtest.GetDriver(t, "vfs") +} + +func TestVfsCreateEmpty(t *testing.T) { + graphtest.DriverTestCreateEmpty(t, "vfs") +} + +func TestVfsCreateBase(t *testing.T) { + graphtest.DriverTestCreateBase(t, "vfs") +} + +func TestVfsCreateSnap(t *testing.T) { + graphtest.DriverTestCreateSnap(t, "vfs") +} + +func TestVfsTeardown(t *testing.T) { + graphtest.PutDriver(t) +} From 4bdb8c03fc9ac4c7c49fd9838d7eccdfd66e1c5b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 27 Mar 2014 17:41:06 +0100 Subject: [PATCH 030/400] graphdriver: Fail initialization if supported but got error If a graphdriver fails initialization due to ErrNotSupported we ignore that and keep trying the next. But if some driver has a different error (for instance if you specified an unknown option for it) we fail the daemon startup, printing the error, rather than falling back to an unexected driver (typically vfs) which may not match what you have run earlier. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- daemon/graphdriver/aufs/aufs.go | 2 +- daemon/graphdriver/aufs/aufs_test.go | 2 +- daemon/graphdriver/btrfs/btrfs.go | 2 +- daemon/graphdriver/driver.go | 22 +++++++++++++++------- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/daemon/graphdriver/aufs/aufs.go b/daemon/graphdriver/aufs/aufs.go index 12b7a77fb..2b7aa1b68 100644 --- a/daemon/graphdriver/aufs/aufs.go +++ b/daemon/graphdriver/aufs/aufs.go @@ -54,7 +54,7 @@ type Driver struct { func Init(root string) (graphdriver.Driver, error) { // Try to load the aufs kernel module if err := supportsAufs(); err != nil { - return nil, err + return nil, graphdriver.ErrNotSupported } paths := []string{ "mnt", diff --git a/daemon/graphdriver/aufs/aufs_test.go b/daemon/graphdriver/aufs/aufs_test.go index 1ffa264aa..dab5aecc4 100644 --- a/daemon/graphdriver/aufs/aufs_test.go +++ b/daemon/graphdriver/aufs/aufs_test.go @@ -19,7 +19,7 @@ var ( func testInit(dir string, t *testing.T) graphdriver.Driver { d, err := Init(dir) if err != nil { - if err == ErrAufsNotSupported { + if err == graphdriver.ErrNotSupported { t.Skip(err) } else { t.Fatal(err) diff --git a/daemon/graphdriver/btrfs/btrfs.go b/daemon/graphdriver/btrfs/btrfs.go index 4d195537e..614dc1ff0 100644 --- a/daemon/graphdriver/btrfs/btrfs.go +++ b/daemon/graphdriver/btrfs/btrfs.go @@ -31,7 +31,7 @@ func Init(home string) (graphdriver.Driver, error) { } if buf.Type != 0x9123683E { - return nil, fmt.Errorf("%s is not a btrfs filesystem", rootdir) + return nil, graphdriver.ErrNotSupported } return &Driver{ diff --git a/daemon/graphdriver/driver.go b/daemon/graphdriver/driver.go index 80bf8a014..96f8d3ab3 100644 --- a/daemon/graphdriver/driver.go +++ b/daemon/graphdriver/driver.go @@ -1,9 +1,9 @@ package graphdriver import ( + "errors" "fmt" "github.com/dotcloud/docker/archive" - "github.com/dotcloud/docker/utils" "os" "path" ) @@ -43,6 +43,8 @@ var ( "devicemapper", "vfs", } + + ErrNotSupported = errors.New("driver not supported") ) func init() { @@ -62,7 +64,7 @@ func GetDriver(name, home string) (Driver, error) { if initFunc, exists := drivers[name]; exists { return initFunc(path.Join(home, name)) } - return nil, fmt.Errorf("No such driver: %s", name) + return nil, ErrNotSupported } func New(root string) (driver Driver, err error) { @@ -74,9 +76,12 @@ func New(root string) (driver Driver, err error) { // Check for priority drivers first for _, name := range priority { - if driver, err = GetDriver(name, root); err != nil { - utils.Debugf("Error loading driver %s: %s", name, err) - continue + driver, err = GetDriver(name, root) + if err != nil { + if err == ErrNotSupported { + continue + } + return nil, err } return driver, nil } @@ -84,9 +89,12 @@ func New(root string) (driver Driver, err error) { // Check all registered drivers if no priority driver is found for _, initFunc := range drivers { if driver, err = initFunc(root); err != nil { - continue + if err == ErrNotSupported { + continue + } + return nil, err } return driver, nil } - return nil, err + return nil, fmt.Errorf("No supported storage backend found") } From 55cd7dd7f90d19332464ac946727297de1969483 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 6 May 2014 14:44:42 +0200 Subject: [PATCH 031/400] grapdriver: Skip tests on non-supported backends For now this means the btrfs backend is skipped when run inside make test. You can however run it manually if you want. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- daemon/graphdriver/graphtest/graphtest.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/daemon/graphdriver/graphtest/graphtest.go b/daemon/graphdriver/graphtest/graphtest.go index c83c840bc..f8ccb2ef3 100644 --- a/daemon/graphdriver/graphtest/graphtest.go +++ b/daemon/graphdriver/graphtest/graphtest.go @@ -31,6 +31,9 @@ func newDriver(t *testing.T, name string) *Driver { d, err := graphdriver.GetDriver(name, root) if err != nil { + if err == graphdriver.ErrNotSupported { + t.Skip("Driver %s not supported", name) + } t.Fatal(err) } return &Driver{d, root, 1} @@ -54,7 +57,7 @@ func GetDriver(t *testing.T, name string) graphdriver.Driver { func PutDriver(t *testing.T) { if drv == nil { - t.Fatal("No driver to put!") + t.Skip("No driver to put!") } drv.refCount-- if drv.refCount == 0 { From b5a37127aadbefea937346846116cb93966798dd Mon Sep 17 00:00:00 2001 From: Max Shytikov Date: Fri, 9 May 2014 14:42:27 +0200 Subject: [PATCH 032/400] Added value format description for VOLUME instruction In the documentation was not mentioned explicitly that VOLUME value shoud be a valid JSON array. Because of this I spent time to discovering the problem with my image where I put `VOLUME ['/data']` (with single quotes). The `['/data']` mount point was parsed and mounted whole as a string without any errors and warnings. Docker-DCO-1.1-Signed-off-by: Max Shytikov (github: mshytikov) --- docs/sources/reference/builder.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index 98e9e0f54..4c726aafa 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -325,8 +325,9 @@ optional but default, you could use a CMD: The `VOLUME` instruction will create a mount point with the specified name and mark it as holding externally mounted volumes from native host or other -containers. For more information/examples and mounting instructions via docker -client, refer to [*Share Directories via Volumes*]( +containers. The value can be a JSON array, `VOLUME ["/var/log/"]`, or a plain +string, `VOLUME /var/log`. For more information/examples and mounting +instructions via the Docker client, refer to [*Share Directories via Volumes*]( /use/working_with_volumes/#volume-def) documentation. ## USER From 5dfe0945c34402760475bbf51001d0ae0fb69ccb Mon Sep 17 00:00:00 2001 From: Aaron Huslage Date: Fri, 9 May 2014 13:56:20 -0400 Subject: [PATCH 033/400] Fixing pre-commit-msg URL URL referred to raw.github.com instead of raw.githubusercontent.com. Fixed. Docker-DCO-1.1-Signed-off-by: Aaron Huslage (github: huslage) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d77afbc44..6f07fcbcc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -182,7 +182,7 @@ One way to automate this, is customise your get ``commit.template`` by adding a ``prepare-commit-msg`` hook to your docker checkout: ``` -curl -o .git/hooks/prepare-commit-msg https://raw.github.com/dotcloud/docker/master/contrib/prepare-commit-msg.hook && chmod +x .git/hooks/prepare-commit-msg +curl -o .git/hooks/prepare-commit-msg https://raw.githubusercontent.com/dotcloud/docker/master/contrib/prepare-commit-msg.hook && chmod +x .git/hooks/prepare-commit-msg ``` * Note: the above script expects to find your GitHub user name in ``git config --get github.user`` From e304e3a6624e740159e99e83d6d13f0a3cdfeb49 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 9 May 2014 18:09:59 +0000 Subject: [PATCH 034/400] remove addString and replace Tail Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api/server/server.go | 48 ++++++++-------- engine/engine.go | 3 +- engine/job.go | 7 ++- engine/job_test.go | 17 +++--- engine/streams.go | 81 ++++++++------------------ engine/streams_test.go | 110 +++++------------------------------- integration/runtime_test.go | 39 +++++++------ integration/server_test.go | 26 ++++----- integration/utils_test.go | 8 ++- 9 files changed, 112 insertions(+), 227 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 3c93a3478..e2a7f651a 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -122,17 +122,17 @@ func postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter var ( authConfig, err = ioutil.ReadAll(r.Body) job = eng.Job("auth") - status string + stdoutBuffer = bytes.NewBuffer(nil) ) if err != nil { return err } job.Setenv("authConfig", string(authConfig)) - job.Stdout.AddString(&status) + job.Stdout.Add(stdoutBuffer) if err = job.Run(); err != nil { return err } - if status != "" { + if status := engine.Tail(stdoutBuffer, 1); status != "" { var env engine.Env env.Set("Status", status) return writeJSON(w, http.StatusOK, env) @@ -393,9 +393,10 @@ func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWrit return err } var ( - config engine.Env - env engine.Env - job = eng.Job("commit", r.Form.Get("container")) + config engine.Env + env engine.Env + job = eng.Job("commit", r.Form.Get("container")) + stdoutBuffer = bytes.NewBuffer(nil) ) if err := config.Decode(r.Body); err != nil { utils.Errorf("%s", err) @@ -407,12 +408,11 @@ func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWrit job.Setenv("comment", r.Form.Get("comment")) job.SetenvSubEnv("config", &config) - var id string - job.Stdout.AddString(&id) + job.Stdout.Add(stdoutBuffer) if err := job.Run(); err != nil { return err } - env.Set("Id", id) + env.Set("Id", engine.Tail(stdoutBuffer, 1)) return writeJSON(w, http.StatusCreated, env) } @@ -603,17 +603,17 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re return nil } var ( - out engine.Env - job = eng.Job("create", r.Form.Get("name")) - outWarnings []string - outId string - warnings = bytes.NewBuffer(nil) + out engine.Env + job = eng.Job("create", r.Form.Get("name")) + outWarnings []string + stdoutBuffer = bytes.NewBuffer(nil) + warnings = bytes.NewBuffer(nil) ) if err := job.DecodeEnv(r.Body); err != nil { return err } // Read container ID from the first line of stdout - job.Stdout.AddString(&outId) + job.Stdout.Add(stdoutBuffer) // Read warnings from stderr job.Stderr.Add(warnings) if err := job.Run(); err != nil { @@ -624,7 +624,7 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re for scanner.Scan() { outWarnings = append(outWarnings, scanner.Text()) } - out.Set("Id", outId) + out.Set("Id", engine.Tail(stdoutBuffer, 1)) out.SetList("Warnings", outWarnings) return writeJSON(w, http.StatusCreated, out) } @@ -720,20 +720,16 @@ func postContainersWait(eng *engine.Engine, version version.Version, w http.Resp return fmt.Errorf("Missing parameter") } var ( - env engine.Env - status string - job = eng.Job("wait", vars["name"]) + env engine.Env + stdoutBuffer = bytes.NewBuffer(nil) + job = eng.Job("wait", vars["name"]) ) - job.Stdout.AddString(&status) + job.Stdout.Add(stdoutBuffer) if err := job.Run(); err != nil { return err } - // Parse a 16-bit encoded integer to map typical unix exit status. - _, err := strconv.ParseInt(status, 10, 16) - if err != nil { - return err - } - env.Set("StatusCode", status) + + env.Set("StatusCode", engine.Tail(stdoutBuffer, 1)) return writeJSON(w, http.StatusOK, env) } diff --git a/engine/engine.go b/engine/engine.go index 58b43eca0..5c3228d5d 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -3,11 +3,12 @@ package engine import ( "bufio" "fmt" - "github.com/dotcloud/docker/utils" "io" "os" "sort" "strings" + + "github.com/dotcloud/docker/utils" ) // Installer is a standard interface for objects which can "install" themselves diff --git a/engine/job.go b/engine/job.go index b56155ac1..ca4d2ebcf 100644 --- a/engine/job.go +++ b/engine/job.go @@ -1,6 +1,7 @@ package engine import ( + "bytes" "fmt" "io" "strings" @@ -56,8 +57,8 @@ func (job *Job) Run() error { defer func() { job.Eng.Logf("-job %s%s", job.CallString(), job.StatusString()) }() - var errorMessage string - job.Stderr.AddString(&errorMessage) + var errorMessage = bytes.NewBuffer(nil) + job.Stderr.Add(errorMessage) if job.handler == nil { job.Errorf("%s: command not found", job.Name) job.status = 127 @@ -73,7 +74,7 @@ func (job *Job) Run() error { return err } if job.status != 0 { - return fmt.Errorf("%s", errorMessage) + return fmt.Errorf("%s", Tail(errorMessage, 1)) } return nil } diff --git a/engine/job_test.go b/engine/job_test.go index 1f927cbaf..67e723988 100644 --- a/engine/job_test.go +++ b/engine/job_test.go @@ -1,6 +1,8 @@ package engine import ( + "bytes" + "fmt" "testing" ) @@ -40,13 +42,13 @@ func TestJobStdoutString(t *testing.T) { }) job := eng.Job("say_something_in_stdout") - var output string - if err := job.Stdout.AddString(&output); err != nil { - t.Fatal(err) - } + var outputBuffer = bytes.NewBuffer(nil) + job.Stdout.Add(outputBuffer) if err := job.Run(); err != nil { t.Fatal(err) } + fmt.Println(outputBuffer) + var output = Tail(outputBuffer, 1) if expectedOutput := "Hello world"; output != expectedOutput { t.Fatalf("Stdout last line:\nExpected: %v\nReceived: %v", expectedOutput, output) } @@ -61,13 +63,12 @@ func TestJobStderrString(t *testing.T) { }) job := eng.Job("say_something_in_stderr") - var output string - if err := job.Stderr.AddString(&output); err != nil { - t.Fatal(err) - } + var outputBuffer = bytes.NewBuffer(nil) + job.Stderr.Add(outputBuffer) if err := job.Run(); err != nil { t.Fatal(err) } + var output = Tail(outputBuffer, 1) if expectedOutput := "Something happened"; output != expectedOutput { t.Fatalf("Stderr last line:\nExpected: %v\nReceived: %v", expectedOutput, output) } diff --git a/engine/streams.go b/engine/streams.go index 48f031de8..6d5511b2d 100644 --- a/engine/streams.go +++ b/engine/streams.go @@ -1,8 +1,7 @@ package engine import ( - "bufio" - "container/ring" + "bytes" "fmt" "io" "io/ioutil" @@ -16,6 +15,28 @@ type Output struct { used bool } +// Tail returns the n last lines of a buffer +// stripped out of the last \n, if any +// if n <= 0, returns an empty string +func Tail(buffer *bytes.Buffer, n int) string { + if n <= 0 { + return "" + } + bytes := buffer.Bytes() + if len(bytes) > 0 && bytes[len(bytes)-1] == '\n' { + bytes = bytes[:len(bytes)-1] + } + for i := buffer.Len() - 2; i >= 0; i-- { + if bytes[i] == '\n' { + n-- + if n == 0 { + return string(bytes[i+1:]) + } + } + } + return string(bytes) +} + // NewOutput returns a new Output object with no destinations attached. // Writing to an empty Output will cause the written data to be discarded. func NewOutput() *Output { @@ -58,42 +79,6 @@ func (o *Output) AddPipe() (io.Reader, error) { return r, nil } -// AddTail starts a new goroutine which will read all subsequent data written to the output, -// line by line, and append the last `n` lines to `dst`. -func (o *Output) AddTail(dst *[]string, n int) error { - src, err := o.AddPipe() - if err != nil { - return err - } - o.tasks.Add(1) - go func() { - defer o.tasks.Done() - Tail(src, n, dst) - }() - return nil -} - -// AddString starts a new goroutine which will read all subsequent data written to the output, -// line by line, and store the last line into `dst`. -func (o *Output) AddString(dst *string) error { - src, err := o.AddPipe() - if err != nil { - return err - } - o.tasks.Add(1) - go func() { - defer o.tasks.Done() - lines := make([]string, 0, 1) - Tail(src, 1, &lines) - if len(lines) == 0 { - *dst = "" - } else { - *dst = lines[0] - } - }() - return nil -} - // Write writes the same data to all registered destinations. // This method is thread-safe. func (o *Output) Write(p []byte) (n int, err error) { @@ -174,26 +159,6 @@ func (i *Input) Add(src io.Reader) error { return nil } -// Tail reads from `src` line per line, and returns the last `n` lines as an array. -// A ring buffer is used to only store `n` lines at any time. -func Tail(src io.Reader, n int, dst *[]string) { - scanner := bufio.NewScanner(src) - r := ring.New(n) - for scanner.Scan() { - if n == 0 { - continue - } - r.Value = scanner.Text() - r = r.Next() - } - r.Do(func(v interface{}) { - if v == nil { - return - } - *dst = append(*dst, v.(string)) - }) -} - // AddEnv starts a new goroutine which will decode all subsequent data // as a stream of json-encoded objects, and point `dst` to the last // decoded object. diff --git a/engine/streams_test.go b/engine/streams_test.go index 30d31d295..83dd05c6f 100644 --- a/engine/streams_test.go +++ b/engine/streams_test.go @@ -10,53 +10,6 @@ import ( "testing" ) -func TestOutputAddString(t *testing.T) { - var testInputs = [][2]string{ - { - "hello, world!", - "hello, world!", - }, - - { - "One\nTwo\nThree", - "Three", - }, - - { - "", - "", - }, - - { - "A line\nThen another nl-terminated line\n", - "Then another nl-terminated line", - }, - - { - "A line followed by an empty line\n\n", - "", - }, - } - for _, testData := range testInputs { - input := testData[0] - expectedOutput := testData[1] - o := NewOutput() - var output string - if err := o.AddString(&output); err != nil { - t.Error(err) - } - if n, err := o.Write([]byte(input)); err != nil { - t.Error(err) - } else if n != len(input) { - t.Errorf("Expected %d, got %d", len(input), n) - } - o.Close() - if output != expectedOutput { - t.Errorf("Last line is not stored as return string.\nInput: '%s'\nExpected: '%s'\nGot: '%s'", input, expectedOutput, output) - } - } -} - type sentinelWriteCloser struct { calledWrite bool calledClose bool @@ -145,59 +98,24 @@ func TestOutputAddPipe(t *testing.T) { } func TestTail(t *testing.T) { - var tests = make(map[string][][]string) - tests["hello, world!"] = [][]string{ - {}, - {"hello, world!"}, - {"hello, world!"}, - {"hello, world!"}, + var tests = make(map[string][]string) + tests["hello, world!"] = []string{ + "", + "hello, world!", + "hello, world!", + "hello, world!", } - tests["One\nTwo\nThree"] = [][]string{ - {}, - {"Three"}, - {"Two", "Three"}, - {"One", "Two", "Three"}, + tests["One\nTwo\nThree"] = []string{ + "", + "Three", + "Two\nThree", + "One\nTwo\nThree", } for input, outputs := range tests { for n, expectedOutput := range outputs { - var output []string - Tail(strings.NewReader(input), n, &output) - if fmt.Sprintf("%v", output) != fmt.Sprintf("%v", expectedOutput) { - t.Errorf("Tail n=%d returned wrong result.\nExpected: '%s'\nGot : '%s'", expectedOutput, output) - } - } - } -} - -func TestOutputAddTail(t *testing.T) { - var tests = make(map[string][][]string) - tests["hello, world!"] = [][]string{ - {}, - {"hello, world!"}, - {"hello, world!"}, - {"hello, world!"}, - } - tests["One\nTwo\nThree"] = [][]string{ - {}, - {"Three"}, - {"Two", "Three"}, - {"One", "Two", "Three"}, - } - for input, outputs := range tests { - for n, expectedOutput := range outputs { - o := NewOutput() - var output []string - if err := o.AddTail(&output, n); err != nil { - t.Error(err) - } - if n, err := o.Write([]byte(input)); err != nil { - t.Error(err) - } else if n != len(input) { - t.Errorf("Expected %d, got %d", len(input), n) - } - o.Close() - if fmt.Sprintf("%v", output) != fmt.Sprintf("%v", expectedOutput) { - t.Errorf("Tail(%d) returned wrong result.\nExpected: %v\nGot: %v", n, expectedOutput, output) + output := Tail(bytes.NewBufferString(input), n) + if output != expectedOutput { + t.Errorf("Tail n=%d returned wrong result.\nExpected: '%s'\nGot : '%s'", n, expectedOutput, output) } } } diff --git a/integration/runtime_test.go b/integration/runtime_test.go index c84ea5bed..9b79134f9 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -3,13 +3,6 @@ package docker import ( "bytes" "fmt" - "github.com/dotcloud/docker/daemon" - "github.com/dotcloud/docker/engine" - "github.com/dotcloud/docker/image" - "github.com/dotcloud/docker/nat" - "github.com/dotcloud/docker/runconfig" - "github.com/dotcloud/docker/sysinit" - "github.com/dotcloud/docker/utils" "io" "log" "net" @@ -22,6 +15,14 @@ import ( "syscall" "testing" "time" + + "github.com/dotcloud/docker/daemon" + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/image" + "github.com/dotcloud/docker/nat" + "github.com/dotcloud/docker/runconfig" + "github.com/dotcloud/docker/sysinit" + "github.com/dotcloud/docker/utils" ) const ( @@ -421,13 +422,14 @@ func TestGet(t *testing.T) { func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daemon.Container, string) { var ( - err error - id string - strPort string - eng = NewTestEngine(t) - daemon = mkDaemonFromEngine(eng, t) - port = 5554 - p nat.Port + err error + id string + outputBuffer = bytes.NewBuffer(nil) + strPort string + eng = NewTestEngine(t) + daemon = mkDaemonFromEngine(eng, t) + port = 5554 + p nat.Port ) defer func() { if err != nil { @@ -455,10 +457,11 @@ func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daem jobCreate.SetenvList("Cmd", []string{"sh", "-c", cmd}) jobCreate.SetenvList("PortSpecs", []string{fmt.Sprintf("%s/%s", strPort, proto)}) jobCreate.SetenvJson("ExposedPorts", ep) - jobCreate.Stdout.AddString(&id) + jobCreate.Stdout.Add(outputBuffer) if err := jobCreate.Run(); err != nil { t.Fatal(err) } + id = engine.Tail(outputBuffer, 1) // FIXME: this relies on the undocumented behavior of daemon.Create // which will return a nil error AND container if the exposed ports // are invalid. That behavior should be fixed! @@ -720,12 +723,12 @@ func TestContainerNameValidation(t *testing.T) { t.Fatal(err) } - var shortID string + var outputBuffer = bytes.NewBuffer(nil) job := eng.Job("create", test.Name) if err := job.ImportEnv(config); err != nil { t.Fatal(err) } - job.Stdout.AddString(&shortID) + job.Stdout.Add(outputBuffer) if err := job.Run(); err != nil { if !test.Valid { continue @@ -733,7 +736,7 @@ func TestContainerNameValidation(t *testing.T) { t.Fatal(err) } - container := daemon.Get(shortID) + container := daemon.Get(engine.Tail(outputBuffer, 1)) if container.Name != "/"+test.Name { t.Fatalf("Expect /%s got %s", test.Name, container.Name) diff --git a/integration/server_test.go b/integration/server_test.go index 226247556..4da3e6e36 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -1,12 +1,14 @@ package docker import ( - "github.com/dotcloud/docker/engine" - "github.com/dotcloud/docker/runconfig" - "github.com/dotcloud/docker/server" + "bytes" "strings" "testing" "time" + + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/runconfig" + "github.com/dotcloud/docker/server" ) func TestCreateNumberHostname(t *testing.T) { @@ -70,13 +72,13 @@ func TestMergeConfigOnCommit(t *testing.T) { job.Setenv("repo", "testrepo") job.Setenv("tag", "testtag") job.SetenvJson("config", config) - var newId string - job.Stdout.AddString(&newId) + var outputBuffer = bytes.NewBuffer(nil) + job.Stdout.Add(outputBuffer) if err := job.Run(); err != nil { t.Error(err) } - container2, _, _ := mkContainer(runtime, []string{newId}, t) + container2, _, _ := mkContainer(runtime, []string{engine.Tail(outputBuffer, 1)}, t) defer runtime.Destroy(container2) job = eng.Job("inspect", container1.Name, "container") @@ -168,8 +170,6 @@ func TestRestartKillWait(t *testing.T) { setTimeout(t, "Waiting on stopped container timedout", 5*time.Second, func() { job = srv.Eng.Job("wait", outs.Data[0].Get("Id")) - var statusStr string - job.Stdout.AddString(&statusStr) if err := job.Run(); err != nil { t.Fatal(err) } @@ -266,8 +266,6 @@ func TestRunWithTooLowMemoryLimit(t *testing.T) { job.Setenv("Memory", "524287") job.Setenv("CpuShares", "1000") job.SetenvList("Cmd", []string{"/bin/cat"}) - var id string - job.Stdout.AddString(&id) if err := job.Run(); err == nil { t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") } @@ -302,13 +300,13 @@ func TestRmi(t *testing.T) { job = eng.Job("commit", containerID) job.Setenv("repo", "test") - var imageID string - job.Stdout.AddString(&imageID) + var outputBuffer = bytes.NewBuffer(nil) + job.Stdout.Add(outputBuffer) if err := job.Run(); err != nil { t.Fatal(err) } - if err := eng.Job("tag", imageID, "test", "0.1").Run(); err != nil { + if err := eng.Job("tag", engine.Tail(outputBuffer, 1), "test", "0.1").Run(); err != nil { t.Fatal(err) } @@ -339,7 +337,7 @@ func TestRmi(t *testing.T) { t.Fatalf("Expected 2 new images, found %d.", images.Len()-initialImages.Len()) } - if err = srv.DeleteImage(imageID, engine.NewTable("", 0), true, false, false); err != nil { + if err = srv.DeleteImage(engine.Tail(outputBuffer, 1), engine.NewTable("", 0), true, false, false); err != nil { t.Fatal(err) } diff --git a/integration/utils_test.go b/integration/utils_test.go index 6901662ce..d8101dfb1 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -3,7 +3,6 @@ package docker import ( "bytes" "fmt" - "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" "io" "io/ioutil" "net/http" @@ -14,6 +13,8 @@ import ( "testing" "time" + "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" + "github.com/dotcloud/docker/builtins" "github.com/dotcloud/docker/daemon" "github.com/dotcloud/docker/engine" @@ -42,11 +43,12 @@ func createNamedTestContainer(eng *engine.Engine, config *runconfig.Config, f ut if err := job.ImportEnv(config); err != nil { f.Fatal(err) } - job.Stdout.AddString(&shortId) + var outputBuffer = bytes.NewBuffer(nil) + job.Stdout.Add(outputBuffer) if err := job.Run(); err != nil { f.Fatal(err) } - return + return engine.Tail(outputBuffer, 1) } func createTestContainer(eng *engine.Engine, config *runconfig.Config, f utils.Fataler) (shortId string) { From 2899195540857f2ad7c50ea509847c3a598c5e81 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 9 May 2014 21:41:18 +0000 Subject: [PATCH 035/400] returns an error when using -h and --net Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- runconfig/parse.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/runconfig/parse.go b/runconfig/parse.go index f27adc2ca..5a588c034 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -17,6 +17,7 @@ var ( ErrInvalidWorkingDirectory = fmt.Errorf("The working directory is invalid. It needs to be an absolute path.") ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d") ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d") + ErrConflictNetworkHostname = fmt.Errorf("Conflicting options: -h and --net") ) //FIXME Only used in tests @@ -101,6 +102,10 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf return nil, nil, cmd, ErrConflictDetachAutoRemove } + if *flNetMode != "bridge" && *flHostname != "" { + return nil, nil, cmd, ErrConflictNetworkHostname + } + // If neither -d or -a are set, attach to everything by default if flAttach.Len() == 0 && !*flDetach { if !*flDetach { From 670564d07096c190011bf7adf74afe4d61ef9720 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 9 May 2014 18:48:55 +0000 Subject: [PATCH 036/400] fix event removal Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- server/server.go | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/server/server.go b/server/server.go index 30e5ef1f6..3c3b0b4c4 100644 --- a/server/server.go +++ b/server/server.go @@ -195,6 +195,15 @@ func (srv *Server) ContainerKill(job *engine.Job) engine.Status { return engine.StatusOK } +func (srv *Server) EvictListener(from string) { + srv.Lock() + if old, ok := srv.listeners[from]; ok { + delete(srv.listeners, from) + close(old) + } + srv.Unlock() +} + func (srv *Server) Events(job *engine.Job) engine.Status { if len(job.Args) != 1 { return job.Errorf("Usage: %s FROM", job.Name) @@ -212,15 +221,7 @@ func (srv *Server) Events(job *engine.Job) engine.Status { return fmt.Errorf("JSON error") } _, err = job.Stdout.Write(b) - if err != nil { - // On error, evict the listener - utils.Errorf("%s", err) - srv.Lock() - delete(srv.listeners, from) - srv.Unlock() - return err - } - return nil + return err } listener := make(chan utils.JSONMessage) @@ -241,8 +242,9 @@ func (srv *Server) Events(job *engine.Job) engine.Status { continue } if err != nil { - job.Error(err) - return engine.StatusErr + // On error, evict the listener + srv.EvictListener(from) + return job.Error(err) } } } @@ -260,6 +262,8 @@ func (srv *Server) Events(job *engine.Job) engine.Status { continue } if err != nil { + // On error, evict the listener + srv.EvictListener(from) return job.Error(err) } case <-timeout.C: From 0aeff69e5900a15e3872494ac0009600f6c1c372 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 9 May 2014 15:39:55 -0700 Subject: [PATCH 037/400] Fix stdin handling in engine.Sender and engine.Receiver This introduces a superficial change to the Beam API: * `beam.SendPipe` is renamed to the more accurate `beam.SendRPipe` * `beam.SendWPipe` is introduced as a mirror to `SendRPipe` There is no other change in the beam API. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- engine/remote.go | 6 ++-- pkg/beam/beam.go | 41 ++++++++++++++++++++++++---- pkg/beam/examples/beamsh/beamsh.go | 4 +-- pkg/beam/examples/beamsh/builtins.go | 2 +- pkg/beam/router.go | 2 +- 5 files changed, 43 insertions(+), 12 deletions(-) diff --git a/engine/remote.go b/engine/remote.go index 60aad243c..e0c3c18e0 100644 --- a/engine/remote.go +++ b/engine/remote.go @@ -92,17 +92,17 @@ func (rcv *Receiver) Run() error { } cmd := data.Message(p).Get("cmd") job := rcv.Engine.Job(cmd[0], cmd[1:]...) - stdout, err := beam.SendPipe(peer, data.Empty().Set("cmd", "log", "stdout").Bytes()) + stdout, err := beam.SendRPipe(peer, data.Empty().Set("cmd", "log", "stdout").Bytes()) if err != nil { return err } job.Stdout.Add(stdout) - stderr, err := beam.SendPipe(peer, data.Empty().Set("cmd", "log", "stderr").Bytes()) + stderr, err := beam.SendRPipe(peer, data.Empty().Set("cmd", "log", "stderr").Bytes()) if err != nil { return err } job.Stderr.Add(stderr) - stdin, err := beam.SendPipe(peer, data.Empty().Set("cmd", "log", "stdin").Bytes()) + stdin, err := beam.SendWPipe(peer, data.Empty().Set("cmd", "log", "stdin").Bytes()) if err != nil { return err } diff --git a/pkg/beam/beam.go b/pkg/beam/beam.go index b1e4667a3..88d3ea7c7 100644 --- a/pkg/beam/beam.go +++ b/pkg/beam/beam.go @@ -29,17 +29,48 @@ type ReceiveSender interface { Sender } -func SendPipe(dst Sender, data []byte) (*os.File, error) { +const ( + R int = 1 << (32 - 1 - iota) + W +) + +func sendPipe(dst Sender, data []byte, mode int) (*os.File, error) { r, w, err := os.Pipe() if err != nil { return nil, err } - if err := dst.Send(data, r); err != nil { - r.Close() - w.Close() + var ( + remote *os.File + local *os.File + ) + if mode == R { + remote = r + local = w + } else if mode == W { + remote = w + local = r + } + if err := dst.Send(data, remote); err != nil { + local.Close() + remote.Close() return nil, err } - return w, nil + return local, nil + +} + +// SendRPipe create a pipe and sends its *read* end attached in a beam message +// to `dst`, with `data` as the message payload. +// It returns the *write* end of the pipe, or an error. +func SendRPipe(dst Sender, data []byte) (*os.File, error) { + return sendPipe(dst, data, R) +} + +// SendWPipe create a pipe and sends its *read* end attached in a beam message +// to `dst`, with `data` as the message payload. +// It returns the *write* end of the pipe, or an error. +func SendWPipe(dst Sender, data []byte) (*os.File, error) { + return sendPipe(dst, data, W) } func SendConn(dst Sender, data []byte) (conn *UnixConn, err error) { diff --git a/pkg/beam/examples/beamsh/beamsh.go b/pkg/beam/examples/beamsh/beamsh.go index 3f258de33..808f038c6 100644 --- a/pkg/beam/examples/beamsh/beamsh.go +++ b/pkg/beam/examples/beamsh/beamsh.go @@ -257,12 +257,12 @@ func Handlers(sink beam.Sender) (*beam.UnixConn, error) { if handler == nil { return } - stdout, err := beam.SendPipe(conn, data.Empty().Set("cmd", "log", "stdout").Set("fromcmd", cmd...).Bytes()) + stdout, err := beam.SendRPipe(conn, data.Empty().Set("cmd", "log", "stdout").Set("fromcmd", cmd...).Bytes()) if err != nil { return } defer stdout.Close() - stderr, err := beam.SendPipe(conn, data.Empty().Set("cmd", "log", "stderr").Set("fromcmd", cmd...).Bytes()) + stderr, err := beam.SendRPipe(conn, data.Empty().Set("cmd", "log", "stderr").Set("fromcmd", cmd...).Bytes()) if err != nil { return } diff --git a/pkg/beam/examples/beamsh/builtins.go b/pkg/beam/examples/beamsh/builtins.go index cc94d2b5f..3242237cc 100644 --- a/pkg/beam/examples/beamsh/builtins.go +++ b/pkg/beam/examples/beamsh/builtins.go @@ -272,7 +272,7 @@ func CmdPrint(args []string, stdout, stderr io.Writer, in beam.Receiver, out bea } // Skip commands if a != nil && data.Message(payload).Get("cmd") == nil { - dup, err := beam.SendPipe(out, payload) + dup, err := beam.SendRPipe(out, payload) if err != nil { a.Close() return diff --git a/pkg/beam/router.go b/pkg/beam/router.go index fc41a8991..15910e95b 100644 --- a/pkg/beam/router.go +++ b/pkg/beam/router.go @@ -78,7 +78,7 @@ func (route *Route) Tee(dst Sender) *Route { return inner(payload, attachment) } // Setup the tee - w, err := SendPipe(dst, payload) + w, err := SendRPipe(dst, payload) if err != nil { return err } From dfdc03b061d5bd5a7557f077b500304d4da26d2e Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 4 May 2014 00:21:53 +0000 Subject: [PATCH 038/400] Engine: fix a timeout bug in Sender/Receiver Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- engine/remote.go | 2 ++ engine/remote_test.go | 73 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/engine/remote.go b/engine/remote.go index e0c3c18e0..fbb995106 100644 --- a/engine/remote.go +++ b/engine/remote.go @@ -90,6 +90,8 @@ func (rcv *Receiver) Run() error { f.Close() return err } + f.Close() + defer peer.Close() cmd := data.Message(p).Get("cmd") job := rcv.Engine.Job(cmd[0], cmd[1:]...) stdout, err := beam.SendRPipe(peer, data.Empty().Set("cmd", "log", "stdout").Bytes()) diff --git a/engine/remote_test.go b/engine/remote_test.go index 54092ec93..a56120941 100644 --- a/engine/remote_test.go +++ b/engine/remote_test.go @@ -1,3 +1,74 @@ package engine -import () +import ( + "bytes" + "fmt" + "github.com/dotcloud/docker/pkg/beam" + "strings" + "testing" + "time" +) + +func TestHelloWorld(t *testing.T) { + testRemote(t, + + // Sender side + func(eng *Engine) { + job := eng.Job("echo", "hello", "world") + out := &bytes.Buffer{} + job.Stdout.Add(out) + job.Run() + if job.status != StatusOK { + t.Fatalf("#%v", job.StatusCode()) + } + if out.String() != "hello world\n" { + t.Fatalf("%#v", out.String()) + } + }, + + // Receiver side + func(eng *Engine) { + eng.Register("echo", func(job *Job) Status { + fmt.Fprintf(job.Stdout, "%s\n", strings.Join(job.Args, " ")) + return StatusOK + }) + }, + ) +} + +func testRemote(t *testing.T, senderSide, receiverSide func(*Engine)) { + sndConn, rcvConn, err := beam.USocketPair() + if err != nil { + t.Fatal(err) + } + defer sndConn.Close() + defer rcvConn.Close() + sender := NewSender(sndConn) + receiver := NewReceiver(rcvConn) + + // Setup the sender side + eng := New() + sender.Install(eng) + + // Setup the receiver side + receiverSide(receiver.Engine) + go receiver.Run() + + timeout(t, func() { + senderSide(eng) + }) +} + +func timeout(t *testing.T, f func()) { + onTimeout := time.After(100 * time.Millisecond) + onDone := make(chan bool) + go func() { + f() + close(onDone) + }() + select { + case <-onTimeout: + t.Fatalf("timeout") + case <-onDone: + } +} From bf25951837440286fb185f97f7c9cfda11962cab Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 4 May 2014 03:01:46 +0000 Subject: [PATCH 039/400] Engine: slightly more stress-testing of Receiver/Sender to reproduce the hang problem Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- engine/remote_test.go | 64 ++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/engine/remote_test.go b/engine/remote_test.go index a56120941..921a4376c 100644 --- a/engine/remote_test.go +++ b/engine/remote_test.go @@ -1,6 +1,7 @@ package engine import ( + "bufio" "bytes" "fmt" "github.com/dotcloud/docker/pkg/beam" @@ -10,32 +11,51 @@ import ( ) func TestHelloWorld(t *testing.T) { - testRemote(t, + for i := 0; i < 10; i++ { + testRemote(t, - // Sender side - func(eng *Engine) { - job := eng.Job("echo", "hello", "world") - out := &bytes.Buffer{} - job.Stdout.Add(out) - job.Run() - if job.status != StatusOK { - t.Fatalf("#%v", job.StatusCode()) - } - if out.String() != "hello world\n" { - t.Fatalf("%#v", out.String()) - } - }, + // Sender side + func(eng *Engine) { + job := eng.Job("echo", "hello", "world") + out := &bytes.Buffer{} + job.Stdout.Add(out) + job.Run() + if job.status != StatusOK { + t.Fatalf("#%v", job.StatusCode()) + } + lines := bufio.NewScanner(out) + var i int + for lines.Scan() { + if lines.Text() != "hello world" { + t.Fatalf("%#v", lines.Text()) + } + i++ + } + if i != 1000 { + t.Fatalf("%#v", i) + } + }, - // Receiver side - func(eng *Engine) { - eng.Register("echo", func(job *Job) Status { - fmt.Fprintf(job.Stdout, "%s\n", strings.Join(job.Args, " ")) - return StatusOK - }) - }, - ) + // Receiver side + func(eng *Engine) { + eng.Register("echo", func(job *Job) Status { + // Simulate more output with a delay in the middle + for i := 0; i < 500; i++ { + fmt.Fprintf(job.Stdout, "%s\n", strings.Join(job.Args, " ")) + } + time.Sleep(5 * time.Millisecond) + for i := 0; i < 500; i++ { + fmt.Fprintf(job.Stdout, "%s\n", strings.Join(job.Args, " ")) + } + return StatusOK + }) + }, + ) + } } +// Helpers + func testRemote(t *testing.T, senderSide, receiverSide func(*Engine)) { sndConn, rcvConn, err := beam.USocketPair() if err != nil { From d61190169de27bc58f1fe4a8f49b37bd2294d489 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 4 May 2014 03:29:55 +0000 Subject: [PATCH 040/400] Engine: ensure all pipes are properly closed by Receiver and Sender Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) [michael@docker.com: fix stdin closing in engine.Job.Run] [michael@docker.com: fix fd leak in engine.Receiver.Run] Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Docker-Tested-By: Solomon Hykes Docker-Tested-by: Michael Crosby --- engine/job.go | 3 +++ engine/remote.go | 21 ++++++++++++++------- engine/remote_test.go | 28 ++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/engine/job.go b/engine/job.go index b56155ac1..7e655e1b1 100644 --- a/engine/job.go +++ b/engine/job.go @@ -72,6 +72,9 @@ func (job *Job) Run() error { if err := job.Stderr.Close(); err != nil { return err } + if err := job.Stdin.Close(); err != nil { + return err + } if job.status != 0 { return fmt.Errorf("%s", errorMessage) } diff --git a/engine/remote.go b/engine/remote.go index fbb995106..1da521a3c 100644 --- a/engine/remote.go +++ b/engine/remote.go @@ -36,20 +36,27 @@ func (s *Sender) Handle(job *Job) Status { r := beam.NewRouter(nil) r.NewRoute().KeyStartsWith("cmd", "log", "stdout").HasAttachment().Handler(func(p []byte, stdout *os.File) error { tasks.Add(1) - io.Copy(job.Stdout, stdout) - tasks.Done() + go func() { + io.Copy(job.Stdout, stdout) + stdout.Close() + tasks.Done() + }() return nil }) r.NewRoute().KeyStartsWith("cmd", "log", "stderr").HasAttachment().Handler(func(p []byte, stderr *os.File) error { tasks.Add(1) - io.Copy(job.Stderr, stderr) - tasks.Done() + go func() { + io.Copy(job.Stderr, stderr) + stderr.Close() + tasks.Done() + }() return nil }) r.NewRoute().KeyStartsWith("cmd", "log", "stdin").HasAttachment().Handler(func(p []byte, stdin *os.File) error { - tasks.Add(1) - io.Copy(stdin, job.Stdin) - tasks.Done() + go func() { + io.Copy(stdin, job.Stdin) + stdin.Close() + }() return nil }) var status int diff --git a/engine/remote_test.go b/engine/remote_test.go index 921a4376c..a23830af0 100644 --- a/engine/remote_test.go +++ b/engine/remote_test.go @@ -5,6 +5,7 @@ import ( "bytes" "fmt" "github.com/dotcloud/docker/pkg/beam" + "io" "strings" "testing" "time" @@ -54,6 +55,33 @@ func TestHelloWorld(t *testing.T) { } } +func TestStdin(t *testing.T) { + testRemote(t, + + func(eng *Engine) { + job := eng.Job("mirror") + job.Stdin.Add(strings.NewReader("hello world!\n")) + out := &bytes.Buffer{} + job.Stdout.Add(out) + if err := job.Run(); err != nil { + t.Fatal(err) + } + if out.String() != "hello world!\n" { + t.Fatalf("%#v", out.String()) + } + }, + + func(eng *Engine) { + eng.Register("mirror", func(job *Job) Status { + if _, err := io.Copy(job.Stdout, job.Stdin); err != nil { + t.Fatal(err) + } + return StatusOK + }) + }, + ) +} + // Helpers func testRemote(t *testing.T, senderSide, receiverSide func(*Engine)) { From 2af030ab57d1d84ac9a1d22552dc9d83b16951c4 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 9 May 2014 17:01:27 -0700 Subject: [PATCH 041/400] beam/data: Message.GetOne() returns the last value set at a key This is a convenience for callers which are only interested in one value per key. Similar to how HTTP headers allow multiple keys per value, but are often used to store and retrieve only one value. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- pkg/beam/data/message.go | 10 ++++++++++ pkg/beam/data/message_test.go | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/pkg/beam/data/message.go b/pkg/beam/data/message.go index 193fb7b24..0ebe90295 100644 --- a/pkg/beam/data/message.go +++ b/pkg/beam/data/message.go @@ -72,6 +72,16 @@ func (m Message) Get(k string) []string { return v } +// GetOne returns the last value added at the key k, +// or an empty string if there is no value. +func (m Message) GetOne(k string) string { + var v string + if vals := m.Get(k); len(vals) > 0 { + v = vals[len(vals)-1] + } + return v +} + func (m Message) Pretty() string { data, err := Decode(string(m)) if err != nil { diff --git a/pkg/beam/data/message_test.go b/pkg/beam/data/message_test.go index 768576906..7224f33d1 100644 --- a/pkg/beam/data/message_test.go +++ b/pkg/beam/data/message_test.go @@ -51,3 +51,11 @@ func TestSetDelMessage(t *testing.T) { t.Fatalf("'%v' != '%v'", output, expectedOutput) } } + +func TestGetOne(t *testing.T) { + m := Empty().Set("shadok words", "ga", "bu", "zo", "meu") + val := m.GetOne("shadok words") + if val != "meu" { + t.Fatalf("%#v", val) + } +} From c7978c98097b92b659e2ea1763cd134b0695407e Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 9 May 2014 17:06:32 -0700 Subject: [PATCH 042/400] Engine: Env.MultiMap, Env.InitMultiMap: import/export to other formats * `Env.MultiMap` returns the contents of an Env as `map[string][]string` * `Env.InitMultiMap` initializes the contents of an Env from a `map[string][]string` This makes it easier to import and export an Env to other formats (specifically `beam/data` messages) Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- engine/env.go | 24 ++++++++++++++++++++++++ engine/env_test.go | 20 ++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/engine/env.go b/engine/env.go index 94d8c3d8a..f63f29e10 100644 --- a/engine/env.go +++ b/engine/env.go @@ -250,3 +250,27 @@ func (env *Env) Map() map[string]string { } return m } + +// MultiMap returns a representation of env as a +// map of string arrays, keyed by string. +// This is the same structure as http headers for example, +// which allow each key to have multiple values. +func (env *Env) MultiMap() map[string][]string { + m := make(map[string][]string) + for _, kv := range *env { + parts := strings.SplitN(kv, "=", 2) + m[parts[0]] = append(m[parts[0]], parts[1]) + } + return m +} + +// InitMultiMap removes all values in env, then initializes +// new values from the contents of m. +func (env *Env) InitMultiMap(m map[string][]string) { + (*env) = make([]string, 0, len(m)) + for k, vals := range m { + for _, v := range vals { + env.Set(k, v) + } + } +} diff --git a/engine/env_test.go b/engine/env_test.go index 0c66cea04..39669d678 100644 --- a/engine/env_test.go +++ b/engine/env_test.go @@ -123,3 +123,23 @@ func TestEnviron(t *testing.T) { t.Fatalf("bar not found in the environ") } } + +func TestMultiMap(t *testing.T) { + e := &Env{} + e.Set("foo", "bar") + e.Set("bar", "baz") + e.Set("hello", "world") + m := e.MultiMap() + e2 := &Env{} + e2.Set("old_key", "something something something") + e2.InitMultiMap(m) + if v := e2.Get("old_key"); v != "" { + t.Fatalf("%#v", v) + } + if v := e2.Get("bar"); v != "baz" { + t.Fatalf("%#v", v) + } + if v := e2.Get("hello"); v != "world" { + t.Fatalf("%#v", v) + } +} From a1754c7e46b321c2a079fc5186984a847aa50218 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 9 May 2014 17:10:33 -0700 Subject: [PATCH 043/400] Engine: Receiver and Sender preserve Job.Env When sending a new job to a `engine.Sender`, the corresponding `engine.Receiver` will receive that job with its environment preserved. Previously the job name, arguments and streams were preserved but the env was lost. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- engine/remote.go | 13 +++++++++++-- engine/remote_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/engine/remote.go b/engine/remote.go index 1da521a3c..974ca0213 100644 --- a/engine/remote.go +++ b/engine/remote.go @@ -25,7 +25,9 @@ func (s *Sender) Install(eng *Engine) error { } func (s *Sender) Handle(job *Job) Status { - msg := data.Empty().Set("cmd", append([]string{job.Name}, job.Args...)...) + cmd := append([]string{job.Name}, job.Args...) + env := data.Encode(job.Env().MultiMap()) + msg := data.Empty().Set("cmd", cmd...).Set("env", env) peer, err := beam.SendConn(s, msg.Bytes()) if err != nil { return job.Errorf("beamsend: %v", err) @@ -99,8 +101,15 @@ func (rcv *Receiver) Run() error { } f.Close() defer peer.Close() - cmd := data.Message(p).Get("cmd") + msg := data.Message(p) + cmd := msg.Get("cmd") job := rcv.Engine.Job(cmd[0], cmd[1:]...) + // Decode env + env, err := data.Decode(msg.GetOne("env")) + if err != nil { + return fmt.Errorf("error decoding 'env': %v", err) + } + job.Env().InitMultiMap(env) stdout, err := beam.SendRPipe(peer, data.Empty().Set("cmd", "log", "stdout").Bytes()) if err != nil { return err diff --git a/engine/remote_test.go b/engine/remote_test.go index a23830af0..e59ac78cc 100644 --- a/engine/remote_test.go +++ b/engine/remote_test.go @@ -82,6 +82,47 @@ func TestStdin(t *testing.T) { ) } +func TestEnv(t *testing.T) { + var ( + foo string + answer int + shadok_words []string + ) + testRemote(t, + + func(eng *Engine) { + job := eng.Job("sendenv") + job.Env().Set("foo", "bar") + job.Env().SetInt("answer", 42) + job.Env().SetList("shadok_words", []string{"ga", "bu", "zo", "meu"}) + if err := job.Run(); err != nil { + t.Fatal(err) + } + }, + + func(eng *Engine) { + eng.Register("sendenv", func(job *Job) Status { + foo = job.Env().Get("foo") + answer = job.Env().GetInt("answer") + shadok_words = job.Env().GetList("shadok_words") + return StatusOK + }) + }, + ) + // Check for results here rather than inside the job handler, + // otherwise the tests may incorrectly pass if the handler is not + // called. + if foo != "bar" { + t.Fatalf("%#v", foo) + } + if answer != 42 { + t.Fatalf("%#v", answer) + } + if strings.Join(shadok_words, ", ") != "ga, bu, zo, meu" { + t.Fatalf("%#v", shadok_words) + } +} + // Helpers func testRemote(t *testing.T, senderSide, receiverSide func(*Engine)) { From b4ccd7cbfb5f2c7c4b6c963c4c12e41500e7ad55 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Fri, 9 May 2014 18:05:54 -0700 Subject: [PATCH 044/400] fix(daemon): ensure the /var/lib/docker dir exists The btrfs driver attempts to stat the /var/lib/docker directory to ensure it exists. If it doesn't exist then it bails with an unhelpful log line: ``` 2014/05/10 00:51:30 no such file or directory ``` In 0.10 the directory was created but quickly digging through the logs I can't tell what sort of re-ordering of code caused this regression. Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- daemon/daemon.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/daemon/daemon.go b/daemon/daemon.go index 00b6d9eee..7901f8ec6 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -680,6 +680,12 @@ func NewDaemonFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (*D if !config.EnableSelinuxSupport { selinux.SetDisabled() } + + // Create the root directory if it doesn't exists + if err := os.MkdirAll(config.Root, 0700); err != nil && !os.IsExist(err) { + return nil, err + } + // Set the default driver graphdriver.DefaultDriver = config.GraphDriver From 46a47f0dcbce1a4240bc970388dd09d029f82f61 Mon Sep 17 00:00:00 2001 From: cyphar Date: Fri, 9 May 2014 20:36:58 +1000 Subject: [PATCH 045/400] Dockerfile: updated ubuntu release used for builds This patch just revs the Ubuntu release used for builds from 13.10 to 14.04. Docker-DCO-1.1-Signed-off-by: Aleksa Sarai (github: cyphar) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index be2233ff8..8a8818d75 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ # docker-version 0.6.1 -FROM ubuntu:13.10 +FROM ubuntu:14.04 MAINTAINER Tianon Gravi (@tianon) # Packaged dependencies From ff4ef504708bfaa51d4d361455689a21a031cc35 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 27 Apr 2014 23:59:46 -0700 Subject: [PATCH 046/400] Start separating the image subsystem This is a first step towards moving all code related to local manipulation of images into a cleanly separated subsystem, accessible via a stable set of commands in the engine API. `graph.TagStore` now implements `engine.Installer`. For now, it is installed by `Server.InitServer`, along with all other Server commands. However this will change in future patches. `graph.TagStore.Install` registers the following commands: * `image_set` creates a new image and stores it locally. * `image_get` returns information about an image stored locally. * `image_tag` assigns a new name and tag to an existing image. These commands are a pre-requisite for moving 'push' and 'pull' out of `Server`. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- graph/service.go | 128 +++++++++++++++++++++++++++++++++++++++++++++++ server/server.go | 7 ++- 2 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 graph/service.go diff --git a/graph/service.go b/graph/service.go new file mode 100644 index 000000000..211babd0b --- /dev/null +++ b/graph/service.go @@ -0,0 +1,128 @@ +package graph + +import ( + "fmt" + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/image" + "github.com/dotcloud/docker/utils" +) + +func (s *TagStore) Install(eng *engine.Engine) error { + eng.Register("image_set", s.CmdSet) + eng.Register("image_tag", s.CmdTag) + eng.Register("image_get", s.CmdGet) + return nil +} + +// CmdSet stores a new image in the graph. +// Images are stored in the graph using 4 elements: +// - A user-defined ID +// - A collection of metadata describing the image +// - A directory tree stored as a tar archive (also called the "layer") +// - A reference to a "parent" ID on top of which the layer should be applied +// +// NOTE: even though the parent ID is only useful in relation to the layer and how +// to apply it (ie you could represent the full directory tree as 'parent_layer + layer', +// it is treated as a top-level property of the image. This is an artifact of early +// design and should probably be cleaned up in the future to simplify the design. +// +// Syntax: image_set ID +// Input: +// - Layer content must be streamed in tar format on stdin. An empty input is +// valid and represents a nil layer. +// +// - Image metadata must be passed in the command environment. +// 'json': a json-encoded object with all image metadata. +// It will be stored as-is, without any encoding/decoding artifacts. +// That is a requirement of the current registry client implementation, +// because a re-encoded json might invalidate the image checksum at +// the next upload, even with functionaly identical content. +func (s *TagStore) CmdSet(job *engine.Job) engine.Status { + if len(job.Args) != 1 { + return job.Errorf("usage: %s NAME", job.Name) + } + var ( + imgJSON = []byte(job.Getenv("json")) + layer = job.Stdin + ) + if len(imgJSON) == 0 { + return job.Errorf("mandatory key 'json' is not set") + } + // We have to pass an *image.Image object, even though it will be completely + // ignored in favor of the redundant json data. + // FIXME: the current prototype of Graph.Register is stupid and redundant. + img, err := image.NewImgJSON(imgJSON) + if err != nil { + return job.Error(err) + } + if err := s.graph.Register(imgJSON, layer, img); err != nil { + return job.Error(err) + } + return engine.StatusOK +} + +// CmdTag assigns a new name and tag to an existing image. If the tag already exists, +// it is changed and the image previously referenced by the tag loses that reference. +// This may cause the old image to be garbage-collected if its reference count reaches zero. +// +// Syntax: image_tag NEWNAME OLDNAME +// Example: image_tag shykes/myapp:latest shykes/myapp:1.42.0 +func (s *TagStore) CmdTag(job *engine.Job) engine.Status { + if len(job.Args) != 2 { + return job.Errorf("usage: %s NEWNAME OLDNAME", job.Name) + } + var ( + newName = job.Args[0] + oldName = job.Args[1] + ) + newRepo, newTag := utils.ParseRepositoryTag(newName) + // FIXME: Set should either parse both old and new name, or neither. + // the current prototype is inconsistent. + if err := s.Set(newRepo, newTag, oldName, true); err != nil { + return job.Error(err) + } + return engine.StatusOK +} + +// CmdGet returns information about an image. +// If the image doesn't exist, an empty object is returned, to allow +// checking for an image's existence. +func (s *TagStore) CmdGet(job *engine.Job) engine.Status { + if len(job.Args) != 1 { + return job.Errorf("usage: %s NAME", job.Name) + } + name := job.Args[0] + res := &engine.Env{} + img, err := s.LookupImage(name) + // Note: if the image doesn't exist, LookupImage returns + // nil, nil. + if err != nil { + return job.Error(err) + } + if img != nil { + // We don't directly expose all fields of the Image objects, + // to maintain a clean public API which we can maintain over + // time even if the underlying structure changes. + // We should have done this with the Image object to begin with... + // but we didn't, so now we're doing it here. + // + // Fields that we're probably better off not including: + // - ID (the caller already knows it, and we stay more flexible on + // naming down the road) + // - Parent. That field is really an implementation detail of + // layer storage ("layer is a diff against this other layer). + // It doesn't belong at the same level as author/description/etc. + // - Config/ContainerConfig. Those structs have the same sprawl problem, + // so we shouldn't include them wholesale either. + // - Comment: initially created to fulfill the "every image is a git commit" + // metaphor, in practice people either ignore it or use it as a + // generic description field which it isn't. On deprecation shortlist. + res.Set("created", fmt.Sprintf("%v", img.Created)) + res.Set("author", img.Author) + res.Set("os", img.OS) + res.Set("architecture", img.Architecture) + res.Set("docker_version", img.DockerVersion) + } + res.WriteTo(job.Stdout) + return engine.StatusOK +} diff --git a/server/server.go b/server/server.go index 30e5ef1f6..ac2bb0cad 100644 --- a/server/server.go +++ b/server/server.go @@ -113,7 +113,7 @@ func InitServer(job *engine.Job) engine.Status { "start": srv.ContainerStart, "kill": srv.ContainerKill, "wait": srv.ContainerWait, - "tag": srv.ImageTag, + "tag": srv.ImageTag, // FIXME merge with "image_tag" "resize": srv.ContainerResize, "commit": srv.ContainerCommit, "info": srv.DockerInfo, @@ -143,6 +143,11 @@ func InitServer(job *engine.Job) engine.Status { return job.Error(err) } } + // Install image-related commands from the image subsystem. + // See `graph/service.go` + if err := srv.daemon.Repositories().Install(job.Eng); err != nil { + return job.Error(err) + } return engine.StatusOK } From 752c57ae567813f354aca66ff51d8d64100ae01b Mon Sep 17 00:00:00 2001 From: Fernando Date: Sat, 10 May 2014 18:05:02 -0400 Subject: [PATCH 047/400] Fix bug on LXC container start. Fixes #5718 Docker-DCO-1.1-Signed-off-by: Fernando Mayo (github: fermayo) --- daemon/execdriver/lxc/driver.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index d787d8d87..0b9817621 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -268,18 +268,14 @@ func (d *driver) waitForStart(c *execdriver.Command, waitLock chan struct{}) (in } output, err = d.getInfo(c.ID) - if err != nil { - output, err = d.getInfo(c.ID) + if err == nil { + info, err := parseLxcInfo(string(output)) if err != nil { return -1, err } - } - info, err := parseLxcInfo(string(output)) - if err != nil { - return -1, err - } - if info.Running { - return info.Pid, nil + if info.Running { + return info.Pid, nil + } } time.Sleep(50 * time.Millisecond) } From 3f8ffb461ab66535b3c3a96a564c06db12a27281 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Sun, 11 May 2014 01:03:12 -0700 Subject: [PATCH 048/400] Remove signal_freebsd (already in pkg/signal) Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- utils/signal_freebsd.go | 42 ----------------------------------------- 1 file changed, 42 deletions(-) delete mode 100644 utils/signal_freebsd.go diff --git a/utils/signal_freebsd.go b/utils/signal_freebsd.go deleted file mode 100644 index 65a700e89..000000000 --- a/utils/signal_freebsd.go +++ /dev/null @@ -1,42 +0,0 @@ -package utils - -import ( - "os" - "os/signal" - "syscall" -) - -func CatchAll(sigc chan os.Signal) { - signal.Notify(sigc, - syscall.SIGABRT, - syscall.SIGALRM, - syscall.SIGBUS, - syscall.SIGCHLD, - syscall.SIGCONT, - syscall.SIGFPE, - syscall.SIGHUP, - syscall.SIGILL, - syscall.SIGINT, - syscall.SIGIO, - syscall.SIGIOT, - syscall.SIGKILL, - syscall.SIGPIPE, - syscall.SIGPROF, - syscall.SIGQUIT, - syscall.SIGSEGV, - syscall.SIGSTOP, - syscall.SIGSYS, - syscall.SIGTERM, - syscall.SIGTRAP, - syscall.SIGTSTP, - syscall.SIGTTIN, - syscall.SIGTTOU, - syscall.SIGURG, - syscall.SIGUSR1, - syscall.SIGUSR2, - syscall.SIGVTALRM, - syscall.SIGWINCH, - syscall.SIGXCPU, - syscall.SIGXFSZ, - ) -} From a4a023580bc89fb34e5f80ca472fc70267546eaf Mon Sep 17 00:00:00 2001 From: Vincent Mayers Date: Fri, 2 May 2014 14:13:50 -0400 Subject: [PATCH 049/400] Add Licensing information to README Docker-DCO-1.1-Signed-off-by: Vincent Mayers vince.mayers@gmail.com (github: vincentmayers) [solomon@docker.com: rephrased commit message and transcribed signoff] Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index fae1bb916..c965efafe 100644 --- a/README.md +++ b/README.md @@ -190,3 +190,9 @@ It is your responsibility to ensure that your use and/or transfer does not violate applicable laws. For more information, please see http://www.bis.doc.gov + + +Licensing +========= +Docker is licensed under the Apache License, Version 2.0. See LICENSE for full license text. + From 62bfef59f7ae6f9128bfc3e7ef2e6ed5e4441d2e Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Sun, 11 May 2014 06:12:48 -0700 Subject: [PATCH 050/400] Use int64 instead of int Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- pkg/beam/beam.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/beam/beam.go b/pkg/beam/beam.go index 88d3ea7c7..2e8895a15 100644 --- a/pkg/beam/beam.go +++ b/pkg/beam/beam.go @@ -30,7 +30,7 @@ type ReceiveSender interface { } const ( - R int = 1 << (32 - 1 - iota) + R = iota W ) From 7cc27b2075d6293ab2f54a7255fcb1b1e062fd91 Mon Sep 17 00:00:00 2001 From: Fabio Falci Date: Sat, 10 May 2014 18:27:24 +0100 Subject: [PATCH 051/400] Integration test for link and unlink containers Docker-DCO-1.1-Signed-off-by: Fabio Falci (github: fabiofalci) --- integration-cli/docker_cli_links_test.go | 26 ++++++++++++++++++++++++ integration-cli/docker_utils.go | 10 +++++++++ 2 files changed, 36 insertions(+) diff --git a/integration-cli/docker_cli_links_test.go b/integration-cli/docker_cli_links_test.go index 55c41e0bb..ed30288b7 100644 --- a/integration-cli/docker_cli_links_test.go +++ b/integration-cli/docker_cli_links_test.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "github.com/dotcloud/docker/pkg/iptables" "os/exec" "testing" ) @@ -28,3 +29,28 @@ func TestPingLinkedContainers(t *testing.T) { cmd(t, "kill", idB) deleteAllContainers() } + +func TestIpTablesRulesWhenLinkAndUnlink(t *testing.T) { + cmd(t, "run", "-d", "--name", "child", "--publish", "8080:80", "busybox", "sleep", "10") + cmd(t, "run", "-d", "--name", "parent", "--link", "child:http", "busybox", "sleep", "10") + + childIp := findContainerIp(t, "child") + parentIp := findContainerIp(t, "parent") + + sourceRule := []string{"FORWARD", "-i", "docker0", "-o", "docker0", "-p", "tcp", "-s", childIp, "--sport", "80", "-d", parentIp, "-j", "ACCEPT"} + destinationRule := []string{"FORWARD", "-i", "docker0", "-o", "docker0", "-p", "tcp", "-s", parentIp, "--dport", "80", "-d", childIp, "-j", "ACCEPT"} + if !iptables.Exists(sourceRule...) || !iptables.Exists(destinationRule...) { + t.Fatal("Iptables rules not found") + } + + cmd(t, "rm", "--link", "parent/http") + if iptables.Exists(sourceRule...) || iptables.Exists(destinationRule...) { + t.Fatal("Iptables rules should be removed when unlink") + } + + cmd(t, "kill", "child") + cmd(t, "kill", "parent") + deleteAllContainers() + + logDone("link - verify iptables when link and unlink") +} diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 6da86c975..17a331f2d 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -61,3 +61,13 @@ func cmd(t *testing.T, args ...string) (string, int, error) { errorOut(err, t, fmt.Sprintf("'%s' failed with errors: %v (%v)", strings.Join(args, " "), err, out)) return out, status, err } + +func findContainerIp(t *testing.T, id string) string { + cmd := exec.Command(dockerBinary, "inspect", "--format='{{ .NetworkSettings.IPAddress }}'", id) + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + + return strings.Trim(out, " \r\n'") +} From 0d59cc080a8dc115bdb727d376e3965e58109718 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Mon, 12 May 2014 10:31:27 +1000 Subject: [PATCH 052/400] Give the Redirect a HostName - filled in from the s3 bucket name. Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/Dockerfile | 2 +- docs/release.sh | 7 +++++-- docs/s3_website.json | 12 ++++++------ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index a907072dd..694729d89 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -4,7 +4,7 @@ FROM debian:jessie MAINTAINER Sven Dowideit (@SvenDowideit) -RUN apt-get update && apt-get install -yq make python-pip python-setuptools vim-tiny git pandoc +RUN apt-get update && apt-get install -yq make python-pip python-setuptools vim-tiny git gettext RUN pip install mkdocs diff --git a/docs/release.sh b/docs/release.sh index 323887f59..acb40a46d 100755 --- a/docs/release.sh +++ b/docs/release.sh @@ -19,7 +19,7 @@ EOF [ "$AWS_S3_BUCKET" ] || usage #VERSION=$(cat VERSION) -BUCKET=$AWS_S3_BUCKET +export BUCKET=$AWS_S3_BUCKET export AWS_CONFIG_FILE=$(pwd)/awsconfig [ -e "$AWS_CONFIG_FILE" ] || usage @@ -37,7 +37,10 @@ setup_s3() { # Make the bucket accessible through website endpoints. echo "make $BUCKET accessible as a website" #aws s3 website s3://$BUCKET --index-document index.html --error-document jsearch/index.html - s3conf=$(cat s3_website.json) + s3conf=$(cat s3_website.json | envsubst) + echo + echo $s3conf + echo aws s3api put-bucket-website --bucket $BUCKET --website-configuration "$s3conf" } diff --git a/docs/s3_website.json b/docs/s3_website.json index fb14628ce..2d158cf9d 100644 --- a/docs/s3_website.json +++ b/docs/s3_website.json @@ -6,12 +6,12 @@ "Suffix": "index.html" }, "RoutingRules": [ - { "Condition": { "KeyPrefixEquals": "en/latest/" }, "Redirect": { "ReplaceKeyPrefixWith": "" } }, - { "Condition": { "KeyPrefixEquals": "en/master/" }, "Redirect": { "ReplaceKeyPrefixWith": "" } }, - { "Condition": { "KeyPrefixEquals": "en/v0.6.3/" }, "Redirect": { "ReplaceKeyPrefixWith": "" } }, - { "Condition": { "KeyPrefixEquals": "jsearch/index.html" }, "Redirect": { "ReplaceKeyPrefixWith": "jsearch/" } }, - { "Condition": { "KeyPrefixEquals": "index/" }, "Redirect": { "ReplaceKeyPrefixWith": "docker-io/" } }, - { "Condition": { "KeyPrefixEquals": "reference/api/index_api/" }, "Redirect": { "ReplaceKeyPrefixWith": "reference/api/docker-io_api/" } } + { "Condition": { "KeyPrefixEquals": "en/latest/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "" } }, + { "Condition": { "KeyPrefixEquals": "en/master/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "" } }, + { "Condition": { "KeyPrefixEquals": "en/v0.6.3/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "" } }, + { "Condition": { "KeyPrefixEquals": "jsearch/index.html" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "jsearch/" } }, + { "Condition": { "KeyPrefixEquals": "index/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "docker-io/" } }, + { "Condition": { "KeyPrefixEquals": "reference/api/index_api/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "reference/api/docker-io_api/" } } ] } From 1dec8fd03c4f9c99491d3019f56772cccaf100fd Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Mon, 12 May 2014 11:31:44 +1000 Subject: [PATCH 053/400] reduce cache time to 1 hours so future docs releases update the cloudfront caches faster Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release.sh b/docs/release.sh index 323887f59..1ca648e57 100755 --- a/docs/release.sh +++ b/docs/release.sh @@ -54,7 +54,7 @@ upload_current_documentation() { echo " to $dst" echo #s3cmd --recursive --follow-symlinks --preserve --acl-public sync "$src" "$dst" - aws s3 sync --acl public-read --exclude "*.rej" --exclude "*.rst" --exclude "*.orig" --exclude "*.py" "$src" "$dst" + aws s3 sync --cache-control "max-age=3600" --acl public-read --exclude "*.rej" --exclude "*.rst" --exclude "*.orig" --exclude "*.py" "$src" "$dst" } setup_s3 From c1108f5d605c30a42bb494920b50d486c6e404a4 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Mon, 12 May 2014 14:50:41 +1000 Subject: [PATCH 054/400] the last remaining raw.github.com URL's Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- contrib/desktop-integration/data/Dockerfile | 2 +- contrib/desktop-integration/iceweasel/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/desktop-integration/data/Dockerfile b/contrib/desktop-integration/data/Dockerfile index 76846af91..236912f90 100644 --- a/contrib/desktop-integration/data/Dockerfile +++ b/contrib/desktop-integration/data/Dockerfile @@ -6,7 +6,7 @@ # /data volume is owned by sysadmin. # USAGE: # # Download data Dockerfile -# wget http://raw.github.com/dotcloud/docker/master/contrib/desktop-integration/data/Dockerfile +# wget http://raw.githubusercontent.com/dotcloud/docker/master/contrib/desktop-integration/data/Dockerfile # # # Build data image # docker build -t data . diff --git a/contrib/desktop-integration/iceweasel/Dockerfile b/contrib/desktop-integration/iceweasel/Dockerfile index f9f58c9ca..80d6a55e4 100644 --- a/contrib/desktop-integration/iceweasel/Dockerfile +++ b/contrib/desktop-integration/iceweasel/Dockerfile @@ -7,7 +7,7 @@ # sound devices. Tested on Debian 7.2 # USAGE: # # Download Iceweasel Dockerfile -# wget http://raw.github.com/dotcloud/docker/master/contrib/desktop-integration/iceweasel/Dockerfile +# wget http://raw.githubusercontent.com/dotcloud/docker/master/contrib/desktop-integration/iceweasel/Dockerfile # # # Build iceweasel image # docker build -t iceweasel . From 70ef53f25e177e42046170ef59bb29ebd77a3016 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 8 May 2014 19:58:33 +0200 Subject: [PATCH 055/400] libcontainer: Create dirs/files as needed for bind mounts If you specify a bind mount in a place that doesn't have a file yet we create that (and parent directories). This is needed because otherwise you can't use volumes like e.g. /dev/log, as that gets covered by the /dev tmpfs mounts. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- pkg/libcontainer/mount/init.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/pkg/libcontainer/mount/init.go b/pkg/libcontainer/mount/init.go index cfe61d153..b0a3ef106 100644 --- a/pkg/libcontainer/mount/init.go +++ b/pkg/libcontainer/mount/init.go @@ -91,6 +91,28 @@ func mountSystem(rootfs string, container *libcontainer.Container) error { return nil } +func createIfNotExists(path string, isDir bool) error { + if _, err := os.Stat(path); err != nil { + if os.IsNotExist(err) { + if isDir { + if err := os.MkdirAll(path, 0755); err != nil { + return err + } + } else { + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + return err + } + f, err := os.OpenFile(path, os.O_CREATE, 0755) + if err != nil { + return err + } + f.Close() + } + } + } + return nil +} + func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error { for _, m := range bindMounts.OfType("bind") { var ( @@ -100,6 +122,15 @@ func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error { if !m.Writable { flags = flags | syscall.MS_RDONLY } + + stat, err := os.Stat(m.Source) + if err != nil { + return err + } + if err := createIfNotExists(dest, stat.IsDir()); err != nil { + return fmt.Errorf("Creating new bind-mount target, %s\n", err) + } + if err := system.Mount(m.Source, dest, "bind", uintptr(flags), ""); err != nil { return fmt.Errorf("mounting %s into %s %s", m.Source, dest, err) } From feb42d3f97f3a4de9317f17297c6277fcb0cc2af Mon Sep 17 00:00:00 2001 From: Jilles Oldenbeuving Date: Mon, 12 May 2014 20:35:18 +0200 Subject: [PATCH 056/400] Cleaned up Network settings overview --- docs/sources/reference/run.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index 0f72679ce..a0be7c34d 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -136,9 +136,8 @@ PID files): ## Network Settings - --dns=[] : Set custom dns servers for the container - --net="bridge": Set the Network mode for the container ('bridge': creates a new network stack for the container on the docker bridge, 'none': no networking for this container, 'container:': reuses another container network stack), 'host': use the host network stack inside the container - --net="bridge" Set the Network mode for the container + --dns=[] : Set custom dns servers for the container + --net="bridge" : Set the Network mode for the container 'bridge': creates a new network stack for the container on the docker bridge 'none': no networking for this container 'container:': reuses another container network stack From 3e636446c3a9de24b5b990988e0bd4890277df61 Mon Sep 17 00:00:00 2001 From: Jason Hall Date: Mon, 12 May 2014 14:48:03 -0400 Subject: [PATCH 057/400] Update google.md Fix numbering resets after code sample blocks --- docs/sources/installation/google.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/sources/installation/google.md b/docs/sources/installation/google.md index 4c22808dc..29ffe0d73 100644 --- a/docs/sources/installation/google.md +++ b/docs/sources/installation/google.md @@ -19,17 +19,16 @@ page_keywords: Docker, Docker documentation, installation, google, Google Comput https://developers.google.com/cloud/sdk/) to use your project with the following commands: - - + ``` $ curl https://dl.google.com/dl/cloudsdk/release/install_google_cloud_sdk.bash | bash $ gcloud auth login Enter a cloud project id (or leave blank to not set): + ``` 3. Start a new instance, select a zone close to you and the desired instance size: - - + ``` $ gcutil addinstance docker-playground --image=backports-debian-7 1: europe-west1-a ... @@ -39,25 +38,26 @@ page_keywords: Docker, Docker documentation, installation, google, Google Comput ... 12: machineTypes/g1-small >>> + ``` 4. Connect to the instance using SSH: - - + ``` $ gcutil ssh docker-playground $ docker-playground:~$ + ``` 5. Install the latest Docker release and configure it to start when the instance boots: - - + ``` $ docker-playground:~$ curl get.docker.io | bash $ docker-playground:~$ sudo update-rc.d docker defaults + ``` 6. Start a new container: - - + ``` $ docker-playground:~$ sudo docker run busybox echo 'docker on GCE \o/' $ docker on GCE \o/ + ``` From 79f234e049c72e0cac97bbd93815f33a919eaf1b Mon Sep 17 00:00:00 2001 From: Jilles Oldenbeuving Date: Mon, 12 May 2014 20:51:40 +0200 Subject: [PATCH 058/400] runmetrics update, linking to collectd plugin rather than just hinting --- docs/sources/articles/runmetrics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/articles/runmetrics.md b/docs/sources/articles/runmetrics.md index 50d46047c..15f53fb0d 100644 --- a/docs/sources/articles/runmetrics.md +++ b/docs/sources/articles/runmetrics.md @@ -310,8 +310,8 @@ layer; you will also have to add traffic going through the userland proxy. Then, you will need to check those counters on a regular basis. If you -happen to use `collectd`, there is a nice plugin to -automate iptables counters collection. +happen to use `collectd`, there is a [nice plugin](https://collectd.org/wiki/index.php/Plugin:IPTables) +to automate iptables counters collection. ### Interface-level counters From fee1bbd79ef8767ed149c1b1af4a39ad35e07772 Mon Sep 17 00:00:00 2001 From: Vishnu Kannan Date: Fri, 9 May 2014 19:01:29 +0000 Subject: [PATCH 059/400] Correct a comment in libcontainer Mount Namespace setup. Docker-DCO-1.1-Signed-off-by: Vishnu Kannan (github: vishh) --- pkg/libcontainer/mount/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/libcontainer/mount/init.go b/pkg/libcontainer/mount/init.go index cfe61d153..2d1f6516f 100644 --- a/pkg/libcontainer/mount/init.go +++ b/pkg/libcontainer/mount/init.go @@ -36,7 +36,7 @@ func InitializeMountNamespace(rootfs, console string, container *libcontainer.Co flag = syscall.MS_SLAVE } if err := system.Mount("", "/", "", uintptr(flag|syscall.MS_REC), ""); err != nil { - return fmt.Errorf("mounting / as slave %s", err) + return fmt.Errorf("mounting / with flags %X %s", (flag | syscall.MS_REC), err) } if err := system.Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mouting %s as bind %s", rootfs, err) From cc678a7078b417a330e8d4e3604b74f9e5d4cda4 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 12 May 2014 12:24:30 -0700 Subject: [PATCH 060/400] Remove newline char in error message Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/mount/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/libcontainer/mount/init.go b/pkg/libcontainer/mount/init.go index b0a3ef106..d01e9d5c9 100644 --- a/pkg/libcontainer/mount/init.go +++ b/pkg/libcontainer/mount/init.go @@ -128,7 +128,7 @@ func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error { return err } if err := createIfNotExists(dest, stat.IsDir()); err != nil { - return fmt.Errorf("Creating new bind-mount target, %s\n", err) + return fmt.Errorf("Creating new bind-mount target, %s", err) } if err := system.Mount(m.Source, dest, "bind", uintptr(flags), ""); err != nil { From 905795ece624675abe2ec2622b0bbafdb9d7f44c Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 7 May 2014 15:08:52 +0200 Subject: [PATCH 061/400] Always mount a /run tmpfs in the container All modern distros set up /run to be a tmpfs, see for instance: https://wiki.debian.org/ReleaseGoals/RunDirectory Its a very useful place to store pid-files, sockets and other things that only live at runtime and that should not be stored in the image. This is also useful when running systemd inside a container, as it will try to mount /run if not already mounted, which will fail for non-privileged container. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- daemon/execdriver/lxc/lxc_template.go | 1 + graph/graph.go | 1 + pkg/libcontainer/mount/init.go | 1 + 3 files changed, 3 insertions(+) diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index 7fdc5ce92..d40b31676 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -90,6 +90,7 @@ lxc.pivotdir = lxc_putold # We cannot mount them directly read-only, because that would prevent loading AppArmor profiles. lxc.mount.entry = proc {{escapeFstabSpaces $ROOTFS}}/proc proc nosuid,nodev,noexec 0 0 lxc.mount.entry = sysfs {{escapeFstabSpaces $ROOTFS}}/sys sysfs nosuid,nodev,noexec 0 0 +lxc.mount.entry = tmpfs {{escapeFstabSpaces $ROOTFS}}/run tmpfs nosuid,nodev,noexec 0 0 {{if .Tty}} lxc.mount.entry = {{.Console}} {{escapeFstabSpaces $ROOTFS}}/dev/console none bind,rw 0 0 diff --git a/graph/graph.go b/graph/graph.go index b88913912..5de9cbe7a 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -254,6 +254,7 @@ func SetupInitLayer(initLayer string) error { "/dev/pts": "dir", "/dev/shm": "dir", "/proc": "dir", + "/run": "dir", "/sys": "dir", "/.dockerinit": "file", "/.dockerenv": "file", diff --git a/pkg/libcontainer/mount/init.go b/pkg/libcontainer/mount/init.go index 4d11cc819..12f833a96 100644 --- a/pkg/libcontainer/mount/init.go +++ b/pkg/libcontainer/mount/init.go @@ -156,6 +156,7 @@ func newSystemMounts(rootfs, mountLabel string, mounts libcontainer.Mounts) []mo {source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags}, {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}, + {source: "tmpfs", path: filepath.Join(rootfs, "run"), device: "tmpfs", flags: defaultMountFlags}, } if len(mounts.OfType("devtmpfs")) == 1 { From 1d15c0b491afc6b32206d62ee3f50448b89b8d49 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 12 May 2014 20:51:45 +0000 Subject: [PATCH 062/400] add vieux as server's MAINTAINERS Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- server/MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/server/MAINTAINERS b/server/MAINTAINERS index aee10c842..3564d3db4 100644 --- a/server/MAINTAINERS +++ b/server/MAINTAINERS @@ -1 +1,2 @@ Solomon Hykes (@shykes) +Victor Vieux (@vieux) \ No newline at end of file From 7894a70f8b2dcb329178978066d825dc41ec6239 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 12 May 2014 21:38:20 +0000 Subject: [PATCH 063/400] move version out of server Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- builtins/builtins.go | 30 ++++++++++++++++++++++++++++-- server/server.go | 19 ------------------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/builtins/builtins.go b/builtins/builtins.go index 40d421f15..572e16252 100644 --- a/builtins/builtins.go +++ b/builtins/builtins.go @@ -1,11 +1,16 @@ package builtins import ( - api "github.com/dotcloud/docker/api/server" + "runtime" + + "github.com/dotcloud/docker/api" + apiserver "github.com/dotcloud/docker/api/server" "github.com/dotcloud/docker/daemon/networkdriver/bridge" + "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/server" + "github.com/dotcloud/docker/utils" ) func Register(eng *engine.Engine) error { @@ -15,12 +20,15 @@ func Register(eng *engine.Engine) error { if err := remote(eng); err != nil { return err } + if err := eng.Register("version", dockerVersion); err != nil { + return err + } return registry.NewService().Install(eng) } // remote: a RESTful api for cross-docker communication func remote(eng *engine.Engine) error { - return eng.Register("serveapi", api.ServeApi) + return eng.Register("serveapi", apiserver.ServeApi) } // daemon: a default execution and storage backend for Docker on Linux, @@ -44,3 +52,21 @@ func daemon(eng *engine.Engine) error { } return eng.Register("init_networkdriver", bridge.InitDriver) } + +// builtins jobs independent of any subsystem +func dockerVersion(job *engine.Job) engine.Status { + v := &engine.Env{} + v.Set("Version", dockerversion.VERSION) + v.SetJson("ApiVersion", api.APIVERSION) + v.Set("GitCommit", dockerversion.GITCOMMIT) + v.Set("GoVersion", runtime.Version()) + v.Set("Os", runtime.GOOS) + v.Set("Arch", runtime.GOARCH) + if kernelVersion, err := utils.GetKernelVersion(); err == nil { + v.Set("KernelVersion", kernelVersion.String()) + } + if _, err := v.WriteTo(job.Stdout); err != nil { + return job.Error(err) + } + return engine.StatusOK +} diff --git a/server/server.go b/server/server.go index ac2bb0cad..7c25d0306 100644 --- a/server/server.go +++ b/server/server.go @@ -41,7 +41,6 @@ import ( "syscall" "time" - "github.com/dotcloud/docker/api" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/daemon" "github.com/dotcloud/docker/daemonconfig" @@ -128,7 +127,6 @@ func InitServer(job *engine.Job) engine.Status { "logs": srv.ContainerLogs, "changes": srv.ContainerChanges, "top": srv.ContainerTop, - "version": srv.DockerVersion, "load": srv.ImageLoad, "build": srv.Build, "pull": srv.ImagePull, @@ -807,23 +805,6 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status { return engine.StatusOK } -func (srv *Server) DockerVersion(job *engine.Job) engine.Status { - v := &engine.Env{} - v.Set("Version", dockerversion.VERSION) - v.SetJson("ApiVersion", api.APIVERSION) - v.Set("GitCommit", dockerversion.GITCOMMIT) - v.Set("GoVersion", runtime.Version()) - v.Set("Os", runtime.GOOS) - v.Set("Arch", runtime.GOARCH) - if kernelVersion, err := utils.GetKernelVersion(); err == nil { - v.Set("KernelVersion", kernelVersion.String()) - } - if _, err := v.WriteTo(job.Stdout); err != nil { - return job.Error(err) - } - return engine.StatusOK -} - func (srv *Server) ImageHistory(job *engine.Job) engine.Status { if n := len(job.Args); n != 1 { return job.Errorf("Usage: %s IMAGE", job.Name) From d54dec4d8b6f36fa9211e7a3379f7e949c40b0ce Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 12 May 2014 15:26:23 -0700 Subject: [PATCH 064/400] Fix port mapping in ps display for public and private Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- api/common.go | 7 ++++--- daemon/network_settings.go | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/api/common.go b/api/common.go index af4ced4f6..f4e31a970 100644 --- a/api/common.go +++ b/api/common.go @@ -2,11 +2,12 @@ package api import ( "fmt" + "mime" + "strings" + "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/pkg/version" "github.com/dotcloud/docker/utils" - "mime" - "strings" ) const ( @@ -30,7 +31,7 @@ func DisplayablePorts(ports *engine.Table) string { ports.Sort() for _, port := range ports.Data { if port.Get("IP") == "" { - result = append(result, fmt.Sprintf("%d/%s", port.GetInt("PublicPort"), port.Get("Type"))) + result = append(result, fmt.Sprintf("%d/%s", port.GetInt("PrivatePort"), port.Get("Type"))) } else { result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.Get("IP"), port.GetInt("PublicPort"), port.GetInt("PrivatePort"), port.Get("Type"))) } diff --git a/daemon/network_settings.go b/daemon/network_settings.go index 762270362..a5c750acf 100644 --- a/daemon/network_settings.go +++ b/daemon/network_settings.go @@ -23,7 +23,7 @@ func (settings *NetworkSettings) PortMappingAPI() *engine.Table { p, _ := nat.ParsePort(port.Port()) if len(bindings) == 0 { out := &engine.Env{} - out.SetInt("PublicPort", p) + out.SetInt("PrivatePort", p) out.Set("Type", port.Proto()) outs.Add(out) continue From d33b4655c4339dcbbf9f78948598e216ac3c06b4 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 12 May 2014 16:40:19 -0700 Subject: [PATCH 065/400] Move duration and size to units pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- api/client/commands.go | 15 ++-- .../execdriver/native/configuration/parse.go | 6 +- daemon/state.go | 7 +- pkg/units/MAINTAINERS | 2 + pkg/units/duration.go | 31 ++++++++ pkg/units/size.go | 56 ++++++++++++++ pkg/units/size_test.go | 54 ++++++++++++++ runconfig/parse.go | 3 +- utils/jsonmessage.go | 8 +- utils/utils.go | 74 ------------------- utils/utils_test.go | 49 ------------ 11 files changed, 165 insertions(+), 140 deletions(-) create mode 100644 pkg/units/MAINTAINERS create mode 100644 pkg/units/duration.go create mode 100644 pkg/units/size.go create mode 100644 pkg/units/size_test.go diff --git a/api/client/commands.go b/api/client/commands.go index 46fec3f28..f60b6cebd 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -28,6 +28,7 @@ import ( "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/pkg/signal" "github.com/dotcloud/docker/pkg/term" + "github.com/dotcloud/docker/pkg/units" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" @@ -884,14 +885,14 @@ func (cli *DockerCli) CmdHistory(args ...string) error { fmt.Fprintf(w, "%s\t", utils.TruncateID(outID)) } - fmt.Fprintf(w, "%s ago\t", utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0)))) + fmt.Fprintf(w, "%s ago\t", units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0)))) if *noTrunc { fmt.Fprintf(w, "%s\t", out.Get("CreatedBy")) } else { fmt.Fprintf(w, "%s\t", utils.Trunc(out.Get("CreatedBy"), 45)) } - fmt.Fprintf(w, "%s\n", utils.HumanSize(out.GetInt64("Size"))) + fmt.Fprintf(w, "%s\n", units.HumanSize(out.GetInt64("Size"))) } else { if *noTrunc { fmt.Fprintln(w, outID) @@ -1249,7 +1250,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { } if !*quiet { - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, outID, utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), utils.HumanSize(out.GetInt64("VirtualSize"))) + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(out.GetInt64("VirtualSize"))) } else { fmt.Fprintln(w, outID) } @@ -1323,7 +1324,7 @@ func (cli *DockerCli) printTreeNode(noTrunc bool, image *engine.Env, prefix stri imageID = utils.TruncateID(image.Get("Id")) } - fmt.Fprintf(cli.out, "%s%s Virtual Size: %s", prefix, imageID, utils.HumanSize(image.GetInt64("VirtualSize"))) + fmt.Fprintf(cli.out, "%s%s Virtual Size: %s", prefix, imageID, units.HumanSize(image.GetInt64("VirtualSize"))) if image.GetList("RepoTags")[0] != ":" { fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.GetList("RepoTags"), ", ")) } else { @@ -1408,12 +1409,12 @@ func (cli *DockerCli) CmdPs(args ...string) error { outCommand = utils.Trunc(outCommand, 20) } ports.ReadListFrom([]byte(out.Get("Ports"))) - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, out.Get("Image"), outCommand, utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), out.Get("Status"), api.DisplayablePorts(ports), strings.Join(outNames, ",")) + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, out.Get("Image"), outCommand, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), out.Get("Status"), api.DisplayablePorts(ports), strings.Join(outNames, ",")) if *size { if out.GetInt("SizeRootFs") > 0 { - fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.GetInt64("SizeRw")), utils.HumanSize(out.GetInt64("SizeRootFs"))) + fmt.Fprintf(w, "%s (virtual %s)\n", units.HumanSize(out.GetInt64("SizeRw")), units.HumanSize(out.GetInt64("SizeRootFs"))) } else { - fmt.Fprintf(w, "%s\n", utils.HumanSize(out.GetInt64("SizeRw"))) + fmt.Fprintf(w, "%s\n", units.HumanSize(out.GetInt64("SizeRw"))) } } else { fmt.Fprint(w, "\n") diff --git a/daemon/execdriver/native/configuration/parse.go b/daemon/execdriver/native/configuration/parse.go index 22fe4b0e6..3767f5a86 100644 --- a/daemon/execdriver/native/configuration/parse.go +++ b/daemon/execdriver/native/configuration/parse.go @@ -8,7 +8,7 @@ import ( "strings" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/pkg/units" ) type Action func(*libcontainer.Container, interface{}, string) error @@ -75,7 +75,7 @@ func memory(container *libcontainer.Container, context interface{}, value string return fmt.Errorf("cannot set cgroups when they are disabled") } - v, err := utils.RAMInBytes(value) + v, err := units.RAMInBytes(value) if err != nil { return err } @@ -88,7 +88,7 @@ func memoryReservation(container *libcontainer.Container, context interface{}, v return fmt.Errorf("cannot set cgroups when they are disabled") } - v, err := utils.RAMInBytes(value) + v, err := units.RAMInBytes(value) if err != nil { return err } diff --git a/daemon/state.go b/daemon/state.go index 562929c87..c0ed9516e 100644 --- a/daemon/state.go +++ b/daemon/state.go @@ -2,9 +2,10 @@ package daemon import ( "fmt" - "github.com/dotcloud/docker/utils" "sync" "time" + + "github.com/dotcloud/docker/pkg/units" ) type State struct { @@ -22,12 +23,12 @@ func (s *State) String() string { defer s.RUnlock() if s.Running { - return fmt.Sprintf("Up %s", utils.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) + return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) } if s.FinishedAt.IsZero() { return "" } - return fmt.Sprintf("Exited (%d) %s ago", s.ExitCode, utils.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) + return fmt.Sprintf("Exited (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) } func (s *State) IsRunning() bool { diff --git a/pkg/units/MAINTAINERS b/pkg/units/MAINTAINERS new file mode 100644 index 000000000..68a97d2fc --- /dev/null +++ b/pkg/units/MAINTAINERS @@ -0,0 +1,2 @@ +Michael Crosby (@crosbymichael) +Victor Vieux (@vieux) diff --git a/pkg/units/duration.go b/pkg/units/duration.go new file mode 100644 index 000000000..cd3312149 --- /dev/null +++ b/pkg/units/duration.go @@ -0,0 +1,31 @@ +package units + +import ( + "fmt" + "time" +) + +// HumanDuration returns a human-readable approximation of a duration +// (eg. "About a minute", "4 hours ago", etc.) +func HumanDuration(d time.Duration) string { + if seconds := int(d.Seconds()); seconds < 1 { + return "Less than a second" + } else if seconds < 60 { + return fmt.Sprintf("%d seconds", seconds) + } else if minutes := int(d.Minutes()); minutes == 1 { + return "About a minute" + } else if minutes < 60 { + return fmt.Sprintf("%d minutes", minutes) + } else if hours := int(d.Hours()); hours == 1 { + return "About an hour" + } else if hours < 48 { + return fmt.Sprintf("%d hours", hours) + } else if hours < 24*7*2 { + return fmt.Sprintf("%d days", hours/24) + } else if hours < 24*30*3 { + return fmt.Sprintf("%d weeks", hours/24/7) + } else if hours < 24*365*2 { + return fmt.Sprintf("%d months", hours/24/30) + } + return fmt.Sprintf("%f years", d.Hours()/24/365) +} diff --git a/pkg/units/size.go b/pkg/units/size.go new file mode 100644 index 000000000..99c880096 --- /dev/null +++ b/pkg/units/size.go @@ -0,0 +1,56 @@ +package units + +import ( + "fmt" + "regexp" + "strconv" + "strings" +) + +// HumanSize returns a human-readable approximation of a size +// using SI standard (eg. "44kB", "17MB") +func HumanSize(size int64) string { + i := 0 + var sizef float64 + sizef = float64(size) + units := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"} + for sizef >= 1000.0 { + sizef = sizef / 1000.0 + i++ + } + return fmt.Sprintf("%.4g %s", sizef, units[i]) +} + +// Parses a human-readable string representing an amount of RAM +// in bytes, kibibytes, mebibytes or gibibytes, and returns the +// number of bytes, or -1 if the string is unparseable. +// Units are case-insensitive, and the 'b' suffix is optional. +func RAMInBytes(size string) (bytes int64, err error) { + re, error := regexp.Compile("^(\\d+)([kKmMgG])?[bB]?$") + if error != nil { + return -1, error + } + + matches := re.FindStringSubmatch(size) + + if len(matches) != 3 { + return -1, fmt.Errorf("Invalid size: '%s'", size) + } + + memLimit, error := strconv.ParseInt(matches[1], 10, 0) + if error != nil { + return -1, error + } + + unit := strings.ToLower(matches[2]) + + if unit == "k" { + memLimit *= 1024 + } else if unit == "m" { + memLimit *= 1024 * 1024 + } else if unit == "g" { + memLimit *= 1024 * 1024 * 1024 + } + + return memLimit, nil +} diff --git a/pkg/units/size_test.go b/pkg/units/size_test.go new file mode 100644 index 000000000..958a4ca13 --- /dev/null +++ b/pkg/units/size_test.go @@ -0,0 +1,54 @@ +package units + +import ( + "strings" + "testing" +) + +func TestHumanSize(t *testing.T) { + + size := strings.Trim(HumanSize(1000), " \t") + expect := "1 kB" + if size != expect { + t.Errorf("1000 -> expected '%s', got '%s'", expect, size) + } + + size = strings.Trim(HumanSize(1024), " \t") + expect = "1.024 kB" + if size != expect { + t.Errorf("1024 -> expected '%s', got '%s'", expect, size) + } +} + +func TestRAMInBytes(t *testing.T) { + assertRAMInBytes(t, "32", false, 32) + assertRAMInBytes(t, "32b", false, 32) + assertRAMInBytes(t, "32B", false, 32) + assertRAMInBytes(t, "32k", false, 32*1024) + assertRAMInBytes(t, "32K", false, 32*1024) + assertRAMInBytes(t, "32kb", false, 32*1024) + assertRAMInBytes(t, "32Kb", false, 32*1024) + assertRAMInBytes(t, "32Mb", false, 32*1024*1024) + assertRAMInBytes(t, "32Gb", false, 32*1024*1024*1024) + + assertRAMInBytes(t, "", true, -1) + assertRAMInBytes(t, "hello", true, -1) + assertRAMInBytes(t, "-32", true, -1) + assertRAMInBytes(t, " 32 ", true, -1) + assertRAMInBytes(t, "32 mb", true, -1) + assertRAMInBytes(t, "32m b", true, -1) + assertRAMInBytes(t, "32bm", true, -1) +} + +func assertRAMInBytes(t *testing.T, size string, expectError bool, expectedBytes int64) { + actualBytes, err := RAMInBytes(size) + if (err != nil) && !expectError { + t.Errorf("Unexpected error parsing '%s': %s", size, err) + } + if (err == nil) && expectError { + t.Errorf("Expected to get an error parsing '%s', but got none (bytes=%d)", size, actualBytes) + } + if actualBytes != expectedBytes { + t.Errorf("Expected '%s' to parse as %d bytes, got %d", size, expectedBytes, actualBytes) + } +} diff --git a/runconfig/parse.go b/runconfig/parse.go index 5a588c034..d8e4127f0 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -10,6 +10,7 @@ import ( "github.com/dotcloud/docker/opts" flag "github.com/dotcloud/docker/pkg/mflag" "github.com/dotcloud/docker/pkg/sysinfo" + "github.com/dotcloud/docker/pkg/units" "github.com/dotcloud/docker/utils" ) @@ -119,7 +120,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf var flMemory int64 if *flMemoryString != "" { - parsedMemory, err := utils.RAMInBytes(*flMemoryString) + parsedMemory, err := units.RAMInBytes(*flMemoryString) if err != nil { return nil, nil, cmd, err } diff --git a/utils/jsonmessage.go b/utils/jsonmessage.go index 6be421be9..d6546e3ee 100644 --- a/utils/jsonmessage.go +++ b/utils/jsonmessage.go @@ -3,10 +3,12 @@ package utils import ( "encoding/json" "fmt" - "github.com/dotcloud/docker/pkg/term" "io" "strings" "time" + + "github.com/dotcloud/docker/pkg/term" + "github.com/dotcloud/docker/pkg/units" ) type JSONError struct { @@ -41,11 +43,11 @@ func (p *JSONProgress) String() string { if p.Current <= 0 && p.Total <= 0 { return "" } - current := HumanSize(int64(p.Current)) + current := units.HumanSize(int64(p.Current)) if p.Total <= 0 { return fmt.Sprintf("%8v", current) } - total := HumanSize(int64(p.Total)) + total := units.HumanSize(int64(p.Total)) percentage := int(float64(p.Current)/float64(p.Total)*100) / 2 if width > 110 { pbBox = fmt.Sprintf("[%s>%s] ", strings.Repeat("=", percentage), strings.Repeat(" ", 50-percentage)) diff --git a/utils/utils.go b/utils/utils.go index 4ef44b561..86c9b74a8 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -16,7 +16,6 @@ import ( "os" "os/exec" "path/filepath" - "regexp" "runtime" "strconv" "strings" @@ -83,79 +82,6 @@ func Errorf(format string, a ...interface{}) { logf("error", format, a...) } -// HumanDuration returns a human-readable approximation of a duration -// (eg. "About a minute", "4 hours ago", etc.) -func HumanDuration(d time.Duration) string { - if seconds := int(d.Seconds()); seconds < 1 { - return "Less than a second" - } else if seconds < 60 { - return fmt.Sprintf("%d seconds", seconds) - } else if minutes := int(d.Minutes()); minutes == 1 { - return "About a minute" - } else if minutes < 60 { - return fmt.Sprintf("%d minutes", minutes) - } else if hours := int(d.Hours()); hours == 1 { - return "About an hour" - } else if hours < 48 { - return fmt.Sprintf("%d hours", hours) - } else if hours < 24*7*2 { - return fmt.Sprintf("%d days", hours/24) - } else if hours < 24*30*3 { - return fmt.Sprintf("%d weeks", hours/24/7) - } else if hours < 24*365*2 { - return fmt.Sprintf("%d months", hours/24/30) - } - return fmt.Sprintf("%f years", d.Hours()/24/365) -} - -// HumanSize returns a human-readable approximation of a size -// using SI standard (eg. "44kB", "17MB") -func HumanSize(size int64) string { - i := 0 - var sizef float64 - sizef = float64(size) - units := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"} - for sizef >= 1000.0 { - sizef = sizef / 1000.0 - i++ - } - return fmt.Sprintf("%.4g %s", sizef, units[i]) -} - -// Parses a human-readable string representing an amount of RAM -// in bytes, kibibytes, mebibytes or gibibytes, and returns the -// number of bytes, or -1 if the string is unparseable. -// Units are case-insensitive, and the 'b' suffix is optional. -func RAMInBytes(size string) (bytes int64, err error) { - re, error := regexp.Compile("^(\\d+)([kKmMgG])?[bB]?$") - if error != nil { - return -1, error - } - - matches := re.FindStringSubmatch(size) - - if len(matches) != 3 { - return -1, fmt.Errorf("Invalid size: '%s'", size) - } - - memLimit, error := strconv.ParseInt(matches[1], 10, 0) - if error != nil { - return -1, error - } - - unit := strings.ToLower(matches[2]) - - if unit == "k" { - memLimit *= 1024 - } else if unit == "m" { - memLimit *= 1024 * 1024 - } else if unit == "g" { - memLimit *= 1024 * 1024 * 1024 - } - - return memLimit, nil -} - func Trunc(s string, maxlen int) string { if len(s) <= maxlen { return s diff --git a/utils/utils_test.go b/utils/utils_test.go index ccd212202..83164c68d 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -6,7 +6,6 @@ import ( "io" "io/ioutil" "os" - "strings" "testing" ) @@ -271,54 +270,6 @@ func TestCompareKernelVersion(t *testing.T) { -1) } -func TestHumanSize(t *testing.T) { - - size := strings.Trim(HumanSize(1000), " \t") - expect := "1 kB" - if size != expect { - t.Errorf("1000 -> expected '%s', got '%s'", expect, size) - } - - size = strings.Trim(HumanSize(1024), " \t") - expect = "1.024 kB" - if size != expect { - t.Errorf("1024 -> expected '%s', got '%s'", expect, size) - } -} - -func TestRAMInBytes(t *testing.T) { - assertRAMInBytes(t, "32", false, 32) - assertRAMInBytes(t, "32b", false, 32) - assertRAMInBytes(t, "32B", false, 32) - assertRAMInBytes(t, "32k", false, 32*1024) - assertRAMInBytes(t, "32K", false, 32*1024) - assertRAMInBytes(t, "32kb", false, 32*1024) - assertRAMInBytes(t, "32Kb", false, 32*1024) - assertRAMInBytes(t, "32Mb", false, 32*1024*1024) - assertRAMInBytes(t, "32Gb", false, 32*1024*1024*1024) - - assertRAMInBytes(t, "", true, -1) - assertRAMInBytes(t, "hello", true, -1) - assertRAMInBytes(t, "-32", true, -1) - assertRAMInBytes(t, " 32 ", true, -1) - assertRAMInBytes(t, "32 mb", true, -1) - assertRAMInBytes(t, "32m b", true, -1) - assertRAMInBytes(t, "32bm", true, -1) -} - -func assertRAMInBytes(t *testing.T, size string, expectError bool, expectedBytes int64) { - actualBytes, err := RAMInBytes(size) - if (err != nil) && !expectError { - t.Errorf("Unexpected error parsing '%s': %s", size, err) - } - if (err == nil) && expectError { - t.Errorf("Expected to get an error parsing '%s', but got none (bytes=%d)", size, actualBytes) - } - if actualBytes != expectedBytes { - t.Errorf("Expected '%s' to parse as %d bytes, got %d", size, expectedBytes, actualBytes) - } -} - func TestParseHost(t *testing.T) { var ( defaultHttpHost = "127.0.0.1" From b9c2d57d890c0f6a798baf74f7b56793fdf1fd61 Mon Sep 17 00:00:00 2001 From: SvenDowideit Date: Tue, 13 May 2014 10:36:19 +1000 Subject: [PATCH 066/400] Was checking something for a PR and noticed some quote issues Docker-DCO-1.1-Signed-off-by: SvenDowideit (github: SvenDowideit) --- docs/sources/use/workingwithrepository.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/use/workingwithrepository.md b/docs/sources/use/workingwithrepository.md index 07f130a90..d92d057d5 100644 --- a/docs/sources/use/workingwithrepository.md +++ b/docs/sources/use/workingwithrepository.md @@ -25,8 +25,8 @@ project provides [Docker.io](http://index.docker.io) to host public and user. We provide user authentication and search over all the public repositories. -Docker acts as a client for these services via the `docker search, pull, -login` and `push` commands. +Docker acts as a client for these services via the `docker search`, `pull`, +`login` and `push` commands. ## Repositories @@ -176,7 +176,7 @@ If you want to see the status of your Trusted Builds you can go to your index, and it will show you the status of your builds, and the build history. -Once you`ve created a Trusted Build you can deactivate or delete it. You +Once you've created a Trusted Build you can deactivate or delete it. You cannot however push to a Trusted Build with the `docker push` command. You can only manage it by committing code to your GitHub repository. From 100a92146e563d39d5a401c11d48e7b4ef7fad6e Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 13 May 2014 00:54:46 +0000 Subject: [PATCH 067/400] move RegisterLinks to daemon Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- daemon/daemon.go | 29 +++++++++++++++++++++++++++++ server/server.go | 33 +-------------------------------- 2 files changed, 30 insertions(+), 32 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index 7901f8ec6..38a107bba 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -667,6 +667,35 @@ func (daemon *Daemon) RegisterLink(parent, child *Container, alias string) error return nil } +func (daemon *Daemon) RegisterLinks(container *Container, hostConfig *runconfig.HostConfig) error { + if hostConfig != nil && hostConfig.Links != nil { + for _, l := range hostConfig.Links { + parts, err := utils.PartParser("name:alias", l) + if err != nil { + return err + } + child, err := daemon.GetByName(parts["name"]) + if err != nil { + return err + } + if child == nil { + return fmt.Errorf("Could not get container for %s", parts["name"]) + } + if err := daemon.RegisterLink(container, child, parts["alias"]); err != nil { + return err + } + } + + // After we load all the links into the daemon + // set them to nil on the hostconfig + hostConfig.Links = nil + if err := container.WriteHostConfig(); err != nil { + return err + } + } + return nil +} + // FIXME: harmonize with NewGraph() func NewDaemon(config *daemonconfig.Config, eng *engine.Engine) (*Daemon, error) { daemon, err := NewDaemonFromDirectory(config, eng) diff --git a/server/server.go b/server/server.go index 7c25d0306..f8e7539e7 100644 --- a/server/server.go +++ b/server/server.go @@ -2029,37 +2029,6 @@ func (srv *Server) ImageGetCached(imgID string, config *runconfig.Config) (*imag return match, nil } -func (srv *Server) RegisterLinks(container *daemon.Container, hostConfig *runconfig.HostConfig) error { - daemon := srv.daemon - - if hostConfig != nil && hostConfig.Links != nil { - for _, l := range hostConfig.Links { - parts, err := utils.PartParser("name:alias", l) - if err != nil { - return err - } - child, err := srv.daemon.GetByName(parts["name"]) - if err != nil { - return err - } - if child == nil { - return fmt.Errorf("Could not get container for %s", parts["name"]) - } - if err := daemon.RegisterLink(container, child, parts["alias"]); err != nil { - return err - } - } - - // After we load all the links into the daemon - // set them to nil on the hostconfig - hostConfig.Links = nil - if err := container.WriteHostConfig(); err != nil { - return err - } - } - return nil -} - func (srv *Server) ContainerStart(job *engine.Job) engine.Status { if len(job.Args) < 1 { return job.Errorf("Usage: %s container_id", job.Name) @@ -2100,7 +2069,7 @@ func (srv *Server) ContainerStart(job *engine.Job) engine.Status { } } // Register any links from the host config before starting the container - if err := srv.RegisterLinks(container, hostConfig); err != nil { + if err := srv.daemon.RegisterLinks(container, hostConfig); err != nil { return job.Error(err) } container.SetHostConfig(hostConfig) From f3736265fdddef9e9a9ab588906eeb7abe8a0cf6 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 1 May 2014 23:07:11 +0000 Subject: [PATCH 068/400] make listen buffer optional Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api/server/server.go | 7 ++++++- docker/docker.go | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/api/server/server.go b/api/server/server.go index 3c93a3478..ed1d43406 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1193,6 +1193,7 @@ func changeGroup(addr string, nameOrGid string) error { // ListenAndServe sets up the required http.Server and gets it listening for // each addr passed in and does protocol specific checking. func ListenAndServe(proto, addr string, job *engine.Job) error { + var l net.Listener r, err := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version")) if err != nil { return err @@ -1208,7 +1209,11 @@ func ListenAndServe(proto, addr string, job *engine.Job) error { } } - l, err := listenbuffer.NewListenBuffer(proto, addr, activationLock) + if job.GetenvBool("BufferRequests") { + l, err = listenbuffer.NewListenBuffer(proto, addr, activationLock) + } else { + l, err = net.Listen(proto, addr) + } if err != nil { return err } diff --git a/docker/docker.go b/docker/docker.go index 60f34a1f1..1683d7a0e 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -185,6 +185,7 @@ func main() { job.Setenv("TlsCa", *flCa) job.Setenv("TlsCert", *flCert) job.Setenv("TlsKey", *flKey) + job.SetenvBool("BuffferRequests", true) if err := job.Run(); err != nil { log.Fatal(err) } From 937f8f2d813f58480e4bb2c0060353be31b04e10 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Sat, 3 May 2014 00:11:20 +0000 Subject: [PATCH 069/400] move acceptconnections as builtin Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api/server/server.go | 8 +++----- builtins/builtins.go | 5 ++++- docker/docker.go | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index ed1d43406..8fe518f7c 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1285,10 +1285,6 @@ func ServeApi(job *engine.Job) engine.Status { ) activationLock = make(chan struct{}) - if err := job.Eng.Register("acceptconnections", AcceptConnections); err != nil { - return job.Error(err) - } - for _, protoAddr := range protoAddrs { protoAddrParts := strings.SplitN(protoAddr, "://", 2) if len(protoAddrParts) != 2 { @@ -1315,7 +1311,9 @@ func AcceptConnections(job *engine.Job) engine.Status { go systemd.SdNotify("READY=1") // close the lock so the listeners start accepting connections - close(activationLock) + if activationLock != nil { + close(activationLock) + } return engine.StatusOK } diff --git a/builtins/builtins.go b/builtins/builtins.go index 572e16252..3e0041c9d 100644 --- a/builtins/builtins.go +++ b/builtins/builtins.go @@ -28,7 +28,10 @@ func Register(eng *engine.Engine) error { // remote: a RESTful api for cross-docker communication func remote(eng *engine.Engine) error { - return eng.Register("serveapi", apiserver.ServeApi) + if err := eng.Register("serveapi", apiserver.ServeApi); err != nil { + return err + } + return eng.Register("acceptconnections", apiserver.AcceptConnections) } // daemon: a default execution and storage backend for Docker on Linux, diff --git a/docker/docker.go b/docker/docker.go index 1683d7a0e..ab485d0b6 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -185,7 +185,7 @@ func main() { job.Setenv("TlsCa", *flCa) job.Setenv("TlsCert", *flCert) job.Setenv("TlsKey", *flKey) - job.SetenvBool("BuffferRequests", true) + job.SetenvBool("BufferRequests", true) if err := job.Run(); err != nil { log.Fatal(err) } From fe445a2447e701cc91ef77d7bcb0978a5d373940 Mon Sep 17 00:00:00 2001 From: SvenDowideit Date: Tue, 13 May 2014 12:50:31 +1000 Subject: [PATCH 070/400] tell the user not to run from OSX Docker-DCO-1.1-Signed-off-by: SvenDowideit (github: SvenDowideit) --- docker/docker.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker/docker.go b/docker/docker.go index 60f34a1f1..275a96c73 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -98,6 +98,9 @@ func main() { } if *flDaemon { + if runtime.GOOS != "linux" { + log.Fatalf("The Docker daemon is only supported on linux") + } if os.Geteuid() != 0 { log.Fatalf("The Docker daemon needs to be run as root") } From 653328c6cef8bab89343587b134ba7676ee39867 Mon Sep 17 00:00:00 2001 From: Dan Keder Date: Tue, 13 May 2014 11:17:42 +0200 Subject: [PATCH 071/400] Fix a typo in contrib/man/md/docker.1.md --- contrib/man/md/docker.1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/man/md/docker.1.md b/contrib/man/md/docker.1.md index d1ddf192b..0071a71c9 100644 --- a/contrib/man/md/docker.1.md +++ b/contrib/man/md/docker.1.md @@ -73,7 +73,7 @@ port=[4243] or path =[/var/run/docker.sock] is omitted, default values are used. **-v**=*true*|*false* Print version information and quit. Default is false. -**--selinux-enabled=*true*|*false* +**--selinux-enabled**=*true*|*false* Enable selinux support. Default is false. # COMMANDS From b7c7b851dce28bb679e0289168da382d7cdad74b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 13 May 2014 15:42:21 +0200 Subject: [PATCH 072/400] libcontainer: Ensure bind mount target files are inside rootfs Before we create any files to bind-mount on, make sure they are inside the container rootfs, handling for instance absolute symbolic links inside the container. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- pkg/libcontainer/mount/init.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/libcontainer/mount/init.go b/pkg/libcontainer/mount/init.go index 12f833a96..16fb758e5 100644 --- a/pkg/libcontainer/mount/init.go +++ b/pkg/libcontainer/mount/init.go @@ -12,6 +12,7 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/mount/nodes" "github.com/dotcloud/docker/pkg/system" + "github.com/dotcloud/docker/utils" ) // default mount point flags @@ -127,6 +128,12 @@ func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error { if err != nil { return err } + + dest, err = utils.FollowSymlinkInScope(dest, rootfs) + if err != nil { + return err + } + if err := createIfNotExists(dest, stat.IsDir()); err != nil { return fmt.Errorf("Creating new bind-mount target, %s", err) } From dcf81f95fdfe3ac8e97602d2ef2fef03288c15b1 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 13 May 2014 10:34:30 -0700 Subject: [PATCH 073/400] Move Follow symlink to pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- {utils => pkg/symlink}/fs.go | 33 +------------------------- {utils => pkg/symlink}/fs_test.go | 2 +- {utils => pkg/symlink}/testdata/fs/a/d | 0 {utils => pkg/symlink}/testdata/fs/a/e | 0 {utils => pkg/symlink}/testdata/fs/a/f | 0 {utils => pkg/symlink}/testdata/fs/b/h | 0 {utils => pkg/symlink}/testdata/fs/g | 0 utils/utils.go | 31 ++++++++++++++++++++++++ 8 files changed, 33 insertions(+), 33 deletions(-) rename {utils => pkg/symlink}/fs.go (64%) rename {utils => pkg/symlink}/fs_test.go (99%) rename {utils => pkg/symlink}/testdata/fs/a/d (100%) rename {utils => pkg/symlink}/testdata/fs/a/e (100%) rename {utils => pkg/symlink}/testdata/fs/a/f (100%) rename {utils => pkg/symlink}/testdata/fs/b/h (100%) rename {utils => pkg/symlink}/testdata/fs/g (100%) diff --git a/utils/fs.go b/pkg/symlink/fs.go similarity index 64% rename from utils/fs.go rename to pkg/symlink/fs.go index e07ced75d..e91d33db4 100644 --- a/utils/fs.go +++ b/pkg/symlink/fs.go @@ -1,43 +1,12 @@ -package utils +package symlink import ( "fmt" "os" "path/filepath" "strings" - "syscall" ) -// TreeSize walks a directory tree and returns its total size in bytes. -func TreeSize(dir string) (size int64, err error) { - data := make(map[uint64]struct{}) - err = filepath.Walk(dir, func(d string, fileInfo os.FileInfo, e error) error { - // Ignore directory sizes - if fileInfo == nil { - return nil - } - - s := fileInfo.Size() - if fileInfo.IsDir() || s == 0 { - return nil - } - - // Check inode to handle hard links correctly - inode := fileInfo.Sys().(*syscall.Stat_t).Ino - // inode is not a uint64 on all platforms. Cast it to avoid issues. - if _, exists := data[uint64(inode)]; exists { - return nil - } - // inode is not a uint64 on all platforms. Cast it to avoid issues. - data[uint64(inode)] = struct{}{} - - size += s - - return nil - }) - return -} - // FollowSymlink will follow an existing link and scope it to the root // path provided. func FollowSymlinkInScope(link, root string) (string, error) { diff --git a/utils/fs_test.go b/pkg/symlink/fs_test.go similarity index 99% rename from utils/fs_test.go rename to pkg/symlink/fs_test.go index 9affc00e9..1f12aa3a6 100644 --- a/utils/fs_test.go +++ b/pkg/symlink/fs_test.go @@ -1,4 +1,4 @@ -package utils +package symlink import ( "io/ioutil" diff --git a/utils/testdata/fs/a/d b/pkg/symlink/testdata/fs/a/d similarity index 100% rename from utils/testdata/fs/a/d rename to pkg/symlink/testdata/fs/a/d diff --git a/utils/testdata/fs/a/e b/pkg/symlink/testdata/fs/a/e similarity index 100% rename from utils/testdata/fs/a/e rename to pkg/symlink/testdata/fs/a/e diff --git a/utils/testdata/fs/a/f b/pkg/symlink/testdata/fs/a/f similarity index 100% rename from utils/testdata/fs/a/f rename to pkg/symlink/testdata/fs/a/f diff --git a/utils/testdata/fs/b/h b/pkg/symlink/testdata/fs/b/h similarity index 100% rename from utils/testdata/fs/b/h rename to pkg/symlink/testdata/fs/b/h diff --git a/utils/testdata/fs/g b/pkg/symlink/testdata/fs/g similarity index 100% rename from utils/testdata/fs/g rename to pkg/symlink/testdata/fs/g diff --git a/utils/utils.go b/utils/utils.go index 4ef44b561..7ffcc06b9 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -21,6 +21,7 @@ import ( "strconv" "strings" "sync" + "syscall" "time" "github.com/dotcloud/docker/dockerversion" @@ -1091,3 +1092,33 @@ func ParseKeyValueOpt(opt string) (string, string, error) { } return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil } + +// TreeSize walks a directory tree and returns its total size in bytes. +func TreeSize(dir string) (size int64, err error) { + data := make(map[uint64]struct{}) + err = filepath.Walk(dir, func(d string, fileInfo os.FileInfo, e error) error { + // Ignore directory sizes + if fileInfo == nil { + return nil + } + + s := fileInfo.Size() + if fileInfo.IsDir() || s == 0 { + return nil + } + + // Check inode to handle hard links correctly + inode := fileInfo.Sys().(*syscall.Stat_t).Ino + // inode is not a uint64 on all platforms. Cast it to avoid issues. + if _, exists := data[uint64(inode)]; exists { + return nil + } + // inode is not a uint64 on all platforms. Cast it to avoid issues. + data[uint64(inode)] = struct{}{} + + size += s + + return nil + }) + return +} From ca040b1a377c467a9504ffa256ae77d9e3d29f0c Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 13 May 2014 10:54:08 -0700 Subject: [PATCH 074/400] Update code to handle new path to Follow Symlink func Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/volumes.go | 4 ++-- pkg/libcontainer/mount/init.go | 4 ++-- server/buildfile.go | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/daemon/volumes.go b/daemon/volumes.go index a15e3084b..ac01c6a98 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -10,7 +10,7 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/daemon/execdriver" - "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/pkg/symlink" ) type BindMap struct { @@ -213,7 +213,7 @@ func createVolumes(container *Container) error { } // Create the mountpoint - rootVolPath, err := utils.FollowSymlinkInScope(filepath.Join(container.basefs, volPath), container.basefs) + rootVolPath, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, volPath), container.basefs) if err != nil { return err } diff --git a/pkg/libcontainer/mount/init.go b/pkg/libcontainer/mount/init.go index 16fb758e5..e5f2b43ca 100644 --- a/pkg/libcontainer/mount/init.go +++ b/pkg/libcontainer/mount/init.go @@ -11,8 +11,8 @@ import ( "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/mount/nodes" + "github.com/dotcloud/docker/pkg/symlink" "github.com/dotcloud/docker/pkg/system" - "github.com/dotcloud/docker/utils" ) // default mount point flags @@ -129,7 +129,7 @@ func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error { return err } - dest, err = utils.FollowSymlinkInScope(dest, rootfs) + dest, err = symlink.FollowSymlinkInScope(dest, rootfs) if err != nil { return err } diff --git a/server/buildfile.go b/server/buildfile.go index eeafbb5b4..faaac0d3d 100644 --- a/server/buildfile.go +++ b/server/buildfile.go @@ -20,6 +20,7 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/daemon" "github.com/dotcloud/docker/nat" + "github.com/dotcloud/docker/pkg/symlink" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" @@ -404,7 +405,7 @@ func (b *buildFile) addContext(container *daemon.Container, orig, dest string, r ) if destPath != container.RootfsPath() { - destPath, err = utils.FollowSymlinkInScope(destPath, container.RootfsPath()) + destPath, err = symlink.FollowSymlinkInScope(destPath, container.RootfsPath()) if err != nil { return err } From ea7647099fcabd73077a403d461e9a0778dda12f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 13 May 2014 11:27:24 -0700 Subject: [PATCH 075/400] Add MAINTAINERS file to symlink pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/symlink/MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 pkg/symlink/MAINTAINERS diff --git a/pkg/symlink/MAINTAINERS b/pkg/symlink/MAINTAINERS new file mode 100644 index 000000000..68a97d2fc --- /dev/null +++ b/pkg/symlink/MAINTAINERS @@ -0,0 +1,2 @@ +Michael Crosby (@crosbymichael) +Victor Vieux (@vieux) From 461f801f832b1ecbe527999e05dc0bede6ca6c71 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 13 May 2014 14:55:46 -0400 Subject: [PATCH 076/400] tarsum: start a test for TarSum Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- utils/tarsum_test.go | 59 ++++++++++++++++++ .../json | 1 + .../layer.tar | Bin 0 -> 9216 bytes 3 files changed, 60 insertions(+) create mode 100644 utils/tarsum_test.go create mode 100644 utils/testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/json create mode 100644 utils/testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar diff --git a/utils/tarsum_test.go b/utils/tarsum_test.go new file mode 100644 index 000000000..82aea57c6 --- /dev/null +++ b/utils/tarsum_test.go @@ -0,0 +1,59 @@ +package utils + +import ( + "io" + "io/ioutil" + "os" + "testing" +) + +type testLayer struct { + filename string + jsonfile string + gzip bool + tarsum string +} + +var testLayers = []testLayer{ + testLayer{ + filename: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar", + jsonfile: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/json", + tarsum: "tarsum+sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b"}, +} + +func TestTarSums(t *testing.T) { + for _, layer := range testLayers { + fh, err := os.Open(layer.filename) + if err != nil { + t.Errorf("failed to open %s: %s", layer.filename, err) + continue + } + ts := &TarSum{Reader: fh} + _, err = io.Copy(ioutil.Discard, ts) + if err != nil { + t.Errorf("failed to copy from %s: %s", layer.filename, err) + continue + } + var gotSum string + if len(layer.jsonfile) > 0 { + jfh, err := os.Open(layer.jsonfile) + if err != nil { + t.Errorf("failed to open %s: %s", layer.jsonfile, err) + continue + } + buf, err := ioutil.ReadAll(jfh) + if err != nil { + t.Errorf("failed to readAll %s: %s", layer.jsonfile, err) + continue + } + gotSum = ts.Sum(buf) + } else { + gotSum = ts.Sum(nil) + } + + if layer.tarsum != gotSum { + t.Errorf("expecting [%s], but got [%s]", layer.tarsum, gotSum) + } + + } +} diff --git a/utils/testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/json b/utils/testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/json new file mode 100644 index 000000000..0f0ba4974 --- /dev/null +++ b/utils/testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/json @@ -0,0 +1 @@ +{"id":"46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457","parent":"def3f9165934325dfd027c86530b2ea49bb57a0963eb1336b3a0415ff6fd56de","created":"2014-04-07T02:45:52.610504484Z","container":"e0f07f8d72cae171a3dcc35859960e7e956e0628bce6fedc4122bf55b2c287c7","container_config":{"Hostname":"88807319f25e","Domainname":"","User":"","Memory":0,"MemorySwap":0,"CpuShares":0,"AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"PortSpecs":null,"ExposedPorts":null,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["HOME=/","PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","sed -ri 's/^(%wheel.*)(ALL)$/\\1NOPASSWD: \\2/' /etc/sudoers"],"Image":"def3f9165934325dfd027c86530b2ea49bb57a0963eb1336b3a0415ff6fd56de","Volumes":null,"WorkingDir":"","Entrypoint":null,"NetworkDisabled":false,"OnBuild":[]},"docker_version":"0.9.1-dev","config":{"Hostname":"88807319f25e","Domainname":"","User":"","Memory":0,"MemorySwap":0,"CpuShares":0,"AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"PortSpecs":null,"ExposedPorts":null,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["HOME=/","PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":null,"Image":"def3f9165934325dfd027c86530b2ea49bb57a0963eb1336b3a0415ff6fd56de","Volumes":null,"WorkingDir":"","Entrypoint":null,"NetworkDisabled":false,"OnBuild":[]},"architecture":"amd64","os":"linux","Size":3425} \ No newline at end of file diff --git a/utils/testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar b/utils/testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar new file mode 100644 index 0000000000000000000000000000000000000000..dfd5c204aea77673f13fdd2f81cb4af1c155c00c GIT binary patch literal 9216 zcmeHMYfsx)8s=;H6|bl&iYAZ?p-5<1$(y*vYHk~c?XX{vu}|fzRr1|&zyvK1! zQq)nWWVPA}63Myvy*}^F5Qtg*V8=g=M!Ru&adFTnf40B*^q|=~Z#CM@#>M%EgGRH_ zXtfULV#j(J_Jz`34wZgZ*0ym!%kRHL9{_(p&BZRoHJYu)<>loz?$!PU{9Bjp<^i?p zS)Tg!r=9Az$G@(0Ao6^75%A;qpMSV)ukcqQn%1X5y|oh!_xLmZX`y%GUBmQG;D6af z{a@yPg@1D=8t(B&ZtcXgE2ck=f9pf*x&ANlU$J}L#UB59rsJ=#>(otde**vZ1?PXJ z)y|dMh8z!Kfh=;zN!B|J)*y8)L$Hbq5c2K_rK=l{{8R8czxwV#$Odd zDsuJ8oS)h8`+U3IsNVOszdy8F?XCC!X1jHMK)Xr!XT8koFP{Hz-;!IxPhJ$Ib48h# zYv~t}ms6n-7Nk?ki-cxgF4IDhpT@D51d2R$2x=V)%F|Svhif#KI>gHaB|@O7JU(A% zo>KEP56(cuboN&-&LROexgfmf&txD1^0c9NNVQI5N~dNwm64!nnnQFH317=JF`{vu zi^$WUtCWHQq4Y!Yy@W{oRoV29sUd<=@!~sJ;!ok8>_qYfz|Ch12+9P6$8i`#qvqS zhsLT-8QL!zwhRx(aXaYF&PwD5LLOm%T#Ds>) z{YV0A>qPL*aFLnz9*nfyl@!I3_Ss=Y=MKNEA zG8|$lPj#9`#(W1sgCgK@f)P?2A)0uPB8Gf6TLITOAl@|29e$jAvBox=W-QCrr59N% zKg$7Xy=69F7QR_X7D_-i2hs*J)6%&RIBr9LDPPP_-? z-X`DPuwzY(j+Gk=rWL_Msfvvp-prW$3W(MwPPgEZO^EI!{*XIAuLp zlpj9k85vO{{2kR4hD{4c;~{+QmhNVfq;xeepJc>QQ@QJfEkdQVBbPJuiA~nsv9l~O zrN&UpxC9i`6;rQ>v?7%WUrr@(gXOs4JE=IN=}4(?RS=2GEd9-ogTEiuP>Fqyb6;vM ziV-Q;Z|ZT?Vz^rPk?`^}6a`cC_=9V1=*>jc&y0jq{h|=m&BK+Jpv}ea1?sKVi^Gj` zk<9K*;4?gK^?Jl6-g0L4kQcX>OZUHi{>Odi#u~f!gnqSdCpW{f zGr2q31WO6O$i;nz9#NH-D^8Rv6Xcv%XFkhmyBsZ;8k2ftd;fPtN1v+`G zPRv~5E)wm1y}~(Py9GwK;`;9K2C_2#(Rc=qFBTa z>?ZUNHvSmq9G9)M%0u+CW!J=jv1~Clz-avUIImk%<&=a9uI;2EY~~stiCKTsh|Oow<5; z$eY1%WV!B_?iFikc)C2TV46YQucl=WfmM#jY|_4sK>Njf)j#u#Y{x@V_A!c2 Date: Tue, 13 May 2014 11:59:27 -0700 Subject: [PATCH 077/400] Remove the bind mount for dev/console which override the mknod/label Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- pkg/libcontainer/console/console.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/libcontainer/console/console.go b/pkg/libcontainer/console/console.go index 5f06aea22..62edbfde8 100644 --- a/pkg/libcontainer/console/console.go +++ b/pkg/libcontainer/console/console.go @@ -40,9 +40,6 @@ func Setup(rootfs, consolePath, mountLabel string) error { if err := label.SetFileLabel(consolePath, mountLabel); err != nil { return fmt.Errorf("set file label %s %s", dest, err) } - if err := system.Mount(consolePath, dest, "bind", syscall.MS_BIND, ""); err != nil { - return fmt.Errorf("bind %s to %s %s", consolePath, dest, err) - } return nil } From efa369a6ee89722ace2e85aad91c69ddf37985c5 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 13 May 2014 15:08:48 -0400 Subject: [PATCH 078/400] tarsum: adding the layer for "scratch" image Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- utils/tarsum_test.go | 4 ++++ .../json | 1 + .../layer.tar | Bin 0 -> 1536 bytes 3 files changed, 5 insertions(+) create mode 100644 utils/testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/json create mode 100644 utils/testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/layer.tar diff --git a/utils/tarsum_test.go b/utils/tarsum_test.go index 82aea57c6..77cacfb73 100644 --- a/utils/tarsum_test.go +++ b/utils/tarsum_test.go @@ -19,6 +19,10 @@ var testLayers = []testLayer{ filename: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar", jsonfile: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/json", tarsum: "tarsum+sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b"}, + testLayer{ + filename: "testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/layer.tar", + jsonfile: "testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/json", + tarsum: "tarsum+sha256:ac672ee85da9ab7f9667ae3c32841d3e42f33cc52c273c23341dabba1c8b0c8b"}, } func TestTarSums(t *testing.T) { diff --git a/utils/testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/json b/utils/testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/json new file mode 100644 index 000000000..12c18a076 --- /dev/null +++ b/utils/testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/json @@ -0,0 +1 @@ +{"id":"511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158","comment":"Imported from -","created":"2013-06-13T14:03:50.821769-07:00","container_config":{"Hostname":"","Domainname":"","User":"","Memory":0,"MemorySwap":0,"CpuShares":0,"AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"PortSpecs":null,"ExposedPorts":null,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":null,"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"NetworkDisabled":false,"OnBuild":null},"docker_version":"0.4.0","architecture":"x86_64","Size":0} \ No newline at end of file diff --git a/utils/testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/layer.tar b/utils/testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/layer.tar new file mode 100644 index 0000000000000000000000000000000000000000..880b3f2c56a3e5e42a7db3b7cc020f95c15104a6 GIT binary patch literal 1536 zcmdPXXP`MSFfcJNH#KE2fB Date: Tue, 13 May 2014 15:14:32 -0400 Subject: [PATCH 079/400] tarsum: test gofmt Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- utils/tarsum_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/tarsum_test.go b/utils/tarsum_test.go index 77cacfb73..1b2f0ba73 100644 --- a/utils/tarsum_test.go +++ b/utils/tarsum_test.go @@ -15,11 +15,11 @@ type testLayer struct { } var testLayers = []testLayer{ - testLayer{ + { filename: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar", jsonfile: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/json", tarsum: "tarsum+sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b"}, - testLayer{ + { filename: "testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/layer.tar", jsonfile: "testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/json", tarsum: "tarsum+sha256:ac672ee85da9ab7f9667ae3c32841d3e42f33cc52c273c23341dabba1c8b0c8b"}, From af891a67516149c4db490018430114c137cba9db Mon Sep 17 00:00:00 2001 From: SvenDowideit Date: Wed, 14 May 2014 07:43:41 +1000 Subject: [PATCH 080/400] Subject and object containers can be confused and `create` is now a loaded word for some readers Docker-DCO-1.1-Signed-off-by: SvenDowideit (github: SvenDowideit) --- docs/sources/use/working_with_volumes.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/use/working_with_volumes.md b/docs/sources/use/working_with_volumes.md index 7d6136b85..065b867c6 100644 --- a/docs/sources/use/working_with_volumes.md +++ b/docs/sources/use/working_with_volumes.md @@ -42,9 +42,9 @@ container with two new volumes: This command will create the new container with two new volumes that exits instantly (`true` is pretty much the smallest, -simplest program that you can run). Once created you can mount its -volumes in any other container using the `--volumes-from` -option; irrespective of whether the container is running or +simplest program that you can run). You can then mount its +volumes in any other container using the `run` `--volumes-from` +option; irrespective of whether the volume container is running or not. Or, you can use the VOLUME instruction in a Dockerfile to add one or From 195f3a3f42ac8894ade9e3dbf8f02945781b6ace Mon Sep 17 00:00:00 2001 From: SvenDowideit Date: Wed, 14 May 2014 10:22:55 +1000 Subject: [PATCH 081/400] Add a mention of 80 column lines and reflow the document to hide the evidence. Docker-DCO-1.1-Signed-off-by: SvenDowideit (github: SvenDowideit) --- docs/README.md | 100 ++++++++++++++++++++++++------------------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/docs/README.md b/docs/README.md index 47b390bda..71ce54800 100755 --- a/docs/README.md +++ b/docs/README.md @@ -1,37 +1,35 @@ # Docker Documentation -The source for Docker documentation is here under `sources/` and uses -extended Markdown, as implemented by [mkdocs](http://mkdocs.org). +The source for Docker documentation is here under `sources/` and uses extended +Markdown, as implemented by [mkdocs](http://mkdocs.org). -The HTML files are built and hosted on `https://docs.docker.io`, and -update automatically after each change to the master or release branch -of [Docker on GitHub](https://github.com/dotcloud/docker) -thanks to post-commit hooks. The "docs" branch maps to the "latest" -documentation and the "master" (unreleased development) branch maps to -the "master" documentation. +The HTML files are built and hosted on `https://docs.docker.io`, and update +automatically after each change to the master or release branch of [Docker on +GitHub](https://github.com/dotcloud/docker) thanks to post-commit hooks. The +"docs" branch maps to the "latest" documentation and the "master" (unreleased +development) branch maps to the "master" documentation. ## Branches -**There are two branches related to editing docs**: `master` and a -`docs` branch. You should always edit documentation on a local branch -of the `master` branch, and send a PR against `master`. +**There are two branches related to editing docs**: `master` and a `docs` +branch. You should always edit documentation on a local branch of the `master` +branch, and send a PR against `master`. -That way your fixes will automatically get included in later releases, -and docs maintainers can easily cherry-pick your changes into the -`docs` release branch. In the rare case where your change is not -forward-compatible, you may need to base your changes on the `docs` -branch. +That way your fixes will automatically get included in later releases, and docs +maintainers can easily cherry-pick your changes into the `docs` release branch. +In the rare case where your change is not forward-compatible, you may need to +base your changes on the `docs` branch. Also, now that we have a `docs` branch, we can keep the -[http://docs.docker.io](http://docs.docker.io) docs up to date with any -bugs found between `docker` code releases. +[http://docs.docker.io](http://docs.docker.io) docs up to date with any bugs +found between `docker` code releases. **Warning**: When *reading* the docs, the -[http://beta-docs.docker.io](http://beta-docs.docker.io) documentation -may include features not yet part of any official docker release. The -`beta-docs` site should be used only for understanding bleeding-edge -development and `docs.docker.io` (which points to the `docs` -branch`) should be used for the latest official release. +[http://beta-docs.docker.io](http://beta-docs.docker.io) documentation may +include features not yet part of any official docker release. The `beta-docs` +site should be used only for understanding bleeding-edge development and +`docs.docker.io` (which points to the `docs` branch`) should be used for the +latest official release. ## Contributing @@ -41,59 +39,61 @@ branch`) should be used for the latest official release. ## Getting Started -Docker documentation builds are done in a Docker container, which -installs all the required tools, adds the local `docs/` directory and -builds the HTML docs. It then starts a HTTP server on port 8000 so that -you can connect and see your changes. +Docker documentation builds are done in a Docker container, which installs all +the required tools, adds the local `docs/` directory and builds the HTML docs. +It then starts a HTTP server on port 8000 so that you can connect and see your +changes. In the root of the `docker` source directory: make docs -If you have any issues you need to debug, you can use `make docs-shell` and -then run `mkdocs serve` +If you have any issues you need to debug, you can use `make docs-shell` and then +run `mkdocs serve` + +## Style guide + +The documentation is written with paragraphs wrapped at 80 colum lines to make +it easier for terminal use. ### Examples -When writing examples give the user hints by making them resemble what -they see in their shell: +When writing examples give the user hints by making them resemble what they see +in their shell: - Indent shell examples by 4 spaces so they get rendered as code. - Start typed commands with `$ ` (dollar space), so that they are easily -differentiated from program output. + differentiated from program output. - Program output has no prefix. - Comments begin with `# ` (hash space). - In-container shell commands begin with `$$ ` (dollar dollar space). ### Images -When you need to add images, try to make them as small as possible -(e.g. as gifs). Usually images should go in the same directory as the -`.md` file which references them, or in a subdirectory if one already -exists. +When you need to add images, try to make them as small as possible (e.g. as +gifs). Usually images should go in the same directory as the `.md` file which +references them, or in a subdirectory if one already exists. ## Working using GitHub's file editor -Alternatively, for small changes and typos you might want to use -GitHub's built in file editor. It allows you to preview your changes -right on-line (though there can be some differences between GitHub -Markdown and [MkDocs Markdown](http://www.mkdocs.org/user-guide/writing-your-docs/)). -Just be careful not to create many commits. And you must still -[sign your work!](../CONTRIBUTING.md#sign-your-work) +Alternatively, for small changes and typos you might want to use GitHub's built +in file editor. It allows you to preview your changes right on-line (though +there can be some differences between GitHub Markdown and [MkDocs +Markdown](http://www.mkdocs.org/user-guide/writing-your-docs/)). Just be +careful not to create many commits. And you must still [sign your +work!](../CONTRIBUTING.md#sign-your-work) ## Publishing Documentation -To publish a copy of the documentation you need a `docs/awsconfig` -file containing AWS settings to deploy to. The release script will +To publish a copy of the documentation you need a `docs/awsconfig` To make life +easier for file containing AWS settings to deploy to. The release script will create an s3 if needed, and will then push the files to it. - [profile dowideit-docs] - aws_access_key_id = IHOIUAHSIDH234rwf.... - aws_secret_access_key = OIUYSADJHLKUHQWIUHE...... - region = ap-southeast-2 + [profile dowideit-docs] aws_access_key_id = IHOIUAHSIDH234rwf.... + aws_secret_access_key = OIUYSADJHLKUHQWIUHE...... region = ap-southeast-2 -The `profile` name must be the same as the name of the bucket you are -deploying to - which you call from the `docker` directory: +The `profile` name must be the same as the name of the bucket you are deploying +to - which you call from the `docker` directory: make AWS_S3_BUCKET=dowideit-docs docs-release From 3de15bda7e1d3ab193094e6e07a5b2e42ea828bd Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 13 May 2014 13:34:31 -0700 Subject: [PATCH 082/400] Copy parents cpus and mems for cpuset Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/cgroups/fs/apply_raw.go | 12 +++++- pkg/cgroups/fs/cpuset.go | 86 ++++++++++++++++++++++++++++++++++--- 2 files changed, 89 insertions(+), 9 deletions(-) diff --git a/pkg/cgroups/fs/apply_raw.go b/pkg/cgroups/fs/apply_raw.go index 5f9fc826b..ee26bffac 100644 --- a/pkg/cgroups/fs/apply_raw.go +++ b/pkg/cgroups/fs/apply_raw.go @@ -103,12 +103,20 @@ func GetStats(c *cgroups.Cgroup, subsystem string, pid int) (map[string]float64, return sys.Stats(d) } -func (raw *data) path(subsystem string) (string, error) { +func (raw *data) parent(subsystem string) (string, error) { initPath, err := cgroups.GetInitCgroupDir(subsystem) if err != nil { return "", err } - return filepath.Join(raw.root, subsystem, initPath, raw.cgroup), nil + return filepath.Join(raw.root, subsystem, initPath), nil +} + +func (raw *data) path(subsystem string) (string, error) { + parent, err := raw.parent(subsystem) + if err != nil { + return "", err + } + return filepath.Join(parent, raw.cgroup), nil } func (raw *data) join(subsystem string) (string, error) { diff --git a/pkg/cgroups/fs/cpuset.go b/pkg/cgroups/fs/cpuset.go index 8a13c56ce..f9f65ba42 100644 --- a/pkg/cgroups/fs/cpuset.go +++ b/pkg/cgroups/fs/cpuset.go @@ -1,7 +1,11 @@ package fs import ( + "bytes" + "io/ioutil" "os" + "path/filepath" + "strconv" ) type cpusetGroup struct { @@ -10,16 +14,19 @@ type cpusetGroup struct { func (s *cpusetGroup) Set(d *data) error { // we don't want to join this cgroup unless it is specified if d.c.CpusetCpus != "" { - dir, err := d.join("cpuset") - if err != nil && d.c.CpusetCpus != "" { + dir, err := d.path("cpuset") + if err != nil { + return err + } + if err := s.ensureParent(dir); err != nil { return err } - defer func() { - if err != nil { - os.RemoveAll(dir) - } - }() + // because we are not using d.join we need to place the pid into the procs file + // unlike the other subsystems + if err := writeFile(dir, "cgroup.procs", strconv.Itoa(d.pid)); err != nil { + return err + } if err := writeFile(dir, "cpuset.cpus", d.c.CpusetCpus); err != nil { return err } @@ -34,3 +41,68 @@ func (s *cpusetGroup) Remove(d *data) error { func (s *cpusetGroup) Stats(d *data) (map[string]float64, error) { return nil, ErrNotSupportStat } + +func (s *cpusetGroup) getSubsystemSettings(parent string) (cpus []byte, mems []byte, err error) { + if cpus, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.cpus")); err != nil { + return + } + if mems, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.mems")); err != nil { + return + } + return cpus, mems, nil +} + +// ensureParent ensures that the parent directory of current is created +// with the proper cpus and mems files copied from it's parent if the values +// are a file with a new line char +func (s *cpusetGroup) ensureParent(current string) error { + parent := filepath.Dir(current) + + if _, err := os.Stat(parent); err != nil { + if !os.IsNotExist(err) { + return err + } + + if err := s.ensureParent(parent); err != nil { + return err + } + } + + if err := os.MkdirAll(current, 0755); err != nil && !os.IsExist(err) { + return err + } + return s.copyIfNeeded(current, parent) +} + +// copyIfNeeded copies the cpuset.cpus and cpuset.mems from the parent +// directory to the current directory if the file's contents are 0 +func (s *cpusetGroup) copyIfNeeded(current, parent string) error { + var ( + err error + currentCpus, currentMems []byte + parentCpus, parentMems []byte + ) + + if currentCpus, currentMems, err = s.getSubsystemSettings(current); err != nil { + return err + } + if parentCpus, parentMems, err = s.getSubsystemSettings(parent); err != nil { + return err + } + + if s.isEmpty(currentCpus) { + if err := writeFile(current, "cpuset.cpus", string(parentCpus)); err != nil { + return err + } + } + if s.isEmpty(currentMems) { + if err := writeFile(current, "cpuset.mems", string(parentMems)); err != nil { + return err + } + } + return nil +} + +func (s *cpusetGroup) isEmpty(b []byte) bool { + return len(bytes.Trim(b, "\n")) == 0 +} From bfc3a4192ae5723e401470688cdae59b95bd61f1 Mon Sep 17 00:00:00 2001 From: cyphar Date: Sat, 10 May 2014 16:38:47 +1000 Subject: [PATCH 083/400] daemon: container: ensure cp cannot traverse outside container rootfs This patch fixes the bug that allowed cp to copy files outside of the containers rootfs, by passing a relative path (such as ../../../../../../../../etc/shadow). This is fixed by first converting the path to an absolute path (relative to /) and then appending it to the container's rootfs before continuing. Docker-DCO-1.1-Signed-off-by: Aleksa Sarai (github: cyphar) --- AUTHORS | 1 + daemon/container.go | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/AUTHORS b/AUTHORS index adfcfaa85..b8c58ab09 100644 --- a/AUTHORS +++ b/AUTHORS @@ -6,6 +6,7 @@ Aanand Prasad Aaron Feng Abel Muiño +Aleksa Sarai Alexander Larsson Alexey Shamrin Alex Gaynor diff --git a/daemon/container.go b/daemon/container.go index 7b6b65494..7250b442a 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -745,8 +745,13 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) { if err := container.Mount(); err != nil { return nil, err } + var filter []string + + // Ensure path is local to container basefs + resource = path.Join("/", resource) basePath := path.Join(container.basefs, resource) + stat, err := os.Stat(basePath) if err != nil { container.Unmount() From 79ca77f3e80d983cf72aa131c1b59c77c60270b0 Mon Sep 17 00:00:00 2001 From: cyphar Date: Sat, 10 May 2014 22:51:45 +1000 Subject: [PATCH 084/400] integration-cli: cp: added tests for cp This patch adds integration tests for the copying of resources from a container, to ensure that regressions in the security of resource copying can be easily discovered. Docker-DCO-1.1-Signed-off-by: Aleksa Sarai (github: cyphar) --- integration-cli/docker_cli_cp_test.go | 208 ++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 integration-cli/docker_cli_cp_test.go diff --git a/integration-cli/docker_cli_cp_test.go b/integration-cli/docker_cli_cp_test.go new file mode 100644 index 000000000..b5a70a45e --- /dev/null +++ b/integration-cli/docker_cli_cp_test.go @@ -0,0 +1,208 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" +) + +const ( + cpTestPathParent = "/some" + cpTestPath = "/some/path" + cpTestName = "test" + cpFullPath = "/some/path/test" + + cpContainerContents = "holla, i am the container" + cpHostContents = "hello, i am the host" +) + +// Test for #5656 +// Check that garbage paths don't escape the container's rootfs +func TestCpGarbagePath(t *testing.T) { + out, exitCode, err := cmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) + if err != nil || exitCode != 0 { + t.Fatal("failed to create a container", out, err) + } + + cleanedContainerID := stripTrailingCharacters(out) + defer deleteContainer(cleanedContainerID) + + out, _, err = cmd(t, "wait", cleanedContainerID) + if err != nil || stripTrailingCharacters(out) != "0" { + t.Fatal("failed to set up container", out, err) + } + + if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { + t.Fatal(err) + } + + hostFile, err := os.Create(cpFullPath) + if err != nil { + t.Fatal(err) + } + defer hostFile.Close() + defer os.RemoveAll(cpTestPathParent) + + fmt.Fprintf(hostFile, "%s", cpHostContents) + + tmpdir, err := ioutil.TempDir("", "docker-integration") + if err != nil { + t.Fatal(err) + } + + tmpname := filepath.Join(tmpdir, cpTestName) + defer os.RemoveAll(tmpdir) + + path := filepath.Join("../../../../../../../../../../../../", cpFullPath) + + _, _, err = cmd(t, "cp", cleanedContainerID+":"+path, tmpdir) + if err != nil { + t.Fatalf("couldn't copy from garbage path: %s:%s %s", cleanedContainerID, path, err) + } + + file, _ := os.Open(tmpname) + defer file.Close() + + test, err := ioutil.ReadAll(file) + if err != nil { + t.Fatal(err) + } + + if string(test) == cpHostContents { + t.Errorf("output matched host file -- garbage path can escape container rootfs") + } + + if string(test) != cpContainerContents { + t.Errorf("output doesn't match the input for garbage path") + } + + logDone("cp - garbage paths relative to container's rootfs") +} + +// Check that relative paths are relative to the container's rootfs +func TestCpRelativePath(t *testing.T) { + out, exitCode, err := cmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) + if err != nil || exitCode != 0 { + t.Fatal("failed to create a container", out, err) + } + + cleanedContainerID := stripTrailingCharacters(out) + defer deleteContainer(cleanedContainerID) + + out, _, err = cmd(t, "wait", cleanedContainerID) + if err != nil || stripTrailingCharacters(out) != "0" { + t.Fatal("failed to set up container", out, err) + } + + if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { + t.Fatal(err) + } + + hostFile, err := os.Create(cpFullPath) + if err != nil { + t.Fatal(err) + } + defer hostFile.Close() + defer os.RemoveAll(cpTestPathParent) + + fmt.Fprintf(hostFile, "%s", cpHostContents) + + tmpdir, err := ioutil.TempDir("", "docker-integration") + + if err != nil { + t.Fatal(err) + } + + tmpname := filepath.Join(tmpdir, cpTestName) + defer os.RemoveAll(tmpdir) + + path, _ := filepath.Rel("/", cpFullPath) + + _, _, err = cmd(t, "cp", cleanedContainerID+":"+path, tmpdir) + if err != nil { + t.Fatalf("couldn't copy from relative path: %s:%s %s", cleanedContainerID, path, err) + } + + file, _ := os.Open(tmpname) + defer file.Close() + + test, err := ioutil.ReadAll(file) + if err != nil { + t.Fatal(err) + } + + if string(test) == cpHostContents { + t.Errorf("output matched host file -- relative path can escape container rootfs") + } + + if string(test) != cpContainerContents { + t.Errorf("output doesn't match the input for relative path") + } + + logDone("cp - relative paths relative to container's rootfs") +} + +// Check that absolute paths are relative to the container's rootfs +func TestCpAbsolutePath(t *testing.T) { + out, exitCode, err := cmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) + if err != nil || exitCode != 0 { + t.Fatal("failed to create a container", out, err) + } + + cleanedContainerID := stripTrailingCharacters(out) + defer deleteContainer(cleanedContainerID) + + out, _, err = cmd(t, "wait", cleanedContainerID) + if err != nil || stripTrailingCharacters(out) != "0" { + t.Fatal("failed to set up container", out, err) + } + + if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { + t.Fatal(err) + } + + hostFile, err := os.Create(cpFullPath) + if err != nil { + t.Fatal(err) + } + defer hostFile.Close() + defer os.RemoveAll(cpTestPathParent) + + fmt.Fprintf(hostFile, "%s", cpHostContents) + + tmpdir, err := ioutil.TempDir("", "docker-integration") + + if err != nil { + t.Fatal(err) + } + + tmpname := filepath.Join(tmpdir, cpTestName) + defer os.RemoveAll(tmpdir) + + path := cpFullPath + + _, _, err = cmd(t, "cp", cleanedContainerID+":"+path, tmpdir) + if err != nil { + t.Fatalf("couldn't copy from absolute path: %s:%s %s", cleanedContainerID, path, err) + } + + file, _ := os.Open(tmpname) + defer file.Close() + + test, err := ioutil.ReadAll(file) + if err != nil { + t.Fatal(err) + } + + if string(test) == cpHostContents { + t.Errorf("output matched host file -- absolute path can escape container rootfs") + } + + if string(test) != cpContainerContents { + t.Errorf("output doesn't match the input for absolute path") + } + + logDone("cp - absolute paths relative to container's rootfs") +} From 0fb507dc2328c5c364a2cd1701a155efb1767a1a Mon Sep 17 00:00:00 2001 From: cyphar Date: Mon, 12 May 2014 06:49:46 +1000 Subject: [PATCH 085/400] daemon: *: refactored container resource path generation This patch is a preventative patch, it fixes possible future vulnerabilities regarding unsantised paths. Due to several recent vulnerabilities, wherein the docker daemon could be fooled into accessing data from the host (rather than a container), this patch was created to try and mitigate future possible vulnerabilities in the same vein. Docker-DCO-1.1-Signed-off-by: Aleksa Sarai (github: cyphar) --- daemon/container.go | 36 ++++++++++++++++++++++-------------- daemon/volumes.go | 4 ++-- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index 7250b442a..4f10cc4e9 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -9,6 +9,7 @@ import ( "log" "os" "path" + "path/filepath" "strings" "sync" "syscall" @@ -89,7 +90,7 @@ func (container *Container) Inject(file io.Reader, pth string) error { defer container.Unmount() // Return error if path exists - destPath := path.Join(container.basefs, pth) + destPath := container.getResourcePath(pth) if _, err := os.Stat(destPath); err == nil { // Since err is nil, the path could be stat'd and it exists return fmt.Errorf("%s exists", pth) @@ -101,7 +102,7 @@ func (container *Container) Inject(file io.Reader, pth string) error { } // Make sure the directory exists - if err := os.MkdirAll(path.Join(container.basefs, path.Dir(pth)), 0755); err != nil { + if err := os.MkdirAll(container.getResourcePath(path.Dir(pth)), 0755); err != nil { return err } @@ -170,6 +171,16 @@ func (container *Container) WriteHostConfig() (err error) { return ioutil.WriteFile(container.hostConfigPath(), data, 0666) } +func (container *Container) getResourcePath(path string) string { + cleanPath := filepath.Join("/", path) + return filepath.Join(container.basefs, cleanPath) +} + +func (container *Container) getRootResourcePath(path string) string { + cleanPath := filepath.Join("/", path) + return filepath.Join(container.root, cleanPath) +} + func populateCommand(c *Container, env []string) error { var ( en *execdriver.Network @@ -344,7 +355,7 @@ func (container *Container) StderrLogPipe() io.ReadCloser { } func (container *Container) buildHostnameFile() error { - container.HostnamePath = path.Join(container.root, "hostname") + container.HostnamePath = container.getRootResourcePath("hostname") if container.Config.Domainname != "" { return ioutil.WriteFile(container.HostnamePath, []byte(fmt.Sprintf("%s.%s\n", container.Config.Hostname, container.Config.Domainname)), 0644) } @@ -356,7 +367,7 @@ func (container *Container) buildHostnameAndHostsFiles(IP string) error { return err } - container.HostsPath = path.Join(container.root, "hosts") + container.HostsPath = container.getRootResourcePath("hosts") extraContent := make(map[string]string) @@ -674,7 +685,7 @@ func (container *Container) Unmount() error { } func (container *Container) logPath(name string) string { - return path.Join(container.root, fmt.Sprintf("%s-%s.log", container.ID, name)) + return container.getRootResourcePath(fmt.Sprintf("%s-%s.log", container.ID, name)) } func (container *Container) ReadLog(name string) (io.Reader, error) { @@ -682,11 +693,11 @@ func (container *Container) ReadLog(name string) (io.Reader, error) { } func (container *Container) hostConfigPath() string { - return path.Join(container.root, "hostconfig.json") + return container.getRootResourcePath("hostconfig.json") } func (container *Container) jsonPath() string { - return path.Join(container.root, "config.json") + return container.getRootResourcePath("config.json") } // This method must be exported to be used from the lxc template @@ -748,10 +759,7 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) { var filter []string - // Ensure path is local to container basefs - resource = path.Join("/", resource) - basePath := path.Join(container.basefs, resource) - + basePath := container.getResourcePath(resource) stat, err := os.Stat(basePath) if err != nil { container.Unmount() @@ -849,7 +857,7 @@ func (container *Container) setupContainerDns() error { } else if len(daemon.config.DnsSearch) > 0 { dnsSearch = daemon.config.DnsSearch } - container.ResolvConfPath = path.Join(container.root, "resolv.conf") + container.ResolvConfPath = container.getRootResourcePath("resolv.conf") return resolvconf.Build(container.ResolvConfPath, dns, dnsSearch) } else { container.ResolvConfPath = "/etc/resolv.conf" @@ -987,12 +995,12 @@ func (container *Container) setupWorkingDirectory() error { if container.Config.WorkingDir != "" { container.Config.WorkingDir = path.Clean(container.Config.WorkingDir) - pthInfo, err := os.Stat(path.Join(container.basefs, container.Config.WorkingDir)) + pthInfo, err := os.Stat(container.getResourcePath(container.Config.WorkingDir)) if err != nil { if !os.IsNotExist(err) { return err } - if err := os.MkdirAll(path.Join(container.basefs, container.Config.WorkingDir), 0755); err != nil { + if err := os.MkdirAll(container.getResourcePath(container.Config.WorkingDir), 0755); err != nil { return err } } diff --git a/daemon/volumes.go b/daemon/volumes.go index ac01c6a98..eac743b2d 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -94,11 +94,11 @@ func applyVolumesFrom(container *Container) error { if _, exists := container.Volumes[volPath]; exists { continue } - stat, err := os.Stat(filepath.Join(c.basefs, volPath)) + stat, err := os.Stat(c.getResourcePath(volPath)) if err != nil { return err } - if err := createIfNotExists(filepath.Join(container.basefs, volPath), stat.IsDir()); err != nil { + if err := createIfNotExists(container.getResourcePath(volPath), stat.IsDir()); err != nil { return err } container.Volumes[volPath] = id From adbe3096e8c8572925dbae5f19ac2ce2dc84fb1c Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 12 May 2014 17:44:57 -0700 Subject: [PATCH 086/400] Add cpuset cpus support for docker Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/container.go | 1 + daemon/execdriver/driver.go | 7 ++++--- daemon/execdriver/lxc/lxc_template.go | 3 +++ daemon/execdriver/native/create.go | 1 + docs/sources/reference/commandline/cli.md | 1 + integration-cli/docker_cli_run_test.go | 11 +++++++++++ runconfig/config.go | 8 +++++--- runconfig/parse.go | 2 ++ 8 files changed, 28 insertions(+), 6 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index 7b6b65494..b8743f4d0 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -215,6 +215,7 @@ func populateCommand(c *Container, env []string) error { Memory: c.Config.Memory, MemorySwap: c.Config.MemorySwap, CpuShares: c.Config.CpuShares, + Cpuset: c.Config.Cpuset, } c.command = &execdriver.Command{ ID: c.ID, diff --git a/daemon/execdriver/driver.go b/daemon/execdriver/driver.go index 4837a398e..a3e43e60a 100644 --- a/daemon/execdriver/driver.go +++ b/daemon/execdriver/driver.go @@ -103,9 +103,10 @@ type NetworkInterface struct { } type Resources struct { - Memory int64 `json:"memory"` - MemorySwap int64 `json:"memory_swap"` - CpuShares int64 `json:"cpu_shares"` + Memory int64 `json:"memory"` + MemorySwap int64 `json:"memory_swap"` + CpuShares int64 `json:"cpu_shares"` + Cpuset string `json:"cpuset"` } type Mount struct { diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index d40b31676..f2364cb77 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -127,6 +127,9 @@ lxc.cgroup.memory.memsw.limit_in_bytes = {{$memSwap}} {{if .Resources.CpuShares}} lxc.cgroup.cpu.shares = {{.Resources.CpuShares}} {{end}} +{{if .Resources.Cpuset}} +lxc.cgroup.cpuset.cpus = {{.Resources.Cpuset}} +{{end}} {{end}} {{if .Config.lxc}} diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index a7b3d9a10..5adae30db 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -117,6 +117,7 @@ func (d *driver) setupCgroups(container *libcontainer.Container, c *execdriver.C container.Cgroups.Memory = c.Resources.Memory container.Cgroups.MemoryReservation = c.Resources.Memory container.Cgroups.MemorySwap = c.Resources.MemorySwap + container.Cgroups.CpusetCpus = c.Resources.Cpuset } return nil } diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 8e0507cbf..cfa0c284c 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -811,6 +811,7 @@ Run a command in a new container -a, --attach=[] Attach to stdin, stdout or stderr. -c, --cpu-shares=0 CPU shares (relative weight) + --cpuset="" CPUs in which to allow execution (0-3, 0,1) --cidfile="" Write the container ID to the file -d, --detach=false Detached mode: Run container in the background, print new container id --dns=[] Set custom dns servers diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index b9737feee..a439c5f38 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -768,3 +768,14 @@ func TestProcWritableInPrivilegedContainers(t *testing.T) { logDone("run - proc writable in privileged container") } + +func TestRunWithCpuset(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--cpuset", "0", "busybox", "true") + if code, err := runCommand(cmd); err != nil || code != 0 { + t.Fatalf("container should run successfuly with cpuset of 0: %s", err) + } + + deleteAllContainers() + + logDone("run - cpuset 0") +} diff --git a/runconfig/config.go b/runconfig/config.go index 33a7882b6..8a069c64c 100644 --- a/runconfig/config.go +++ b/runconfig/config.go @@ -12,9 +12,10 @@ type Config struct { Hostname string Domainname string User string - Memory int64 // Memory limit (in bytes) - MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap - CpuShares int64 // CPU shares (relative weight vs. other containers) + Memory int64 // Memory limit (in bytes) + MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap + CpuShares int64 // CPU shares (relative weight vs. other containers) + Cpuset string // Cpuset 0-2, 0,1 AttachStdin bool AttachStdout bool AttachStderr bool @@ -41,6 +42,7 @@ func ContainerConfigFromJob(job *engine.Job) *Config { Memory: job.GetenvInt64("Memory"), MemorySwap: job.GetenvInt64("MemorySwap"), CpuShares: job.GetenvInt64("CpuShares"), + Cpuset: job.Getenv("Cpuset"), AttachStdin: job.GetenvBool("AttachStdin"), AttachStdout: job.GetenvBool("AttachStdout"), AttachStderr: job.GetenvBool("AttachStderr"), diff --git a/runconfig/parse.go b/runconfig/parse.go index 5a588c034..8c4b9dcc1 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -63,6 +63,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID") flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container") flCpuShares = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)") + flCpuset = cmd.String([]string{"-cpuset"}, "", "CPUs in which to allow execution (0-3, 0,1)") flNetMode = cmd.String([]string{"-net"}, "bridge", "Set the Network mode for the container\n'bridge': creates a new network stack for the container on the docker bridge\n'none': no networking for this container\n'container:': reuses another container network stack\n'host': use the host network stack inside the contaner") // For documentation purpose _ = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxify all received signal to the process (even in non-tty mode)") @@ -219,6 +220,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf OpenStdin: *flStdin, Memory: flMemory, CpuShares: *flCpuShares, + Cpuset: *flCpuset, AttachStdin: flAttach.Get("stdin"), AttachStdout: flAttach.Get("stdout"), AttachStderr: flAttach.Get("stderr"), From 5128feb690e8fd0244d1fecef5f3f5f77598bbfa Mon Sep 17 00:00:00 2001 From: Alexandr Morozov Date: Tue, 13 May 2014 21:16:06 +0400 Subject: [PATCH 087/400] Refactoring collections/orderedintset and benchmarks for it Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- pkg/collections/orderedintset.go | 81 +++++++++++++-------------- pkg/collections/orderedintset_test.go | 71 +++++++++++++++++++++++ 2 files changed, 111 insertions(+), 41 deletions(-) create mode 100644 pkg/collections/orderedintset_test.go diff --git a/pkg/collections/orderedintset.go b/pkg/collections/orderedintset.go index 23abab04d..7442f2e93 100644 --- a/pkg/collections/orderedintset.go +++ b/pkg/collections/orderedintset.go @@ -1,12 +1,13 @@ package collections import ( + "sort" "sync" ) // OrderedIntSet is a thread-safe sorted set and a stack. type OrderedIntSet struct { - sync.RWMutex + sync.Mutex set []int } @@ -15,29 +16,22 @@ func NewOrderedIntSet() *OrderedIntSet { return &OrderedIntSet{} } -// Push takes a string and adds it to the set. If the elem aready exists, it has no effect. +// Push takes an int and adds it to the set. If the elem aready exists, it has no effect. func (s *OrderedIntSet) Push(elem int) { - s.RLock() - for _, e := range s.set { - if e == elem { - s.RUnlock() - return - } - } - s.RUnlock() - s.Lock() + if len(s.set) == 0 { + s.set = append(s.set, elem) + s.Unlock() + return + } // Make sure the list is always sorted - for i, e := range s.set { - if elem < e { - s.set = append(s.set[:i], append([]int{elem}, s.set[i:]...)...) - s.Unlock() - return - } + i := sort.SearchInts(s.set, elem) + if i < len(s.set) && s.set[i] == elem { + s.Unlock() + return } - // If we reach here, then elem is the biggest elem of the list. - s.set = append(s.set, elem) + s.set = append(s.set[:i], append([]int{elem}, s.set[i:]...)...) s.Unlock() } @@ -46,28 +40,26 @@ func (s *OrderedIntSet) Pop() int { return s.PopFront() } -// Pop returns the first elemen from the list and removes it. +// Pop returns the first element from the list and removes it. // If the list is empty, it returns 0 func (s *OrderedIntSet) PopFront() int { - s.RLock() - - for i, e := range s.set { - ret := e - s.RUnlock() - s.Lock() - s.set = append(s.set[:i], s.set[i+1:]...) + s.Lock() + if len(s.set) == 0 { s.Unlock() - return ret + return 0 } - s.RUnlock() - - return 0 + ret := s.set[0] + s.set = s.set[1:] + s.Unlock() + return ret } // PullBack retrieve the last element of the list. // The element is not removed. // If the list is empty, an empty element is returned. func (s *OrderedIntSet) PullBack() int { + s.Lock() + defer s.Unlock() if len(s.set) == 0 { return 0 } @@ -76,21 +68,28 @@ func (s *OrderedIntSet) PullBack() int { // Exists checks if the given element present in the list. func (s *OrderedIntSet) Exists(elem int) bool { - for _, e := range s.set { - if e == elem { - return true - } + s.Lock() + if len(s.set) == 0 { + s.Unlock() + return false } - return false + i := sort.SearchInts(s.set, elem) + res := i < len(s.set) && s.set[i] == elem + s.Unlock() + return res } // Remove removes an element from the list. // If the element is not found, it has no effect. func (s *OrderedIntSet) Remove(elem int) { - for i, e := range s.set { - if e == elem { - s.set = append(s.set[:i], s.set[i+1:]...) - return - } + s.Lock() + if len(s.set) == 0 { + s.Unlock() + return } + i := sort.SearchInts(s.set, elem) + if i < len(s.set) && s.set[i] == elem { + s.set = append(s.set[:i], s.set[i+1:]...) + } + s.Unlock() } diff --git a/pkg/collections/orderedintset_test.go b/pkg/collections/orderedintset_test.go new file mode 100644 index 000000000..0ac4ca545 --- /dev/null +++ b/pkg/collections/orderedintset_test.go @@ -0,0 +1,71 @@ +package collections + +import ( + "math/rand" + "testing" +) + +func BenchmarkPush(b *testing.B) { + var testSet []int + for i := 0; i < 1000; i++ { + testSet = append(testSet, rand.Int()) + } + s := NewOrderedIntSet() + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, elem := range testSet { + s.Push(elem) + } + } +} + +func BenchmarkPop(b *testing.B) { + var testSet []int + for i := 0; i < 1000; i++ { + testSet = append(testSet, rand.Int()) + } + s := NewOrderedIntSet() + for _, elem := range testSet { + s.Push(elem) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + for j := 0; j < 1000; j++ { + s.Pop() + } + } +} + +func BenchmarkExist(b *testing.B) { + var testSet []int + for i := 0; i < 1000; i++ { + testSet = append(testSet, rand.Intn(2000)) + } + s := NewOrderedIntSet() + for _, elem := range testSet { + s.Push(elem) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + for j := 0; j < 1000; j++ { + s.Exists(j) + } + } +} + +func BenchmarkRemove(b *testing.B) { + var testSet []int + for i := 0; i < 1000; i++ { + testSet = append(testSet, rand.Intn(2000)) + } + s := NewOrderedIntSet() + for _, elem := range testSet { + s.Push(elem) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + for j := 0; j < 1000; j++ { + s.Remove(j) + } + } +} From 80fca061e7661549a05b2696488db3fea008e2dd Mon Sep 17 00:00:00 2001 From: Alexandr Morozov Date: Tue, 13 May 2014 23:04:45 +0400 Subject: [PATCH 088/400] Change ip allocation logic Now IP reuses only after all IPs from network was allocated Fixes #5729 Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- daemon/networkdriver/ipallocator/allocator.go | 42 +++--- .../ipallocator/allocator_test.go | 125 +++++++++++++++--- 2 files changed, 124 insertions(+), 43 deletions(-) diff --git a/daemon/networkdriver/ipallocator/allocator.go b/daemon/networkdriver/ipallocator/allocator.go index 914df3494..4bcce6517 100644 --- a/daemon/networkdriver/ipallocator/allocator.go +++ b/daemon/networkdriver/ipallocator/allocator.go @@ -7,9 +7,19 @@ import ( "github.com/dotcloud/docker/pkg/collections" "net" "sync" + "sync/atomic" ) -type networkSet map[string]*collections.OrderedIntSet +type allocatedMap struct { + *collections.OrderedIntSet + last int32 +} + +func newAllocatedMap() *allocatedMap { + return &allocatedMap{OrderedIntSet: collections.NewOrderedIntSet()} +} + +type networkSet map[string]*allocatedMap var ( ErrNoAvailableIPs = errors.New("no available ip addresses on network") @@ -19,7 +29,6 @@ var ( var ( lock = sync.Mutex{} allocatedIPs = networkSet{} - availableIPS = networkSet{} ) // RequestIP requests an available ip from the given network. It @@ -55,13 +64,11 @@ func ReleaseIP(address *net.IPNet, ip *net.IP) error { checkAddress(address) var ( - existing = allocatedIPs[address.String()] - available = availableIPS[address.String()] + allocated = allocatedIPs[address.String()] pos = getPosition(address, ip) ) - existing.Remove(int(pos)) - available.Push(int(pos)) + allocated.Remove(int(pos)) return nil } @@ -82,29 +89,19 @@ func getPosition(address *net.IPNet, ip *net.IP) int32 { func getNextIp(address *net.IPNet) (*net.IP, error) { var ( ownIP = ipToInt(&address.IP) - available = availableIPS[address.String()] allocated = allocatedIPs[address.String()] first, _ = networkdriver.NetworkRange(address) base = ipToInt(&first) size = int(networkdriver.NetworkSize(address.Mask)) max = int32(size - 2) // size -1 for the broadcast address, -1 for the gateway address - pos = int32(available.Pop()) + pos = atomic.LoadInt32(&allocated.last) ) - // We pop and push the position not the ip - if pos != 0 { - ip := intToIP(int32(base + pos)) - allocated.Push(int(pos)) - - return ip, nil - } - var ( firstNetIP = address.IP.To4().Mask(address.Mask) firstAsInt = ipToInt(&firstNetIP) + 1 ) - pos = int32(allocated.PullBack()) for i := int32(0); i < max; i++ { pos = pos%max + 1 next := int32(base + pos) @@ -116,6 +113,7 @@ func getNextIp(address *net.IPNet) (*net.IP, error) { if !allocated.Exists(int(pos)) { ip := intToIP(next) allocated.Push(int(pos)) + atomic.StoreInt32(&allocated.last, pos) return ip, nil } } @@ -124,15 +122,14 @@ func getNextIp(address *net.IPNet) (*net.IP, error) { func registerIP(address *net.IPNet, ip *net.IP) error { var ( - existing = allocatedIPs[address.String()] - available = availableIPS[address.String()] + allocated = allocatedIPs[address.String()] pos = getPosition(address, ip) ) - if existing.Exists(int(pos)) { + if allocated.Exists(int(pos)) { return ErrIPAlreadyAllocated } - available.Remove(int(pos)) + atomic.StoreInt32(&allocated.last, pos) return nil } @@ -153,7 +150,6 @@ func intToIP(n int32) *net.IP { func checkAddress(address *net.IPNet) { key := address.String() if _, exists := allocatedIPs[key]; !exists { - allocatedIPs[key] = collections.NewOrderedIntSet() - availableIPS[key] = collections.NewOrderedIntSet() + allocatedIPs[key] = newAllocatedMap() } } diff --git a/daemon/networkdriver/ipallocator/allocator_test.go b/daemon/networkdriver/ipallocator/allocator_test.go index 5e9fcfc98..2b63d9a03 100644 --- a/daemon/networkdriver/ipallocator/allocator_test.go +++ b/daemon/networkdriver/ipallocator/allocator_test.go @@ -8,7 +8,6 @@ import ( func reset() { allocatedIPs = networkSet{} - availableIPS = networkSet{} } func TestRequestNewIps(t *testing.T) { @@ -18,8 +17,10 @@ func TestRequestNewIps(t *testing.T) { Mask: []byte{255, 255, 255, 0}, } + var ip *net.IP + var err error for i := 2; i < 10; i++ { - ip, err := RequestIP(network, nil) + ip, err = RequestIP(network, nil) if err != nil { t.Fatal(err) } @@ -28,6 +29,17 @@ func TestRequestNewIps(t *testing.T) { t.Fatalf("Expected ip %s got %s", expected, ip.String()) } } + value := intToIP(ipToInt(ip) + 1).String() + if err := ReleaseIP(network, ip); err != nil { + t.Fatal(err) + } + ip, err = RequestIP(network, nil) + if err != nil { + t.Fatal(err) + } + if ip.String() != value { + t.Fatalf("Expected to receive the next ip %s got %s", value, ip.String()) + } } func TestReleaseIp(t *testing.T) { @@ -64,6 +76,17 @@ func TestGetReleasedIp(t *testing.T) { t.Fatal(err) } + for i := 0; i < 252; i++ { + _, err = RequestIP(network, nil) + if err != nil { + t.Fatal(err) + } + err = ReleaseIP(network, ip) + if err != nil { + t.Fatal(err) + } + } + ip, err = RequestIP(network, nil) if err != nil { t.Fatal(err) @@ -185,24 +208,6 @@ func TestIPAllocator(t *testing.T) { newIPs[i] = ip } - // Before loop begin - // 2(u) - 3(u) - 4(f) - 5(f) - 6(f) - // ↑ - - // After i = 0 - // 2(u) - 3(u) - 4(f) - 5(u) - 6(f) - // ↑ - - // After i = 1 - // 2(u) - 3(u) - 4(f) - 5(u) - 6(u) - // ↑ - - // After i = 2 - // 2(u) - 3(u) - 4(u) - 5(u) - 6(u) - // ↑ - - // Reordered these because the new set will always return the - // lowest ips first and not in the order that they were released assertIPEquals(t, &expectedIPs[2], newIPs[0]) assertIPEquals(t, &expectedIPs[3], newIPs[1]) assertIPEquals(t, &expectedIPs[4], newIPs[2]) @@ -234,6 +239,86 @@ func TestAllocateFirstIP(t *testing.T) { } } +func TestAllocateAllIps(t *testing.T) { + defer reset() + network := &net.IPNet{ + IP: []byte{192, 168, 0, 1}, + Mask: []byte{255, 255, 255, 0}, + } + + var ( + current, first *net.IP + err error + isFirst = true + ) + + for err == nil { + current, err = RequestIP(network, nil) + if isFirst { + first = current + isFirst = false + } + } + + if err != ErrNoAvailableIPs { + t.Fatal(err) + } + + if _, err := RequestIP(network, nil); err != ErrNoAvailableIPs { + t.Fatal(err) + } + + if err := ReleaseIP(network, first); err != nil { + t.Fatal(err) + } + + again, err := RequestIP(network, nil) + if err != nil { + t.Fatal(err) + } + + assertIPEquals(t, first, again) +} + +func TestAllocateDifferentSubnets(t *testing.T) { + defer reset() + network1 := &net.IPNet{ + IP: []byte{192, 168, 0, 1}, + Mask: []byte{255, 255, 255, 0}, + } + network2 := &net.IPNet{ + IP: []byte{127, 0, 0, 1}, + Mask: []byte{255, 255, 255, 0}, + } + expectedIPs := []net.IP{ + 0: net.IPv4(192, 168, 0, 2), + 1: net.IPv4(192, 168, 0, 3), + 2: net.IPv4(127, 0, 0, 2), + 3: net.IPv4(127, 0, 0, 3), + } + + ip11, err := RequestIP(network1, nil) + if err != nil { + t.Fatal(err) + } + ip12, err := RequestIP(network1, nil) + if err != nil { + t.Fatal(err) + } + ip21, err := RequestIP(network2, nil) + if err != nil { + t.Fatal(err) + } + ip22, err := RequestIP(network2, nil) + if err != nil { + t.Fatal(err) + } + assertIPEquals(t, &expectedIPs[0], ip11) + assertIPEquals(t, &expectedIPs[1], ip12) + assertIPEquals(t, &expectedIPs[2], ip21) + assertIPEquals(t, &expectedIPs[3], ip22) +} + func assertIPEquals(t *testing.T, ip1, ip2 *net.IP) { if !ip1.Equal(*ip2) { t.Fatalf("Expected IP %s, got %s", ip1, ip2) From 830c2d7fa3f19a2fba50464273ae19cd2113e689 Mon Sep 17 00:00:00 2001 From: Bernerd Schaefer Date: Wed, 14 May 2014 10:56:55 +0200 Subject: [PATCH 089/400] "nsinit exec ..." forwards signals to container Docker-DCO-1.1-Signed-off-by: Bernerd Schaefer (github: bernerdschaefer) --- pkg/libcontainer/nsinit/nsinit/main.go | 32 +++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/pkg/libcontainer/nsinit/nsinit/main.go b/pkg/libcontainer/nsinit/nsinit/main.go index b5325d40b..b076b5c55 100644 --- a/pkg/libcontainer/nsinit/nsinit/main.go +++ b/pkg/libcontainer/nsinit/nsinit/main.go @@ -5,6 +5,8 @@ import ( "io/ioutil" "log" "os" + "os/exec" + "os/signal" "path/filepath" "strconv" @@ -39,7 +41,7 @@ func main() { exitCode, err = nsinit.ExecIn(container, nspid, os.Args[2:]) } else { term := nsinit.NewTerminal(os.Stdin, os.Stdout, os.Stderr, container.Tty) - exitCode, err = nsinit.Exec(container, term, "", dataPath, os.Args[2:], nsinit.DefaultCreateCommand, nil) + exitCode, err = startContainer(container, term, dataPath, os.Args[2:]) } if err != nil { @@ -95,3 +97,31 @@ func readPid() (int, error) { } return pid, nil } + +// startContainer starts the container. Returns the exit status or -1 and an +// error. +// +// Signals sent to the current process will be forwarded to container. +func startContainer(container *libcontainer.Container, term nsinit.Terminal, dataPath string, args []string) (int, error) { + var ( + cmd *exec.Cmd + sigc = make(chan os.Signal, 10) + ) + + signal.Notify(sigc) + + createCommand := func(container *libcontainer.Container, console, rootfs, dataPath, init string, pipe *os.File, args []string) *exec.Cmd { + cmd = nsinit.DefaultCreateCommand(container, console, rootfs, dataPath, init, pipe, args) + return cmd + } + + startCallback := func() { + go func() { + for sig := range sigc { + cmd.Process.Signal(sig) + } + }() + } + + return nsinit.Exec(container, term, "", dataPath, args, createCommand, startCallback) +} From e963179c790ec49b28fae0d7ebc0d9d2b3ac0b72 Mon Sep 17 00:00:00 2001 From: unclejack Date: Wed, 14 May 2014 14:17:58 +0300 Subject: [PATCH 090/400] don't call sort for every add in history This moves the call to sort in daemon/history to a function to be called explicitly when we're done adding elements to the list. This speeds up `docker ps`. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- daemon/daemon.go | 1 + daemon/history.go | 3 +++ 2 files changed, 4 insertions(+) diff --git a/daemon/daemon.go b/daemon/daemon.go index 38a107bba..33ddb87ae 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -85,6 +85,7 @@ func (daemon *Daemon) List() []*Container { for e := daemon.containers.Front(); e != nil; e = e.Next() { containers.Add(e.Value.(*Container)) } + containers.Sort() return *containers } diff --git a/daemon/history.go b/daemon/history.go index 57a00a209..0b125ad2b 100644 --- a/daemon/history.go +++ b/daemon/history.go @@ -26,5 +26,8 @@ func (history *History) Swap(i, j int) { func (history *History) Add(container *Container) { *history = append(*history, container) +} + +func (history *History) Sort() { sort.Sort(history) } From 219b7ae8b526bb5e6d0e27176308db71438a002f Mon Sep 17 00:00:00 2001 From: unclejack Date: Wed, 14 May 2014 17:55:06 +0300 Subject: [PATCH 091/400] add UpdateSuffixarray and refactor TruncIndex This commit refactors TruncIndex to make it possible to add container ids to the TruncIndex without updating the Suffixarray. This is useful during the Docker daemon's startup when we don't want to update the Suffixarray for every container we add. Add continues to function like before. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- utils/utils.go | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/utils/utils.go b/utils/utils.go index 7ffcc06b9..7788b20d5 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -493,9 +493,7 @@ func NewTruncIndex(ids []string) (idx *TruncIndex) { return } -func (idx *TruncIndex) Add(id string) error { - idx.Lock() - defer idx.Unlock() +func (idx *TruncIndex) addId(id string) error { if strings.Contains(id, " ") { return fmt.Errorf("Illegal character: ' '") } @@ -504,10 +502,31 @@ func (idx *TruncIndex) Add(id string) error { } idx.ids[id] = true idx.bytes = append(idx.bytes, []byte(id+" ")...) + return nil +} + +func (idx *TruncIndex) Add(id string) error { + idx.Lock() + defer idx.Unlock() + if err := idx.addId(id); err != nil { + return err + } idx.index = suffixarray.New(idx.bytes) return nil } +func (idx *TruncIndex) AddWithoutSuffixarrayUpdate(id string) error { + idx.Lock() + defer idx.Unlock() + return idx.addId(id) +} + +func (idx *TruncIndex) UpdateSuffixarray() { + idx.Lock() + defer idx.Unlock() + idx.index = suffixarray.New(idx.bytes) +} + func (idx *TruncIndex) Delete(id string) error { idx.Lock() defer idx.Unlock() From 5d5c89398c39e2f38459aae42189c9ca1125c1d3 Mon Sep 17 00:00:00 2001 From: unclejack Date: Wed, 14 May 2014 17:58:37 +0300 Subject: [PATCH 092/400] update Suffixarray only once during daemon startup This commit makes the Docker daemon call UpdateSuffixarray only after it finishes registering all containers. This lowers the amount of time required for the Docker daemon to start up. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- daemon/daemon.go | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index 38a107bba..143683001 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -141,7 +141,13 @@ func (daemon *Daemon) load(id string) (*Container, error) { } // Register makes a container object usable by the daemon as +// This is a wrapper for register func (daemon *Daemon) Register(container *Container) error { + return daemon.register(container, true) +} + +// register makes a container object usable by the daemon as +func (daemon *Daemon) register(container *Container, updateSuffixarray bool) error { if container.daemon != nil || daemon.Exists(container.ID) { return fmt.Errorf("Container is already loaded") } @@ -165,7 +171,14 @@ func (daemon *Daemon) Register(container *Container) error { } // done daemon.containers.PushBack(container) - daemon.idIndex.Add(container.ID) + + // don't update the Suffixarray if we're starting up + // we'll waste time if we update it for every container + if updateSuffixarray { + daemon.idIndex.Add(container.ID) + } else { + daemon.idIndex.AddWithoutSuffixarrayUpdate(container.ID) + } // FIXME: if the container is supposed to be running but is not, auto restart it? // if so, then we need to restart monitor and init a new lock @@ -329,8 +342,8 @@ func (daemon *Daemon) restore() error { } } - register := func(container *Container) { - if err := daemon.Register(container); err != nil { + registerContainer := func(container *Container) { + if err := daemon.register(container, false); err != nil { utils.Debugf("Failed to register container %s: %s", container.ID, err) } } @@ -342,7 +355,7 @@ func (daemon *Daemon) restore() error { } e := entities[p] if container, ok := containers[e.ID()]; ok { - register(container) + registerContainer(container) delete(containers, e.ID()) } } @@ -359,9 +372,10 @@ func (daemon *Daemon) restore() error { if _, err := daemon.containerGraph.Set(container.Name, container.ID); err != nil { utils.Debugf("Setting default id - %s", err) } - register(container) + registerContainer(container) } + daemon.idIndex.UpdateSuffixarray() if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { fmt.Printf(": done.\n") } From d153740d9c7d672b9433f173b9a098a5d3c14c53 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 14 May 2014 11:48:17 -0400 Subject: [PATCH 093/400] tarsum: adding benchmarks to cover a couple of use-cases: * 1mb file, using no compression * 1mb file, using compression * 1024 1k files, using no compression * 1024 1k files, using compression Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- utils/tarsum_test.go | 169 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 165 insertions(+), 4 deletions(-) diff --git a/utils/tarsum_test.go b/utils/tarsum_test.go index 1b2f0ba73..52ddd6459 100644 --- a/utils/tarsum_test.go +++ b/utils/tarsum_test.go @@ -1,6 +1,10 @@ package utils import ( + "bytes" + "crypto/rand" + "fmt" + "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" "io" "io/ioutil" "os" @@ -9,6 +13,7 @@ import ( type testLayer struct { filename string + options *sizedOptions jsonfile string gzip bool tarsum string @@ -19,20 +24,100 @@ var testLayers = []testLayer{ filename: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar", jsonfile: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/json", tarsum: "tarsum+sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b"}, + { + filename: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar", + jsonfile: "testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/json", + gzip: true, + tarsum: "tarsum+sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b"}, { filename: "testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/layer.tar", jsonfile: "testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/json", tarsum: "tarsum+sha256:ac672ee85da9ab7f9667ae3c32841d3e42f33cc52c273c23341dabba1c8b0c8b"}, + { + options: &sizedOptions{1, 1024 * 1024, false, false}, // a 1mb file (in memory) + tarsum: "tarsum+sha256:8bf12d7e67c51ee2e8306cba569398b1b9f419969521a12ffb9d8875e8836738"}, +} + +type sizedOptions struct { + num int64 + size int64 + isRand bool + realFile bool +} + +// make a tar: +// * num is the number of files the tar should have +// * size is the bytes per file +// * isRand is whether the contents of the files should be a random chunk (otherwise it's all zeros) +// * realFile will write to a TempFile, instead of an in memory buffer +func sizedTar(opts sizedOptions) io.Reader { + var ( + fh io.ReadWriter + err error + ) + if opts.realFile { + fh, err = ioutil.TempFile("", "tarsum") + if err != nil { + return nil + } + } else { + fh = bytes.NewBuffer([]byte{}) + } + tarW := tar.NewWriter(fh) + for i := int64(0); i < opts.num; i++ { + err := tarW.WriteHeader(&tar.Header{ + Name: fmt.Sprintf("/testdata%d", i), + Mode: 0755, + Uid: 0, + Gid: 0, + Size: opts.size, + }) + if err != nil { + return nil + } + var rBuf []byte + if opts.isRand { + rBuf = make([]byte, 8) + _, err = rand.Read(rBuf) + if err != nil { + return nil + } + } else { + rBuf = []byte{0, 0, 0, 0, 0, 0, 0, 0} + } + + for i := int64(0); i < opts.size/int64(8); i++ { + tarW.Write(rBuf) + } + } + return fh } func TestTarSums(t *testing.T) { for _, layer := range testLayers { - fh, err := os.Open(layer.filename) - if err != nil { - t.Errorf("failed to open %s: %s", layer.filename, err) + var ( + fh io.Reader + err error + ) + if len(layer.filename) > 0 { + fh, err = os.Open(layer.filename) + if err != nil { + t.Errorf("failed to open %s: %s", layer.filename, err) + continue + } + } else if layer.options != nil { + fh = sizedTar(*layer.options) + } else { + // What else is there to test? + t.Errorf("what to do with %#V", layer) continue } - ts := &TarSum{Reader: fh} + if file, ok := fh.(*os.File); ok { + defer file.Close() + } + + // double negatives! + ts := &TarSum{Reader: fh, DisableCompression: !layer.gzip} _, err = io.Copy(ioutil.Discard, ts) if err != nil { t.Errorf("failed to copy from %s: %s", layer.filename, err) @@ -58,6 +143,82 @@ func TestTarSums(t *testing.T) { if layer.tarsum != gotSum { t.Errorf("expecting [%s], but got [%s]", layer.tarsum, gotSum) } + } +} +func Benchmark9kTar(b *testing.B) { + buf := bytes.NewBuffer([]byte{}) + fh, err := os.Open("testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar") + if err != nil { + b.Error(err) + return + } + n, err := io.Copy(buf, fh) + fh.Close() + + b.SetBytes(n) + b.ResetTimer() + for i := 0; i < b.N; i++ { + ts := &TarSum{Reader: buf, DisableCompression: true} + io.Copy(ioutil.Discard, ts) + ts.Sum(nil) + } +} + +func Benchmark9kTarGzip(b *testing.B) { + buf := bytes.NewBuffer([]byte{}) + fh, err := os.Open("testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar") + if err != nil { + b.Error(err) + return + } + n, err := io.Copy(buf, fh) + fh.Close() + + b.SetBytes(n) + b.ResetTimer() + for i := 0; i < b.N; i++ { + ts := &TarSum{Reader: buf, DisableCompression: false} + io.Copy(ioutil.Discard, ts) + ts.Sum(nil) + } +} + +// this is a single big file in the tar archive +func Benchmark1mbSingleFileTar(b *testing.B) { + benchmarkTar(b, sizedOptions{1, 1024 * 1024, true, true}, false) +} + +// this is a single big file in the tar archive +func Benchmark1mbSingleFileTarGzip(b *testing.B) { + benchmarkTar(b, sizedOptions{1, 1024 * 1024, true, true}, true) +} + +// this is 1024 1k files in the tar archive +func Benchmark1kFilesTar(b *testing.B) { + benchmarkTar(b, sizedOptions{1024, 1024, true, true}, false) +} + +// this is 1024 1k files in the tar archive +func Benchmark1kFilesTarGzip(b *testing.B) { + benchmarkTar(b, sizedOptions{1024, 1024, true, true}, true) +} + +func benchmarkTar(b *testing.B, opts sizedOptions, isGzip bool) { + var fh *os.File + tarReader := sizedTar(opts) + if br, ok := tarReader.(*os.File); ok { + fh = br + } + defer os.Remove(fh.Name()) + defer fh.Close() + + b.SetBytes(opts.size * opts.num) + b.ResetTimer() + for i := 0; i < b.N; i++ { + ts := &TarSum{Reader: fh, DisableCompression: !isGzip} + io.Copy(ioutil.Discard, ts) + ts.Sum(nil) + fh.Seek(0, 0) } } From a50a46048b8853ddfdac6f000ece86f95229dfea Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 14 May 2014 20:12:42 +0200 Subject: [PATCH 094/400] Minor fixups to the Docs README Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- docs/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/README.md b/docs/README.md index 71ce54800..fa3c50108 100755 --- a/docs/README.md +++ b/docs/README.md @@ -1,12 +1,12 @@ # Docker Documentation The source for Docker documentation is here under `sources/` and uses extended -Markdown, as implemented by [mkdocs](http://mkdocs.org). +Markdown, as implemented by [MkDocs](http://mkdocs.org). The HTML files are built and hosted on `https://docs.docker.io`, and update automatically after each change to the master or release branch of [Docker on GitHub](https://github.com/dotcloud/docker) thanks to post-commit hooks. The -"docs" branch maps to the "latest" documentation and the "master" (unreleased +`docs` branch maps to the "latest" documentation and the `master` (unreleased development) branch maps to the "master" documentation. ## Branches @@ -22,11 +22,11 @@ base your changes on the `docs` branch. Also, now that we have a `docs` branch, we can keep the [http://docs.docker.io](http://docs.docker.io) docs up to date with any bugs -found between `docker` code releases. +found between Docker code releases. **Warning**: When *reading* the docs, the [http://beta-docs.docker.io](http://beta-docs.docker.io) documentation may -include features not yet part of any official docker release. The `beta-docs` +include features not yet part of any official Docker release. The `beta-docs` site should be used only for understanding bleeding-edge development and `docs.docker.io` (which points to the `docs` branch`) should be used for the latest official release. From 137f4b326ad1669cd562fa59d809f39d21b653fe Mon Sep 17 00:00:00 2001 From: Joel Handwell Date: Wed, 14 May 2014 16:22:38 -0400 Subject: [PATCH 095/400] changed deprecated -rm option to --rm changed deprecated -rm option to --rm --- docs/sources/examples/running_ssh_service.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/examples/running_ssh_service.md b/docs/sources/examples/running_ssh_service.md index 864d10c72..fc7cd933b 100644 --- a/docs/sources/examples/running_ssh_service.md +++ b/docs/sources/examples/running_ssh_service.md @@ -35,7 +35,7 @@ quick access to a test container. Build the image using: - $ sudo docker build -rm -t eg_sshd . + $ sudo docker build --rm -t eg_sshd . Then run it. You can then use `docker port` to find out what host port the container's port 22 is mapped to: From 2d622df835f8c80b213a68bbceef21fb003a9a6a Mon Sep 17 00:00:00 2001 From: Joel Handwell Date: Wed, 14 May 2014 16:24:08 -0400 Subject: [PATCH 096/400] changed deprecated -name option to --name changed deprecated -name option to --name --- docs/sources/examples/running_ssh_service.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/examples/running_ssh_service.md b/docs/sources/examples/running_ssh_service.md index 864d10c72..b2cb39280 100644 --- a/docs/sources/examples/running_ssh_service.md +++ b/docs/sources/examples/running_ssh_service.md @@ -40,7 +40,7 @@ Build the image using: Then run it. You can then use `docker port` to find out what host port the container's port 22 is mapped to: - $ sudo docker run -d -P -name test_sshd eg_sshd + $ sudo docker run -d -P --name test_sshd eg_sshd $ sudo docker port test_sshd 22 0.0.0.0:49154 From 8699f53e6a033a7b5b55a2b6356da7c6e220d01f Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Wed, 14 May 2014 22:24:11 +0200 Subject: [PATCH 097/400] Check channel closed state in /events loop When listener channel is closed, it becomes always available for reading: select becomes an active loop which writes default-constructed events (i.e: empty strings). Fixes #5766. Docker-DCO-1.1-Signed-off-by: Arnaud Porterie (github: icecrime) --- AUTHORS | 1 + server/server.go | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index b8c58ab09..014748e18 100644 --- a/AUTHORS +++ b/AUTHORS @@ -28,6 +28,7 @@ Anthony Bishopric Anton Nikitin Antony Messerli apocas +Arnaud Porterie Asbjørn Enge Barry Allard Bartłomiej Piotrowski diff --git a/server/server.go b/server/server.go index d82c5fe54..cab11bcf5 100644 --- a/server/server.go +++ b/server/server.go @@ -259,7 +259,10 @@ func (srv *Server) Events(job *engine.Job) engine.Status { } for { select { - case event := <-listener: + case event, ok := <-listener: + if !ok { // Channel is closed: listener was evicted + return engine.StatusOK + } err := sendEvent(&event) if err != nil && err.Error() == "JSON error" { continue From f664e9a7d9a584220c28486f8e40cb1a1023e7f6 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 13 May 2014 06:49:23 +0200 Subject: [PATCH 098/400] Added to small patch exemption to cover Markdown/syntax errors in documentation Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- CONTRIBUTING.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d77afbc44..db75354c3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -192,7 +192,10 @@ curl -o .git/hooks/prepare-commit-msg https://raw.github.com/dotcloud/docker/mas There are several exceptions to the signing requirement. Currently these are: * Your patch fixes spelling or grammar errors. -* Your patch is a single line change to documentation. +* Your patch is a single line change to documentation contained in the + `docs` directory. +* Your patch fixes Markdown formatting or syntax errors in the + documentation contained in the `docs` directory. If you have any questions, please refer to the FAQ in the [docs](http://docs.docker.io) From 2bc34036b9106318f8564ee36b696ad070b02573 Mon Sep 17 00:00:00 2001 From: Bernerd Schaefer Date: Mon, 12 May 2014 14:41:07 +0200 Subject: [PATCH 099/400] Setup standard /dev symlinks After copying allowed device nodes, set up "/dev/fd", "/dev/stdin", "/dev/stdout", and "/dev/stderr" symlinks. Docker-DCO-1.1-Signed-off-by: Bernerd Schaefer (github: bernerdschaefer) [rebased by @crosbymichael] Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/mount/init.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/pkg/libcontainer/mount/init.go b/pkg/libcontainer/mount/init.go index e5f2b43ca..6f3080cf0 100644 --- a/pkg/libcontainer/mount/init.go +++ b/pkg/libcontainer/mount/init.go @@ -54,6 +54,9 @@ func InitializeMountNamespace(rootfs, console string, container *libcontainer.Co if err := SetupPtmx(rootfs, console, container.Context["mount_label"]); err != nil { return err } + if err := setupDevSymlinks(rootfs); err != nil { + return fmt.Errorf("dev symlinks %s", err) + } if err := system.Chdir(rootfs); err != nil { return fmt.Errorf("chdir into %s %s", rootfs, err) } @@ -114,6 +117,34 @@ func createIfNotExists(path string, isDir bool) error { return nil } +func setupDevSymlinks(rootfs string) error { + var links = [][2]string{ + {"/proc/self/fd", "/dev/fd"}, + {"/proc/self/fd/0", "/dev/stdin"}, + {"/proc/self/fd/1", "/dev/stdout"}, + {"/proc/self/fd/2", "/dev/stderr"}, + } + + // kcore support can be toggled with CONFIG_PROC_KCORE; only create a symlink + // in /dev if it exists in /proc. + if _, err := os.Stat("/proc/kcore"); err == nil { + links = append(links, [2]string{"/proc/kcore", "/dev/kcore"}) + } + + for _, link := range links { + var ( + src = link[0] + dst = filepath.Join(rootfs, link[1]) + ) + + if err := os.Symlink(src, dst); err != nil && !os.IsExist(err) { + return fmt.Errorf("symlink %s %s %s", src, dst, err) + } + } + + return nil +} + func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error { for _, m := range bindMounts.OfType("bind") { var ( From 359f8aca29bf0e7fca757fa73e1d5565ed2d16f7 Mon Sep 17 00:00:00 2001 From: unclejack Date: Tue, 13 May 2014 15:23:56 +0300 Subject: [PATCH 100/400] process directories in parallel in ChangesDirs This commit lowers the total time spent in ChangesDirs to half during a commit. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- archive/changes.go | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/archive/changes.go b/archive/changes.go index 723e4a742..88cea0f70 100644 --- a/archive/changes.go +++ b/archive/changes.go @@ -3,15 +3,16 @@ package archive import ( "bytes" "fmt" - "github.com/dotcloud/docker/pkg/system" - "github.com/dotcloud/docker/utils" - "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" "io" "os" "path/filepath" "strings" "syscall" "time" + + "github.com/dotcloud/docker/pkg/system" + "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) type ChangeType int @@ -293,13 +294,23 @@ func collectFileInfo(sourceDir string) (*FileInfo, error) { // Compare two directories and generate an array of Change objects describing the changes func ChangesDirs(newDir, oldDir string) ([]Change, error) { - oldRoot, err := collectFileInfo(oldDir) - if err != nil { - return nil, err - } - newRoot, err := collectFileInfo(newDir) - if err != nil { - return nil, err + var ( + oldRoot, newRoot *FileInfo + err1, err2 error + errs = make(chan error, 2) + ) + go func() { + oldRoot, err1 = collectFileInfo(oldDir) + errs <- err1 + }() + go func() { + newRoot, err2 = collectFileInfo(newDir) + errs <- err2 + }() + for i := 0; i < 2; i++ { + if err := <-errs; err != nil { + return nil, err + } } return newRoot.Changes(oldRoot), nil From 3b7a19def609c8fbadc6559e7f47f8a5a7769a5b Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 14 May 2014 15:21:44 -0700 Subject: [PATCH 101/400] Move cgroups package into libcontainer Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/execdriver/lxc/driver.go | 2 +- daemon/execdriver/native/driver.go | 2 +- daemon/execdriver/native/template/default_template.go | 2 +- pkg/{ => libcontainer}/cgroups/MAINTAINERS | 0 pkg/{ => libcontainer}/cgroups/cgroups.go | 0 pkg/{ => libcontainer}/cgroups/cgroups_test.go | 0 pkg/{ => libcontainer}/cgroups/fs/apply_raw.go | 2 +- pkg/{ => libcontainer}/cgroups/fs/blkio.go | 2 +- pkg/{ => libcontainer}/cgroups/fs/blkio_test.go | 0 pkg/{ => libcontainer}/cgroups/fs/cpu.go | 0 pkg/{ => libcontainer}/cgroups/fs/cpu_test.go | 0 pkg/{ => libcontainer}/cgroups/fs/cpuacct.go | 2 +- pkg/{ => libcontainer}/cgroups/fs/cpuset.go | 0 pkg/{ => libcontainer}/cgroups/fs/devices.go | 0 pkg/{ => libcontainer}/cgroups/fs/freezer.go | 2 +- pkg/{ => libcontainer}/cgroups/fs/memory.go | 0 pkg/{ => libcontainer}/cgroups/fs/memory_test.go | 0 pkg/{ => libcontainer}/cgroups/fs/perf_event.go | 2 +- pkg/{ => libcontainer}/cgroups/fs/test_util.go | 0 pkg/{ => libcontainer}/cgroups/fs/utils.go | 0 pkg/{ => libcontainer}/cgroups/fs/utils_test.go | 0 pkg/{ => libcontainer}/cgroups/systemd/apply_nosystemd.go | 3 ++- pkg/{ => libcontainer}/cgroups/systemd/apply_systemd.go | 2 +- pkg/{ => libcontainer}/cgroups/utils.go | 0 pkg/libcontainer/container.go | 2 +- pkg/libcontainer/nsinit/exec.go | 6 +++--- pkg/libcontainer/nsinit/unsupported.go | 2 +- pkg/sysinfo/sysinfo.go | 3 ++- 28 files changed, 18 insertions(+), 16 deletions(-) rename pkg/{ => libcontainer}/cgroups/MAINTAINERS (100%) rename pkg/{ => libcontainer}/cgroups/cgroups.go (100%) rename pkg/{ => libcontainer}/cgroups/cgroups_test.go (100%) rename pkg/{ => libcontainer}/cgroups/fs/apply_raw.go (98%) rename pkg/{ => libcontainer}/cgroups/fs/blkio.go (97%) rename pkg/{ => libcontainer}/cgroups/fs/blkio_test.go (100%) rename pkg/{ => libcontainer}/cgroups/fs/cpu.go (100%) rename pkg/{ => libcontainer}/cgroups/fs/cpu_test.go (100%) rename pkg/{ => libcontainer}/cgroups/fs/cpuacct.go (98%) rename pkg/{ => libcontainer}/cgroups/fs/cpuset.go (100%) rename pkg/{ => libcontainer}/cgroups/fs/devices.go (100%) rename pkg/{ => libcontainer}/cgroups/fs/freezer.go (95%) rename pkg/{ => libcontainer}/cgroups/fs/memory.go (100%) rename pkg/{ => libcontainer}/cgroups/fs/memory_test.go (100%) rename pkg/{ => libcontainer}/cgroups/fs/perf_event.go (89%) rename pkg/{ => libcontainer}/cgroups/fs/test_util.go (100%) rename pkg/{ => libcontainer}/cgroups/fs/utils.go (100%) rename pkg/{ => libcontainer}/cgroups/fs/utils_test.go (100%) rename pkg/{ => libcontainer}/cgroups/systemd/apply_nosystemd.go (78%) rename pkg/{ => libcontainer}/cgroups/systemd/apply_systemd.go (99%) rename pkg/{ => libcontainer}/cgroups/utils.go (100%) diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index 0b9817621..6b2b2cc46 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -15,8 +15,8 @@ import ( "time" "github.com/dotcloud/docker/daemon/execdriver" - "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/label" + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" "github.com/dotcloud/docker/pkg/system" "github.com/dotcloud/docker/utils" ) diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index 2e57729d4..59d193ed0 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -13,8 +13,8 @@ import ( "github.com/dotcloud/docker/daemon/execdriver" "github.com/dotcloud/docker/pkg/apparmor" - "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" "github.com/dotcloud/docker/pkg/libcontainer/nsinit" "github.com/dotcloud/docker/pkg/system" ) diff --git a/daemon/execdriver/native/template/default_template.go b/daemon/execdriver/native/template/default_template.go index 249c5d5fe..03b76e0f5 100644 --- a/daemon/execdriver/native/template/default_template.go +++ b/daemon/execdriver/native/template/default_template.go @@ -2,8 +2,8 @@ package template import ( "github.com/dotcloud/docker/pkg/apparmor" - "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) // New returns the docker default configuration for libcontainer diff --git a/pkg/cgroups/MAINTAINERS b/pkg/libcontainer/cgroups/MAINTAINERS similarity index 100% rename from pkg/cgroups/MAINTAINERS rename to pkg/libcontainer/cgroups/MAINTAINERS diff --git a/pkg/cgroups/cgroups.go b/pkg/libcontainer/cgroups/cgroups.go similarity index 100% rename from pkg/cgroups/cgroups.go rename to pkg/libcontainer/cgroups/cgroups.go diff --git a/pkg/cgroups/cgroups_test.go b/pkg/libcontainer/cgroups/cgroups_test.go similarity index 100% rename from pkg/cgroups/cgroups_test.go rename to pkg/libcontainer/cgroups/cgroups_test.go diff --git a/pkg/cgroups/fs/apply_raw.go b/pkg/libcontainer/cgroups/fs/apply_raw.go similarity index 98% rename from pkg/cgroups/fs/apply_raw.go rename to pkg/libcontainer/cgroups/fs/apply_raw.go index ee26bffac..65aabcc52 100644 --- a/pkg/cgroups/fs/apply_raw.go +++ b/pkg/libcontainer/cgroups/fs/apply_raw.go @@ -7,7 +7,7 @@ import ( "path/filepath" "strconv" - "github.com/dotcloud/docker/pkg/cgroups" + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) var ( diff --git a/pkg/cgroups/fs/blkio.go b/pkg/libcontainer/cgroups/fs/blkio.go similarity index 97% rename from pkg/cgroups/fs/blkio.go rename to pkg/libcontainer/cgroups/fs/blkio.go index 79e14fa2d..213d250d2 100644 --- a/pkg/cgroups/fs/blkio.go +++ b/pkg/libcontainer/cgroups/fs/blkio.go @@ -9,7 +9,7 @@ import ( "strconv" "strings" - "github.com/dotcloud/docker/pkg/cgroups" + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) type blkioGroup struct { diff --git a/pkg/cgroups/fs/blkio_test.go b/pkg/libcontainer/cgroups/fs/blkio_test.go similarity index 100% rename from pkg/cgroups/fs/blkio_test.go rename to pkg/libcontainer/cgroups/fs/blkio_test.go diff --git a/pkg/cgroups/fs/cpu.go b/pkg/libcontainer/cgroups/fs/cpu.go similarity index 100% rename from pkg/cgroups/fs/cpu.go rename to pkg/libcontainer/cgroups/fs/cpu.go diff --git a/pkg/cgroups/fs/cpu_test.go b/pkg/libcontainer/cgroups/fs/cpu_test.go similarity index 100% rename from pkg/cgroups/fs/cpu_test.go rename to pkg/libcontainer/cgroups/fs/cpu_test.go diff --git a/pkg/cgroups/fs/cpuacct.go b/pkg/libcontainer/cgroups/fs/cpuacct.go similarity index 98% rename from pkg/cgroups/fs/cpuacct.go rename to pkg/libcontainer/cgroups/fs/cpuacct.go index 892b5ab6b..3382440d2 100644 --- a/pkg/cgroups/fs/cpuacct.go +++ b/pkg/libcontainer/cgroups/fs/cpuacct.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "github.com/dotcloud/docker/pkg/cgroups" + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" "github.com/dotcloud/docker/pkg/system" ) diff --git a/pkg/cgroups/fs/cpuset.go b/pkg/libcontainer/cgroups/fs/cpuset.go similarity index 100% rename from pkg/cgroups/fs/cpuset.go rename to pkg/libcontainer/cgroups/fs/cpuset.go diff --git a/pkg/cgroups/fs/devices.go b/pkg/libcontainer/cgroups/fs/devices.go similarity index 100% rename from pkg/cgroups/fs/devices.go rename to pkg/libcontainer/cgroups/fs/devices.go diff --git a/pkg/cgroups/fs/freezer.go b/pkg/libcontainer/cgroups/fs/freezer.go similarity index 95% rename from pkg/cgroups/fs/freezer.go rename to pkg/libcontainer/cgroups/fs/freezer.go index 70cfcdde7..f3b1985a5 100644 --- a/pkg/cgroups/fs/freezer.go +++ b/pkg/libcontainer/cgroups/fs/freezer.go @@ -8,7 +8,7 @@ import ( "strconv" "strings" - "github.com/dotcloud/docker/pkg/cgroups" + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) type freezerGroup struct { diff --git a/pkg/cgroups/fs/memory.go b/pkg/libcontainer/cgroups/fs/memory.go similarity index 100% rename from pkg/cgroups/fs/memory.go rename to pkg/libcontainer/cgroups/fs/memory.go diff --git a/pkg/cgroups/fs/memory_test.go b/pkg/libcontainer/cgroups/fs/memory_test.go similarity index 100% rename from pkg/cgroups/fs/memory_test.go rename to pkg/libcontainer/cgroups/fs/memory_test.go diff --git a/pkg/cgroups/fs/perf_event.go b/pkg/libcontainer/cgroups/fs/perf_event.go similarity index 89% rename from pkg/cgroups/fs/perf_event.go rename to pkg/libcontainer/cgroups/fs/perf_event.go index 789b3e59a..063cec4b0 100644 --- a/pkg/cgroups/fs/perf_event.go +++ b/pkg/libcontainer/cgroups/fs/perf_event.go @@ -1,7 +1,7 @@ package fs import ( - "github.com/dotcloud/docker/pkg/cgroups" + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) type perfEventGroup struct { diff --git a/pkg/cgroups/fs/test_util.go b/pkg/libcontainer/cgroups/fs/test_util.go similarity index 100% rename from pkg/cgroups/fs/test_util.go rename to pkg/libcontainer/cgroups/fs/test_util.go diff --git a/pkg/cgroups/fs/utils.go b/pkg/libcontainer/cgroups/fs/utils.go similarity index 100% rename from pkg/cgroups/fs/utils.go rename to pkg/libcontainer/cgroups/fs/utils.go diff --git a/pkg/cgroups/fs/utils_test.go b/pkg/libcontainer/cgroups/fs/utils_test.go similarity index 100% rename from pkg/cgroups/fs/utils_test.go rename to pkg/libcontainer/cgroups/fs/utils_test.go diff --git a/pkg/cgroups/systemd/apply_nosystemd.go b/pkg/libcontainer/cgroups/systemd/apply_nosystemd.go similarity index 78% rename from pkg/cgroups/systemd/apply_nosystemd.go rename to pkg/libcontainer/cgroups/systemd/apply_nosystemd.go index 4faa74974..302fab773 100644 --- a/pkg/cgroups/systemd/apply_nosystemd.go +++ b/pkg/libcontainer/cgroups/systemd/apply_nosystemd.go @@ -4,7 +4,8 @@ package systemd import ( "fmt" - "github.com/dotcloud/docker/pkg/cgroups" + + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) func UseSystemd() bool { diff --git a/pkg/cgroups/systemd/apply_systemd.go b/pkg/libcontainer/cgroups/systemd/apply_systemd.go similarity index 99% rename from pkg/cgroups/systemd/apply_systemd.go rename to pkg/libcontainer/cgroups/systemd/apply_systemd.go index c4b0937b6..52940f6fa 100644 --- a/pkg/cgroups/systemd/apply_systemd.go +++ b/pkg/libcontainer/cgroups/systemd/apply_systemd.go @@ -11,7 +11,7 @@ import ( "sync" systemd1 "github.com/coreos/go-systemd/dbus" - "github.com/dotcloud/docker/pkg/cgroups" + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" "github.com/dotcloud/docker/pkg/systemd" "github.com/godbus/dbus" ) diff --git a/pkg/cgroups/utils.go b/pkg/libcontainer/cgroups/utils.go similarity index 100% rename from pkg/cgroups/utils.go rename to pkg/libcontainer/cgroups/utils.go diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index 5acdff3d2..7328f5544 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -1,7 +1,7 @@ package libcontainer import ( - "github.com/dotcloud/docker/pkg/cgroups" + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) // Context is a generic key value pair that allows diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 5d0d772a0..03e0f4b9a 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -7,10 +7,10 @@ import ( "os/exec" "syscall" - "github.com/dotcloud/docker/pkg/cgroups" - "github.com/dotcloud/docker/pkg/cgroups/fs" - "github.com/dotcloud/docker/pkg/cgroups/systemd" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" + "github.com/dotcloud/docker/pkg/libcontainer/cgroups/fs" + "github.com/dotcloud/docker/pkg/libcontainer/cgroups/systemd" "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/system" ) diff --git a/pkg/libcontainer/nsinit/unsupported.go b/pkg/libcontainer/nsinit/unsupported.go index 929b3dba5..51509f79a 100644 --- a/pkg/libcontainer/nsinit/unsupported.go +++ b/pkg/libcontainer/nsinit/unsupported.go @@ -3,8 +3,8 @@ package nsinit import ( - "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) func Exec(container *libcontainer.Container, term Terminal, rootfs, dataPath string, args []string, createCommand CreateCommand, startCallback func()) (int, error) { diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index 27af37bb8..bdc192b91 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -1,11 +1,12 @@ package sysinfo import ( - "github.com/dotcloud/docker/pkg/cgroups" "io/ioutil" "log" "os" "path" + + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) type SysInfo struct { From b22d10e3c541c46cebe7da44fd2f521c4bc653f4 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 14 May 2014 16:01:45 -0700 Subject: [PATCH 102/400] Remove the cgroups maintainer file We don't need this because it is covered by the libcontainer MAINTAINERS file Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/cgroups/MAINTAINERS | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 pkg/libcontainer/cgroups/MAINTAINERS diff --git a/pkg/libcontainer/cgroups/MAINTAINERS b/pkg/libcontainer/cgroups/MAINTAINERS deleted file mode 100644 index 30cb8bba2..000000000 --- a/pkg/libcontainer/cgroups/MAINTAINERS +++ /dev/null @@ -1,3 +0,0 @@ -Michael Crosby (@crosbymichael) -Rohit Jnagal (@rjnagal) -Victor Marmol (@vmarmol) From 2269472f3ad09de59ba15185b9558b91f1ecb0b5 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 14 May 2014 19:22:49 +0200 Subject: [PATCH 103/400] Updated a bunch of formatting in the docs/sources/use files Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- .../sources/use/ambassador_pattern_linking.md | 75 +++++++-------- docs/sources/use/basics.md | 57 +++++------ docs/sources/use/chef.md | 6 +- docs/sources/use/host_integration.md | 18 ++-- docs/sources/use/networking.md | 49 +++++----- docs/sources/use/port_redirection.md | 55 ++++++----- docs/sources/use/puppet.md | 11 ++- docs/sources/use/working_with_links_names.md | 95 +++++++++---------- docs/sources/use/working_with_volumes.md | 78 +++++++-------- docs/sources/use/workingwithrepository.md | 77 ++++++++------- 10 files changed, 261 insertions(+), 260 deletions(-) diff --git a/docs/sources/use/ambassador_pattern_linking.md b/docs/sources/use/ambassador_pattern_linking.md index 2bdd434f6..01962f19a 100644 --- a/docs/sources/use/ambassador_pattern_linking.md +++ b/docs/sources/use/ambassador_pattern_linking.md @@ -7,53 +7,48 @@ page_keywords: Examples, Usage, links, docker, documentation, examples, names, n ## Introduction Rather than hardcoding network links between a service consumer and -provider, Docker encourages service portability. - -eg, instead of +provider, Docker encourages service portability, for example instead of: (consumer) --> (redis) -requiring you to restart the `consumer` to attach it -to a different `redis` service, you can add -ambassadors +Requiring you to restart the `consumer` to attach it to a different +`redis` service, you can add ambassadors: (consumer) --> (redis-ambassador) --> (redis) - or +Or (consumer) --> (redis-ambassador) ---network---> (redis-ambassador) --> (redis) -When you need to rewire your consumer to talk to a different redis -server, you can just restart the `redis-ambassador` -container that the consumer is connected to. +When you need to rewire your consumer to talk to a different Redis +server, you can just restart the `redis-ambassador` container that the +consumer is connected to. -This pattern also allows you to transparently move the redis server to a +This pattern also allows you to transparently move the Redis server to a different docker host from the consumer. -Using the `svendowideit/ambassador` container, the -link wiring is controlled entirely from the `docker run` -parameters. +Using the `svendowideit/ambassador` container, the link wiring is +controlled entirely from the `docker run` parameters. ## Two host Example -Start actual redis server on one Docker host +Start actual Redis server on one Docker host big-server $ docker run -d -name redis crosbymichael/redis -Then add an ambassador linked to the redis server, mapping a port to the +Then add an ambassador linked to the Redis server, mapping a port to the outside world big-server $ docker run -d -link redis:redis -name redis_ambassador -p 6379:6379 svendowideit/ambassador On the other host, you can set up another ambassador setting environment -variables for each remote port we want to proxy to the -`big-server` +variables for each remote port we want to proxy to the `big-server` client-server $ docker run -d -name redis_ambassador -expose 6379 -e REDIS_PORT_6379_TCP=tcp://192.168.1.52:6379 svendowideit/ambassador -Then on the `client-server` host, you can use a -redis client container to talk to the remote redis server, just by -linking to the local redis ambassador. +Then on the `client-server` host, you can use a Redis client container +to talk to the remote Redis server, just by linking to the local Redis +ambassador. client-server $ docker run -i -t -rm -link redis_ambassador:redis relateiq/redis-cli redis 172.17.0.160:6379> ping @@ -61,10 +56,10 @@ linking to the local redis ambassador. ## How it works -The following example shows what the `svendowideit/ambassador` -container does automatically (with a tiny amount of `sed`) +The following example shows what the `svendowideit/ambassador` container +does automatically (with a tiny amount of `sed`) -On the docker host (192.168.1.52) that redis will run on: +On the Docker host (192.168.1.52) that Redis will run on: # start actual redis server $ docker run -d -name redis crosbymichael/redis @@ -81,8 +76,8 @@ On the docker host (192.168.1.52) that redis will run on: # add redis ambassador $ docker run -t -i -link redis:redis -name redis_ambassador -p 6379:6379 busybox sh -in the redis_ambassador container, you can see the linked redis -containers'senv +In the `redis_ambassador` container, you can see the linked Redis +containers `env`: $ env REDIS_PORT=tcp://172.17.0.136:6379 @@ -98,8 +93,8 @@ containers'senv PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PWD=/ -This environment is used by the ambassador socat script to expose redis -to the world (via the -p 6379:6379 port mapping) +This environment is used by the ambassador `socat` script to expose Redis +to the world (via the `-p 6379:6379` port mapping): $ docker rm redis_ambassador $ sudo ./contrib/mkimage-unittest.sh @@ -107,16 +102,16 @@ to the world (via the -p 6379:6379 port mapping) $ socat TCP4-LISTEN:6379,fork,reuseaddr TCP4:172.17.0.136:6379 -then ping the redis server via the ambassador +Now ping the Redis server via the ambassador: -Now goto a different server +Now go to a different server: $ sudo ./contrib/mkimage-unittest.sh $ docker run -t -i -expose 6379 -name redis_ambassador docker-ut sh $ socat TCP4-LISTEN:6379,fork,reuseaddr TCP4:192.168.1.52:6379 -and get the redis-cli image so we can talk over the ambassador bridge +And get the `redis-cli` image so we can talk over the ambassador bridge. $ docker pull relateiq/redis-cli $ docker run -i -t -rm -link redis_ambassador:redis relateiq/redis-cli @@ -125,16 +120,16 @@ and get the redis-cli image so we can talk over the ambassador bridge ## The svendowideit/ambassador Dockerfile -The `svendowideit/ambassador` image is a small -busybox image with `socat` built in. When you start -the container, it uses a small `sed` script to parse -out the (possibly multiple) link environment variables to set up the -port forwarding. On the remote host, you need to set the variable using -the `-e` command line option. +The `svendowideit/ambassador` image is a small `busybox` image with +`socat` built in. When you start the container, it uses a small `sed` +script to parse out the (possibly multiple) link environment variables +to set up the port forwarding. On the remote host, you need to set the +variable using the `-e` command line option. -`--expose 1234 -e REDIS_PORT_1234_TCP=tcp://192.168.1.52:6379` -will forward the local `1234` port to the -remote IP and port - in this case `192.168.1.52:6379`. + --expose 1234 -e REDIS_PORT_1234_TCP=tcp://192.168.1.52:6379 + +Will forward the local `1234` port to the remote IP and port, in this +case `192.168.1.52:6379`. # # diff --git a/docs/sources/use/basics.md b/docs/sources/use/basics.md index ee3eeabd9..e4422034d 100644 --- a/docs/sources/use/basics.md +++ b/docs/sources/use/basics.md @@ -12,10 +12,10 @@ your Docker install, run the following command: # Check that you have a working install $ docker info -If you get `docker: command not found` or something -like `/var/lib/docker/repositories: permission denied` -you may have an incomplete docker installation or insufficient -privileges to access Docker on your machine. +If you get `docker: command not found` or something like +`/var/lib/docker/repositories: permission denied` you may have an +incomplete Docker installation or insufficient privileges to access +Docker on your machine. Please refer to [*Installation*](/installation/#installation-list) for installation instructions. @@ -26,9 +26,9 @@ for installation instructions. $ sudo docker pull ubuntu This will find the `ubuntu` image by name on -[*Docker.io*](../workingwithrepository/#find-public-images-on-dockerio) and -download it from [Docker.io](https://index.docker.io) to a local image -cache. +[*Docker.io*](../workingwithrepository/#find-public-images-on-dockerio) +and download it from [Docker.io](https://index.docker.io) to a local +image cache. > **Note**: > When the image has successfully downloaded, you will see a 12 character @@ -50,7 +50,7 @@ cache. ## Bind Docker to another host/port or a Unix socket -> **Warning**: +> **Warning**: > Changing the default `docker` daemon binding to a > TCP port or Unix *docker* user group will increase your security risks > by allowing non-root users to gain *root* access on the host. Make sure @@ -58,41 +58,44 @@ cache. > to a TCP port, anyone with access to that port has full Docker access; > so it is not advisable on an open network. -With `-H` it is possible to make the Docker daemon -to listen on a specific IP and port. By default, it will listen on -`unix:///var/run/docker.sock` to allow only local -connections by the *root* user. You *could* set it to -`0.0.0.0:4243` or a specific host IP to give access -to everybody, but that is **not recommended** because then it is trivial -for someone to gain root access to the host where the daemon is running. +With `-H` it is possible to make the Docker daemon to listen on a +specific IP and port. By default, it will listen on +`unix:///var/run/docker.sock` to allow only local connections by the +*root* user. You *could* set it to `0.0.0.0:4243` or a specific host IP +to give access to everybody, but that is **not recommended** because +then it is trivial for someone to gain root access to the host where the +daemon is running. -Similarly, the Docker client can use `-H` to connect -to a custom port. +Similarly, the Docker client can use `-H` to connect to a custom port. -`-H` accepts host and port assignment in the -following format: `tcp://[host][:port]` or -`unix://path` +`-H` accepts host and port assignment in the following format: + + tcp://[host][:port]` or `unix://path For example: -- `tcp://host:4243` -> tcp connection on +- `tcp://host:4243` -> TCP connection on host:4243 -- `unix://path/to/socket` -> unix socket located +- `unix://path/to/socket` -> Unix socket located at `path/to/socket` `-H`, when empty, will default to the same value as when no `-H` was passed in. `-H` also accepts short form for TCP bindings: -`host[:port]` or `:port` - # Run docker in daemon mode + host[:port]` or `:port + +Run Docker in daemon mode: + $ sudo /docker -H 0.0.0.0:5555 -d & - # Download an ubuntu image + +Download an `ubuntu` image: + $ sudo docker -H :5555 pull ubuntu -You can use multiple `-H`, for example, if you want -to listen on both TCP and a Unix socket +You can use multiple `-H`, for example, if you want to listen on both +TCP and a Unix socket # Run docker in daemon mode $ sudo /docker -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock -d & diff --git a/docs/sources/use/chef.md b/docs/sources/use/chef.md index 897c2b429..5568e99af 100644 --- a/docs/sources/use/chef.md +++ b/docs/sources/use/chef.md @@ -19,8 +19,8 @@ operating systems. ## Installation The cookbook is available on the [Chef Community -Site](http://community.opscode.com/cookbooks/docker) and can be installed using -your favorite cookbook dependency manager. +Site](http://community.opscode.com/cookbooks/docker) and can be +installed using your favorite cookbook dependency manager. The source can be found on [GitHub](https://github.com/bflad/chef-docker). @@ -71,4 +71,4 @@ This is equivalent to running the following command, but under upstart: $ docker run --detach=true --publish='5000:5000' --env='SETTINGS_FLAVOR=local' --volume='/mnt/docker:/docker-storage' samalba/docker-registry The resources will accept a single string or an array of values for any -docker flags that allow multiple values. +Docker flags that allow multiple values. diff --git a/docs/sources/use/host_integration.md b/docs/sources/use/host_integration.md index 370c00e20..fa442620e 100644 --- a/docs/sources/use/host_integration.md +++ b/docs/sources/use/host_integration.md @@ -10,16 +10,15 @@ You can use your Docker containers with process managers like ## Introduction If you want a process manager to manage your containers you will need to -run the docker daemon with the `-r=false` so that -docker will not automatically restart your containers when the host is -restarted. +run the docker daemon with the `-r=false` so that docker will not +automatically restart your containers when the host is restarted. When you have finished setting up your image and are happy with your running container, you can then attach a process manager to manage it. -When your run `docker start -a` docker will -automatically attach to the running container, or start it if needed and -forward all signals so that the process manager can detect when a -container stops and correctly restart it. +When your run `docker start -a` docker will automatically attach to the +running container, or start it if needed and forward all signals so that +the process manager can detect when a container stops and correctly +restart it. Here are a few sample scripts for systemd and upstart to integrate with docker. @@ -27,9 +26,8 @@ docker. ## Sample Upstart Script In this example We've already created a container to run Redis with -`--name redis_server`. To create an upstart script -for our container, we create a file named -`/etc/init/redis.conf` and place the following into +`--name redis_server`. To create an upstart script for our container, we +create a file named `/etc/init/redis.conf` and place the following into it: description "Redis container" diff --git a/docs/sources/use/networking.md b/docs/sources/use/networking.md index 00d068425..db60fe843 100644 --- a/docs/sources/use/networking.md +++ b/docs/sources/use/networking.md @@ -7,13 +7,13 @@ page_keywords: network, networking, bridge, docker, documentation ## Introduction Docker uses Linux bridge capabilities to provide network connectivity to -containers. The `docker0` bridge interface is -managed by Docker for this purpose. When the Docker daemon starts it : +containers. The `docker0` bridge interface is managed by Docker for this +purpose. When the Docker daemon starts it: - - creates the `docker0` bridge if not present - - searches for an IP address range which doesn't overlap with an existing route - - picks an IP in the selected range - - assigns this IP to the `docker0` bridge + - Creates the `docker0` bridge if not present + - Searches for an IP address range which doesn't overlap with an existing route + - Picks an IP in the selected range + - Assigns this IP to the `docker0` bridge @@ -42,7 +42,7 @@ for the container. docker0 8000.fef213db5a66 no vethQCDY1N Above, `docker0` acts as a bridge for the `vethQCDY1N` interface which -is dedicated to the 52f811c5d3d6 container. +is dedicated to the `52f811c5d3d6` container. ## How to use a specific IP address range @@ -50,16 +50,15 @@ Docker will try hard to find an IP range that is not used by the host. Even though it works for most cases, it's not bullet-proof and sometimes you need to have more control over the IP addressing scheme. -For this purpose, Docker allows you to manage the `docker0` -bridge or your own one using the `-b=` -parameter. +For this purpose, Docker allows you to manage the `docker0` bridge or +your own one using the `-b=` parameter. In this scenario: - - ensure Docker is stopped - - create your own bridge (`bridge0` for example) - - assign a specific IP to this bridge - - start Docker with the `-b=bridge0` parameter + - Ensure Docker is stopped + - Create your own bridge (`bridge0` for example) + - Assign a specific IP to this bridge + - Start Docker with the `-b=bridge0` parameter @@ -107,29 +106,27 @@ In this scenario: ## Container intercommunication -The value of the Docker daemon's `icc` parameter -determines whether containers can communicate with each other over the -bridge network. +The value of the Docker daemon's `icc` parameter determines whether +containers can communicate with each other over the bridge network. - The default, `-icc=true` allows containers to communicate with each other. - `-icc=false` means containers are isolated from each other. -Docker uses `iptables` under the hood to either -accept or drop communication between containers. +Docker uses `iptables` under the hood to either accept or drop +communication between containers. ## What is the vethXXXX device? Well. Things get complicated here. -The `vethXXXX` interface is the host side of a -point-to-point link between the host and the corresponding container; -the other side of the link is the container's `eth0` -interface. This pair (host `vethXXX` and container -`eth0`) are connected like a tube. Everything that -comes in one side will come out the other side. +The `vethXXXX` interface is the host side of a point-to-point link +between the host and the corresponding container; the other side of the +link is the container's `eth0` interface. This pair (host `vethXXX` and +container `eth0`) are connected like a tube. Everything that comes in +one side will come out the other side. All the plumbing is delegated to Linux network capabilities (check the -ip link command) and the namespaces infrastructure. +`ip link` command) and the namespaces infrastructure. ## I want more diff --git a/docs/sources/use/port_redirection.md b/docs/sources/use/port_redirection.md index 19de4141b..315ef2650 100644 --- a/docs/sources/use/port_redirection.md +++ b/docs/sources/use/port_redirection.md @@ -29,10 +29,10 @@ containers, Docker provides the linking mechanism. ## Auto map all exposed ports on the host To bind all the exposed container ports to the host automatically, use -`docker run -P `. The mapped host ports -will be auto-selected from a pool of unused ports (49000..49900), and -you will need to use `docker ps`, `docker inspect ` or -`docker port ` to determine what they are. +`docker run -P `. The mapped host ports will be auto-selected +from a pool of unused ports (49000..49900), and you will need to use +`docker ps`, `docker inspect ` or `docker port + ` to determine what they are. ## Binding a port to a host interface @@ -65,9 +65,9 @@ combinations described for TCP work. Here is only one example: # Bind UDP port 5353 of the container to UDP port 53 on 127.0.0.1 of the host machine. $ docker run -p 127.0.0.1:53:5353/udp -The command `docker port` lists the interface and port on the host machine -bound to a given container port. It is useful when using dynamically allocated -ports: +The command `docker port` lists the interface and port on the host +machine bound to a given container port. It is useful when using +dynamically allocated ports: # Bind to a dynamically allocated port $ docker run -p 127.0.0.1::8080 --name dyn-bound @@ -79,24 +79,25 @@ ports: ## Linking a container Communication between two containers can also be established in a -docker-specific way called linking. +Docker-specific way called linking. -To briefly present the concept of linking, let us consider two containers: -`server`, containing the service, and `client`, accessing the service. Once -`server` is running, `client` is started and links to server. Linking sets -environment variables in `client` giving it some information about `server`. -In this sense, linking is a method of service discovery. +To briefly present the concept of linking, let us consider two +containers: `server`, containing the service, and `client`, accessing +the service. Once `server` is running, `client` is started and links to +server. Linking sets environment variables in `client` giving it some +information about `server`. In this sense, linking is a method of +service discovery. -Let us now get back to our topic of interest; communication between the two -containers. We mentioned that the tricky part about this communication was that -the IP address of `server` was not fixed. Therefore, some of the environment -variables are going to be used to inform `client` about this IP address. This -process called exposure, is possible because `client` is started after `server` -has been started. +Let us now get back to our topic of interest; communication between the +two containers. We mentioned that the tricky part about this +communication was that the IP address of `server` was not fixed. +Therefore, some of the environment variables are going to be used to +inform `client` about this IP address. This process called exposure, is +possible because the `client` is started after the `server` has been started. -Here is a full example. On `server`, the port of interest is exposed. The -exposure is done either through the `--expose` parameter to the `docker run` -command, or the `EXPOSE` build command in a Dockerfile: +Here is a full example. On `server`, the port of interest is exposed. +The exposure is done either through the `--expose` parameter to the +`docker run` command, or the `EXPOSE` build command in a `Dockerfile`: # Expose port 80 $ docker run --expose 80 --name server @@ -106,7 +107,7 @@ The `client` then links to the `server`: # Link $ docker run --name client --link server:linked-server -`client` locally refers to `server` as `linked-server`. The following +Here `client` locally refers to `server` as `linked-server`. The following environment variables, among others, are available on `client`: # The default protocol, ip, and port of the service running in the container @@ -118,7 +119,9 @@ environment variables, among others, are available on `client`: $ LINKED-SERVER_PORT_80_TCP_ADDR=172.17.0.8 $ LINKED-SERVER_PORT_80_TCP_PORT=80 -This tells `client` that a service is running on port 80 of `server` and that -`server` is accessible at the IP address 172.17.0.8 +This tells `client` that a service is running on port 80 of `server` and +that `server` is accessible at the IP address `172.17.0.8`: + +> **Note:** +> Using the `-p` parameter also exposes the port. -Note: Using the `-p` parameter also exposes the port. diff --git a/docs/sources/use/puppet.md b/docs/sources/use/puppet.md index a0d20ab44..81ae05ba5 100644 --- a/docs/sources/use/puppet.md +++ b/docs/sources/use/puppet.md @@ -12,7 +12,7 @@ page_keywords: puppet, installation, usage, docker, documentation ## Requirements To use this guide you'll need a working installation of Puppet from -[Puppetlabs](https://puppetlabs.com) . +[Puppet Labs](https://puppetlabs.com) . The module also currently uses the official PPA so only works with Ubuntu. @@ -26,8 +26,8 @@ installed using the built-in module tool. $ puppet module install garethr/docker It can also be found on -[GitHub](https://github.com/garethr/garethr-docker) if you would -rather download the source. +[GitHub](https://github.com/garethr/garethr-docker) if you would rather +download the source. ## Usage @@ -88,5 +88,6 @@ Run also contains a number of optional parameters: dns => ['8.8.8.8', '8.8.4.4'], } -Note that ports, env, dns and volumes can be set with either a single -string or as above with an array of values. +> *Note:* +> The `ports`, `env`, `dns` and `volumes` attributes can be set with either a single +> string or as above with an array of values. diff --git a/docs/sources/use/working_with_links_names.md b/docs/sources/use/working_with_links_names.md index 6951e3c26..ea3a25ab7 100644 --- a/docs/sources/use/working_with_links_names.md +++ b/docs/sources/use/working_with_links_names.md @@ -6,18 +6,16 @@ page_keywords: Examples, Usage, links, linking, docker, documentation, examples, ## Introduction -From version 0.6.5 you are now able to `name` a container and `link` it to -another container by referring to its name. This will create a parent -> child -relationship where the parent container can see selected information about its -child. +From version 0.6.5 you are now able to `name` a container and `link` it +to another container by referring to its name. This will create a parent +-> child relationship where the parent container can see selected +information about its child. ## Container Naming -New in version v0.6.5. - -You can now name your container by using the `--name` flag. If no name is -provided, Docker will automatically generate a name. You can see this name -using the `docker ps` command. +You can now name your container by using the `--name` flag. If no name +is provided, Docker will automatically generate a name. You can see this +name using the `docker ps` command. # format is "sudo docker run --name " $ sudo docker run --name test ubuntu /bin/bash @@ -29,48 +27,49 @@ using the `docker ps` command. ## Links: service discovery for docker -New in version v0.6.5. - Links allow containers to discover and securely communicate with each -other by using the flag `-link name:alias`. Inter-container communication -can be disabled with the daemon flag `-icc=false`. With this flag set to -`false`, Container A cannot access Container unless explicitly allowed via -a link. This is a huge win for securing your containers. When two containers -are linked together Docker creates a parent child relationship between the -containers. The parent container will be able to access information via -environment variables of the child such as name, exposed ports, IP and other -selected environment variables. +other by using the flag `-link name:alias`. Inter-container +communication can be disabled with the daemon flag `-icc=false`. With +this flag set to `false`, Container A cannot access Container unless +explicitly allowed via a link. This is a huge win for securing your +containers. When two containers are linked together Docker creates a +parent child relationship between the containers. The parent container +will be able to access information via environment variables of the +child such as name, exposed ports, IP and other selected environment +variables. -When linking two containers Docker will use the exposed ports of the container -to create a secure tunnel for the parent to access. If a database container -only exposes port 8080 then the linked container will only be allowed to access -port 8080 and nothing else if inter-container communication is set to false. +When linking two containers Docker will use the exposed ports of the +container to create a secure tunnel for the parent to access. If a +database container only exposes port 8080 then the linked container will +only be allowed to access port 8080 and nothing else if inter-container +communication is set to false. -For example, there is an image called `crosbymichael/redis` that exposes the -port 6379 and starts the Redis server. Let's name the container as `redis` -based on that image and run it as daemon. +For example, there is an image called `crosbymichael/redis` that exposes +the port 6379 and starts the Redis server. Let's name the container as +`redis` based on that image and run it as daemon. $ sudo docker run -d --name redis crosbymichael/redis -We can issue all the commands that you would expect using the name `redis`; -start, stop, attach, using the name for our container. The name also allows -us to link other containers into this one. +We can issue all the commands that you would expect using the name +`redis`; start, stop, attach, using the name for our container. The name +also allows us to link other containers into this one. -Next, we can start a new web application that has a dependency on Redis and -apply a link to connect both containers. If you noticed when running our Redis -server we did not use the `-p` flag to publish the Redis port to the host -system. Redis exposed port 6379 and this is all we need to establish a link. +Next, we can start a new web application that has a dependency on Redis +and apply a link to connect both containers. If you noticed when running +our Redis server we did not use the `-p` flag to publish the Redis port +to the host system. Redis exposed port 6379 and this is all we need to +establish a link. $ sudo docker run -t -i --link redis:db --name webapp ubuntu bash When you specified `--link redis:db` you are telling Docker to link the container named `redis` into this new container with the alias `db`. -Environment variables are prefixed with the alias so that the parent container -can access network and environment information from the containers that are -linked into it. +Environment variables are prefixed with the alias so that the parent +container can access network and environment information from the +containers that are linked into it. -If we inspect the environment variables of the second container, we would see -all the information about the child container. +If we inspect the environment variables of the second container, we +would see all the information about the child container. $ root@4c01db0b339c:/# env @@ -90,20 +89,20 @@ all the information about the child container. _=/usr/bin/env root@4c01db0b339c:/# -Accessing the network information along with the environment of the child -container allows us to easily connect to the Redis service on the specific -IP and port in the environment. +Accessing the network information along with the environment of the +child container allows us to easily connect to the Redis service on the +specific IP and port in the environment. > **Note**: > These Environment variables are only set for the first process in the > container. Similarly, some daemons (such as `sshd`) > will scrub them when spawning shells for connection. -You can work around this by storing the initial `env` in a file, or looking -at `/proc/1/environ`. +You can work around this by storing the initial `env` in a file, or +looking at `/proc/1/environ`. -Running `docker ps` shows the 2 containers, and the `webapp/db` alias name for -the Redis container. +Running `docker ps` shows the 2 containers, and the `webapp/db` alias +name for the Redis container. $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES @@ -112,13 +111,13 @@ the Redis container. ## Resolving Links by Name -New in version v0.11. +> *Note:* New in version v0.11. Linked containers can be accessed by hostname. Hostnames are mapped by appending entries to '/etc/hosts' using the linked container's alias. -For example, linking a container using '--link redis:db' will generate the -following '/etc/hosts' file: +For example, linking a container using '--link redis:db' will generate +the following '/etc/hosts' file: root@6541a75d44a0:/# cat /etc/hosts 172.17.0.3 6541a75d44a0 diff --git a/docs/sources/use/working_with_volumes.md b/docs/sources/use/working_with_volumes.md index 065b867c6..f6d1db04f 100644 --- a/docs/sources/use/working_with_volumes.md +++ b/docs/sources/use/working_with_volumes.md @@ -8,8 +8,8 @@ page_keywords: Examples, Usage, volume, docker, documentation, examples A *data volume* is a specially-designated directory within one or more containers that bypasses the [*Union File -System*](/terms/layer/#ufs-def) to provide several useful features -for persistent or shared data: +System*](/terms/layer/#ufs-def) to provide several useful features for +persistent or shared data: - **Data volumes can be shared and reused between containers:** This is the feature that makes data volumes so powerful. You can @@ -28,26 +28,22 @@ for persistent or shared data: Each container can have zero or more data volumes. -New in version v0.3.0. - ## Getting Started -Using data volumes is as simple as adding a `-v` -parameter to the `docker run` command. The -`-v` parameter can be used more than once in order -to create more volumes within the new container. To create a new +Using data volumes is as simple as adding a `-v` parameter to the +`docker run` command. The `-v` parameter can be used more than once in +order to create more volumes within the new container. To create a new container with two new volumes: $ docker run -v /var/volume1 -v /var/volume2 busybox true This command will create the new container with two new volumes that -exits instantly (`true` is pretty much the smallest, -simplest program that you can run). You can then mount its -volumes in any other container using the `run` `--volumes-from` -option; irrespective of whether the volume container is running or -not. +exits instantly (`true` is pretty much the smallest, simplest program +that you can run). You can then mount its volumes in any other container +using the `run` `--volumes-from` option; irrespective of whether the +volume container is running or not. -Or, you can use the VOLUME instruction in a Dockerfile to add one or +Or, you can use the `VOLUME` instruction in a `Dockerfile` to add one or more new volumes to any container created from that image: # BUILD-USING: $ docker build -t data . @@ -63,8 +59,8 @@ containers, or want to use from non-persistent containers, it's best to create a named Data Volume Container, and then to mount the data from it. -Create a named container with volumes to share (`/var/volume1` -and `/var/volume2`): +Create a named container with volumes to share (`/var/volume1` and +`/var/volume2`): $ docker run -v /var/volume1 -v /var/volume2 -name DATA busybox true @@ -72,12 +68,12 @@ Then mount those data volumes into your application containers: $ docker run -t -i -rm -volumes-from DATA -name client1 ubuntu bash -You can use multiple `-volumes-from` parameters to -bring together multiple data volumes from multiple containers. +You can use multiple `-volumes-from` parameters to bring together +multiple data volumes from multiple containers. -Interestingly, you can mount the volumes that came from the -`DATA` container in yet another container via the -`client1` middleman container: +Interestingly, you can mount the volumes that came from the `DATA` +container in yet another container via the `client1` middleman +container: $ docker run -t -i -rm -volumes-from client1 -name client2 ubuntu bash @@ -94,14 +90,15 @@ upgrade, or effectively migrate data volumes between containers. -v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. -You must specify an absolute path for `host-dir`. If `host-dir` is missing from -the command, then Docker creates a new volume. If `host-dir` is present but -points to a non-existent directory on the host, Docker will automatically -create this directory and use it as the source of the bind-mount. +You must specify an absolute path for `host-dir`. If `host-dir` is +missing from the command, then Docker creates a new volume. If +`host-dir` is present but points to a non-existent directory on the +host, Docker will automatically create this directory and use it as the +source of the bind-mount. -Note that this is not available from a Dockerfile due the portability and -sharing purpose of it. The `host-dir` volumes are entirely host-dependent -and might not work on any other machine. +Note that this is not available from a `Dockerfile` due the portability +and sharing purpose of it. The `host-dir` volumes are entirely +host-dependent and might not work on any other machine. For example: @@ -110,26 +107,28 @@ For example: # Example: $ sudo docker run -i -t -v /var/log:/logs_from_host:ro ubuntu bash -The command above mounts the host directory `/var/log` into the container -with *read only* permissions as `/logs_from_host`. +The command above mounts the host directory `/var/log` into the +container with *read only* permissions as `/logs_from_host`. New in version v0.5.0. ### Note for OS/X users and remote daemon users: -OS/X users run `boot2docker` to create a minimalist virtual machine running -the docker daemon. That virtual machine then launches docker commands on -behalf of the OS/X command line. The means that `host directories` refer to -directories in the `boot2docker` virtual machine, not the OS/X filesystem. +OS/X users run `boot2docker` to create a minimalist virtual machine +running the docker daemon. That virtual machine then launches docker +commands on behalf of the OS/X command line. The means that `host +directories` refer to directories in the `boot2docker` virtual machine, +not the OS/X filesystem. -Similarly, anytime when the docker daemon is on a remote machine, the +Similarly, anytime when the docker daemon is on a remote machine, the `host directories` always refer to directories on the daemon's machine. ### Backup, restore, or migrate data volumes -You cannot back up volumes using `docker export`, `docker save` and `docker cp` -because they are external to images. Instead you can use `--volumes-from` to -start a new container that can access the data-container's volume. For example: +You cannot back up volumes using `docker export`, `docker save` and +`docker cp` because they are external to images. Instead you can use +`--volumes-from` to start a new container that can access the +data-container's volume. For example: $ sudo docker run -rm --volumes-from DATA -v $(pwd):/backup busybox tar cvf /backup/backup.tar /data @@ -144,7 +143,8 @@ start a new container that can access the data-container's volume. For example: - `tar cvf /backup/backup.tar /data`: creates an uncompressed tar file of all the files in the `/data` directory -Then to restore to the same container, or another that you`ve made elsewhere: +Then to restore to the same container, or another that you've made +elsewhere: # create a new data container $ sudo docker run -v /data -name DATA2 busybox true diff --git a/docs/sources/use/workingwithrepository.md b/docs/sources/use/workingwithrepository.md index d92d057d5..88c3f6c44 100644 --- a/docs/sources/use/workingwithrepository.md +++ b/docs/sources/use/workingwithrepository.md @@ -18,15 +18,14 @@ You can find one or more repositories hosted on a *registry*. There are two types of *registry*: public and private. There's also a default *registry* that Docker uses which is called [Docker.io](http://index.docker.io). -[Docker.io](http://index.docker.io) is the home of -"top-level" repositories and public "user" repositories. The Docker -project provides [Docker.io](http://index.docker.io) to host public and -[private repositories](https://index.docker.io/plans/), namespaced by -user. We provide user authentication and search over all the public -repositories. +[Docker.io](http://index.docker.io) is the home of "top-level" +repositories and public "user" repositories. The Docker project +provides [Docker.io](http://index.docker.io) to host public and [private +repositories](https://index.docker.io/plans/), namespaced by user. We +provide user authentication and search over all the public repositories. -Docker acts as a client for these services via the `docker search`, `pull`, -`login` and `push` commands. +Docker acts as a client for these services via the `docker search`, +`pull`, `login` and `push` commands. ## Repositories @@ -42,8 +41,8 @@ There are two types of public repositories: *top-level* repositories which are controlled by the Docker team, and *user* repositories created by individual contributors. Anyone can read from these repositories – they really help people get started quickly! You could also use -[*Trusted Builds*](#trusted-builds) if you need to keep -control of who accesses your images. +[*Trusted Builds*](#trusted-builds) if you need to keep control of who +accesses your images. - Top-level repositories can easily be recognized by **not** having a `/` (slash) in their name. These repositories represent trusted images @@ -83,12 +82,11 @@ user name or description: ... There you can see two example results: `centos` and -`slantview/centos-chef-solo`. The second result -shows that it comes from the public repository of a user, -`slantview/`, while the first result -(`centos`) doesn't explicitly list a repository so -it comes from the trusted top-level namespace. The `/` -character separates a user's repository and the image name. +`slantview/centos-chef-solo`. The second result shows that it comes from +the public repository of a user, `slantview/`, while the first result +(`centos`) doesn't explicitly list a repository so it comes from the +trusted top-level namespace. The `/` character separates a user's +repository and the image name. Once you have found the image name, you can download it: @@ -98,8 +96,8 @@ Once you have found the image name, you can download it: 539c0211cd76: Download complete What can you do with that image? Check out the -[*Examples*](/examples/#example-list) and, when you're ready with -your own image, come back here to learn how to share it. +[*Examples*](/examples/#example-list) and, when you're ready with your +own image, come back here to learn how to share it. ## Contributing to Docker.io @@ -114,10 +112,9 @@ first. You can create your username and login on This will prompt you for a username, which will become a public namespace for your public repositories. -If your username is available then `docker` will -also prompt you to enter a password and your e-mail address. It will -then automatically log you in. Now you're ready to commit and push your -own images! +If your username is available then `docker` will also prompt you to +enter a password and your e-mail address. It will then automatically log +you in. Now you're ready to commit and push your own images! > **Note:** > Your authentication credentials will be stored in the [`.dockercfg` @@ -150,9 +147,9 @@ or tag. ## Trusted Builds Trusted Builds automate the building and updating of images from GitHub, -directly on `docker.io` servers. It works by adding -a commit hook to your selected repository, triggering a build and update -when you push a commit. +directly on Docker.io. It works by adding a commit hook to +your selected repository, triggering a build and update when you push a +commit. ### To setup a trusted build @@ -206,9 +203,10 @@ identify a host), like this: Once a repository has your registry's host name as part of the tag, you can push and pull it like any other repository, but it will **not** be -searchable (or indexed at all) on [Docker.io](http://index.docker.io), and there will be -no user name checking performed. Your registry will function completely -independently from the [Docker.io](http://index.docker.io) registry. +searchable (or indexed at all) on [Docker.io](http://index.docker.io), +and there will be no user name checking performed. Your registry will +function completely independently from the +[Docker.io](http://index.docker.io) registry. @@ -219,15 +217,20 @@ http://blog.docker.io/2013/07/how-to-use-your-own-registry/) ## Authentication File -The authentication is stored in a json file, `.dockercfg` -located in your home directory. It supports multiple registry -urls. +The authentication is stored in a JSON file, `.dockercfg`, located in +your home directory. It supports multiple registry URLs. -`docker login` will create the "[https://index.docker.io/v1/]( -https://index.docker.io/v1/)" key. +The `docker login` command will create the: -`docker login https://my-registry.com` will create the -"[https://my-registry.com](https://my-registry.com)" key. + [https://index.docker.io/v1/](https://index.docker.io/v1/) + +key. + +The `docker login https://my-registry.com` command will create the: + + [https://my-registry.com](https://my-registry.com) + +key. For example: @@ -243,4 +246,6 @@ For example: } The `auth` field represents -`base64(:)` + + base64(:) + From ed703fc8be652eee65319a774ffbe95a07a03242 Mon Sep 17 00:00:00 2001 From: SvenDowideit Date: Tue, 13 May 2014 13:38:11 +1000 Subject: [PATCH 104/400] Add some more specific help for Dockerfile build contexts. I've copy and pasted the error message so its googlable. Docker-DCO-1.1-Signed-off-by: SvenDowideit (github: SvenDowideit) --- docs/sources/reference/commandline/cli.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 8e0507cbf..c928b03eb 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -201,7 +201,8 @@ and a "context". The files at `PATH` or `URL` are called the "context" of the build. The build process may refer to any of the files in the context, for example when using an [*ADD*](/reference/builder/#dockerfile-add) instruction. When a single Dockerfile is -given as `URL`, then no context is set. +given as `URL` or is piped through STDIN (`docker build - < Dockerfile`), then +no context is set. When a Git repository is set as `URL`, then the repository is used as the context. The Git repository is cloned with its @@ -283,6 +284,13 @@ repository is used as Dockerfile. Note that you can specify an arbitrary Git repository by using the `git://` schema. +> **Note:** `docker build` will return a `no such file or directory` error +> if the file or directory does not exist in the uploaded context. This may +> happen if there is no context, or if you specify a file that is elsewhere +> on the Host system. The context is limited to the current directory (and its +> children) for security reasons, and to ensure repeatable builds on remote +> Docker hosts. This is also the reason why `ADD ../file` will not work. + ## commit Create a new image from a container᾿s changes From 002aa8fc207d803349777cde61426603976ca8ee Mon Sep 17 00:00:00 2001 From: Bernerd Schaefer Date: Wed, 14 May 2014 11:48:10 +0200 Subject: [PATCH 105/400] Add GetParentDeathSignal() to pkg/system Docker-DCO-1.1-Signed-off-by: Bernerd Schaefer (github: bernerdschaefer) --- pkg/system/calls_linux.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pkg/system/calls_linux.go b/pkg/system/calls_linux.go index cc4727aaa..faead0114 100644 --- a/pkg/system/calls_linux.go +++ b/pkg/system/calls_linux.go @@ -3,6 +3,7 @@ package system import ( "os/exec" "syscall" + "unsafe" ) func Chroot(dir string) error { @@ -122,6 +123,18 @@ func ParentDeathSignal(sig uintptr) error { return nil } +func GetParentDeathSignal() (int, error) { + var sig int + + _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_GET_PDEATHSIG, uintptr(unsafe.Pointer(&sig)), 0) + + if err != 0 { + return -1, err + } + + return sig, nil +} + func Setctty() error { if _, _, err := syscall.RawSyscall(syscall.SYS_IOCTL, 0, uintptr(syscall.TIOCSCTTY), 0); err != 0 { return err From 239d1ef301eb8d9b2b1e8469489b25ff3708f47f Mon Sep 17 00:00:00 2001 From: unclejack Date: Thu, 15 May 2014 11:56:23 +0300 Subject: [PATCH 106/400] docs: explain when RUN cache gets invalidated This adds a few lines to the RUN Dockerfile docs to explain how to bypass the RUN caching and how ADD can also invalidate the cache for these instructions. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- docs/sources/reference/builder.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index 4c726aafa..c3ba93955 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -131,6 +131,16 @@ any point in an image's history, much like source control. The *exec* form makes it possible to avoid shell string munging, and to `RUN` commands using a base image that does not contain `/bin/sh`. +The cache for `RUN` instructions isn't invalidated automatically during the +next build. The cache for an instruction like `RUN apt-get dist-upgrade -y` +will be reused during the next build. +The cache for `RUN` instructions can be invalidated by using the `--no-cache` +flag, for example `docker build --no-cache`. + +The first encountered `ADD` instruction will invalidate the cache for all +following instructions from the 'Dockerfile' if the contents of the context +have changed. This will also invalidate the cache for `RUN` instructions. + ### Known Issues (RUN) - [Issue 783](https://github.com/dotcloud/docker/issues/783) is about file From 10a320818e875547c83fecea32cd73231248acf8 Mon Sep 17 00:00:00 2001 From: Joel Handwell Date: Thu, 15 May 2014 10:50:59 -0400 Subject: [PATCH 107/400] changed deprecated option styles to supported ones -name and -rm to --name and --rm --- docs/sources/reference/run.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index a0be7c34d..7587c70ba 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -54,10 +54,10 @@ following options. - [Detached (-d)](#detached-d) - [Foreground](#foreground) - [Container Identification](#container-identification) - - [Name (–name)](#name-name) + - [Name (--name)](#name-name) - [PID Equivalent](#pid-equivalent) - [Network Settings](#network-settings) - - [Clean Up (–rm)](#clean-up-rm) + - [Clean Up (--rm)](#clean-up-rm) - [Runtime Constraints on CPU and Memory](#runtime-constraints-on-cpu-and-memory) - [Runtime Privilege and LXC From 77c2b76e44f77eee2ca89ff551ac64f8c6b8bd23 Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Fri, 9 May 2014 15:49:02 -0700 Subject: [PATCH 108/400] archive: use bufio for compression detection Docker-DCO-1.1-Signed-off-by: Johan Euphrosine (github: proppy) --- archive/archive.go | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/archive/archive.go b/archive/archive.go index e21b10ca0..078aa6d35 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -1,14 +1,11 @@ package archive import ( - "bytes" + "bufio" "compress/bzip2" "compress/gzip" "errors" "fmt" - "github.com/dotcloud/docker/pkg/system" - "github.com/dotcloud/docker/utils" - "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" "io" "io/ioutil" "os" @@ -17,6 +14,10 @@ import ( "path/filepath" "strings" "syscall" + + "github.com/dotcloud/docker/pkg/system" + "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) type ( @@ -74,31 +75,24 @@ func xzDecompress(archive io.Reader) (io.ReadCloser, error) { } func DecompressStream(archive io.Reader) (io.ReadCloser, error) { - buf := make([]byte, 10) - totalN := 0 - for totalN < 10 { - n, err := archive.Read(buf[totalN:]) - if err != nil { - if err == io.EOF { - return nil, fmt.Errorf("Tarball too short") - } - return nil, err - } - totalN += n - utils.Debugf("[tar autodetect] n: %d", n) + buf := bufio.NewReader(archive) + bs, err := buf.Peek(10) + if err != nil { + return nil, err } - compression := DetectCompression(buf) - wrap := io.MultiReader(bytes.NewReader(buf), archive) + utils.Debugf("[tar autodetect] n: %v", bs) + + compression := DetectCompression(bs) switch compression { case Uncompressed: - return ioutil.NopCloser(wrap), nil + return ioutil.NopCloser(buf), nil case Gzip: - return gzip.NewReader(wrap) + return gzip.NewReader(buf) case Bzip2: - return ioutil.NopCloser(bzip2.NewReader(wrap)), nil + return ioutil.NopCloser(bzip2.NewReader(buf)), nil case Xz: - return xzDecompress(wrap) + return xzDecompress(buf) default: return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension()) } From 8ec01f9dc1d18c532f60f1bfb6c0ab5eea378b3c Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 15 May 2014 16:21:17 -0600 Subject: [PATCH 109/400] Add "cross" to the "test" Makefile target Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a4c8658e0..a8e4dc5ca 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ docs-release: docs-build $(DOCKER_RUN_DOCS) "$(DOCKER_DOCS_IMAGE)" ./release.sh test: build - $(DOCKER_RUN_DOCKER) hack/make.sh binary test-unit test-integration test-integration-cli + $(DOCKER_RUN_DOCKER) hack/make.sh binary cross test-unit test-integration test-integration-cli test-unit: build $(DOCKER_RUN_DOCKER) hack/make.sh test-unit From 094926206bc98718f2eac95849e57439155d7c6c Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 15 May 2014 08:18:56 -0700 Subject: [PATCH 110/400] Update archive/tar vendored Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- hack/vendor.sh | 2 +- .../p/go/src/pkg/archive/tar/common.go | 1 + .../p/go/src/pkg/archive/tar/reader.go | 457 +++++++++++++++++- .../p/go/src/pkg/archive/tar/reader_test.go | 320 +++++++++++- .../archive/tar/testdata/sparse-formats.tar | Bin 0 -> 17920 bytes .../archive/tar/testdata/writer-big-long.tar | Bin 0 -> 4096 bytes .../p/go/src/pkg/archive/tar/writer.go | 4 +- .../p/go/src/pkg/archive/tar/writer_test.go | 23 + 8 files changed, 782 insertions(+), 25 deletions(-) create mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/sparse-formats.tar create mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big-long.tar diff --git a/hack/vendor.sh b/hack/vendor.sh index 79322cd9a..0bec5838f 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -53,7 +53,7 @@ clone hg code.google.com/p/gosqlite 74691fb6f837 # get Go tip's archive/tar, for xattr support # TODO after Go 1.3 drops, bump our minimum supported version and drop this vendored dep -clone hg code.google.com/p/go a15f344a9efa +clone hg code.google.com/p/go 3458ba248590 mv src/code.google.com/p/go/src/pkg/archive/tar tmp-tar rm -rf src/code.google.com/p/go mkdir -p src/code.google.com/p/go/src/pkg/archive diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/common.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/common.go index e8b973c1f..e363aa793 100644 --- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/common.go +++ b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/common.go @@ -38,6 +38,7 @@ const ( TypeXGlobalHeader = 'g' // global extended header TypeGNULongName = 'L' // Next file has a long name TypeGNULongLink = 'K' // Next file symlinks to a file w/ a long name + TypeGNUSparse = 'S' // sparse file ) // A Header represents a single header in a tar archive. diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader.go index 7cb6e649c..920a9b08f 100644 --- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader.go +++ b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader.go @@ -29,12 +29,57 @@ const maxNanoSecondIntSize = 9 // The Next method advances to the next file in the archive (including the first), // and then it can be treated as an io.Reader to access the file's data. type Reader struct { - r io.Reader - err error - nb int64 // number of unread bytes for current file entry - pad int64 // amount of padding (ignored) after current file entry + r io.Reader + err error + pad int64 // amount of padding (ignored) after current file entry + curr numBytesReader // reader for current file entry } +// A numBytesReader is an io.Reader with a numBytes method, returning the number +// of bytes remaining in the underlying encoded data. +type numBytesReader interface { + io.Reader + numBytes() int64 +} + +// A regFileReader is a numBytesReader for reading file data from a tar archive. +type regFileReader struct { + r io.Reader // underlying reader + nb int64 // number of unread bytes for current file entry +} + +// A sparseFileReader is a numBytesReader for reading sparse file data from a tar archive. +type sparseFileReader struct { + rfr *regFileReader // reads the sparse-encoded file data + sp []sparseEntry // the sparse map for the file + pos int64 // keeps track of file position + tot int64 // total size of the file +} + +// Keywords for GNU sparse files in a PAX extended header +const ( + paxGNUSparseNumBlocks = "GNU.sparse.numblocks" + paxGNUSparseOffset = "GNU.sparse.offset" + paxGNUSparseNumBytes = "GNU.sparse.numbytes" + paxGNUSparseMap = "GNU.sparse.map" + paxGNUSparseName = "GNU.sparse.name" + paxGNUSparseMajor = "GNU.sparse.major" + paxGNUSparseMinor = "GNU.sparse.minor" + paxGNUSparseSize = "GNU.sparse.size" + paxGNUSparseRealSize = "GNU.sparse.realsize" +) + +// Keywords for old GNU sparse headers +const ( + oldGNUSparseMainHeaderOffset = 386 + oldGNUSparseMainHeaderIsExtendedOffset = 482 + oldGNUSparseMainHeaderNumEntries = 4 + oldGNUSparseExtendedHeaderIsExtendedOffset = 504 + oldGNUSparseExtendedHeaderNumEntries = 21 + oldGNUSparseOffsetSize = 12 + oldGNUSparseNumBytesSize = 12 +) + // NewReader creates a new Reader reading from r. func NewReader(r io.Reader) *Reader { return &Reader{r: r} } @@ -64,6 +109,18 @@ func (tr *Reader) Next() (*Header, error) { tr.skipUnread() hdr = tr.readHeader() mergePAX(hdr, headers) + + // Check for a PAX format sparse file + sp, err := tr.checkForGNUSparsePAXHeaders(hdr, headers) + if err != nil { + tr.err = err + return nil, err + } + if sp != nil { + // Current file is a PAX format GNU sparse file. + // Set the current file reader to a sparse file reader. + tr.curr = &sparseFileReader{rfr: tr.curr.(*regFileReader), sp: sp, tot: hdr.Size} + } return hdr, nil case TypeGNULongName: // We have a GNU long name header. Its contents are the real file name. @@ -87,6 +144,67 @@ func (tr *Reader) Next() (*Header, error) { return hdr, tr.err } +// checkForGNUSparsePAXHeaders checks the PAX headers for GNU sparse headers. If they are found, then +// this function reads the sparse map and returns it. Unknown sparse formats are ignored, causing the file to +// be treated as a regular file. +func (tr *Reader) checkForGNUSparsePAXHeaders(hdr *Header, headers map[string]string) ([]sparseEntry, error) { + var sparseFormat string + + // Check for sparse format indicators + major, majorOk := headers[paxGNUSparseMajor] + minor, minorOk := headers[paxGNUSparseMinor] + sparseName, sparseNameOk := headers[paxGNUSparseName] + _, sparseMapOk := headers[paxGNUSparseMap] + sparseSize, sparseSizeOk := headers[paxGNUSparseSize] + sparseRealSize, sparseRealSizeOk := headers[paxGNUSparseRealSize] + + // Identify which, if any, sparse format applies from which PAX headers are set + if majorOk && minorOk { + sparseFormat = major + "." + minor + } else if sparseNameOk && sparseMapOk { + sparseFormat = "0.1" + } else if sparseSizeOk { + sparseFormat = "0.0" + } else { + // Not a PAX format GNU sparse file. + return nil, nil + } + + // Check for unknown sparse format + if sparseFormat != "0.0" && sparseFormat != "0.1" && sparseFormat != "1.0" { + return nil, nil + } + + // Update hdr from GNU sparse PAX headers + if sparseNameOk { + hdr.Name = sparseName + } + if sparseSizeOk { + realSize, err := strconv.ParseInt(sparseSize, 10, 0) + if err != nil { + return nil, ErrHeader + } + hdr.Size = realSize + } else if sparseRealSizeOk { + realSize, err := strconv.ParseInt(sparseRealSize, 10, 0) + if err != nil { + return nil, ErrHeader + } + hdr.Size = realSize + } + + // Set up the sparse map, according to the particular sparse format in use + var sp []sparseEntry + var err error + switch sparseFormat { + case "0.0", "0.1": + sp, err = readGNUSparseMap0x1(headers) + case "1.0": + sp, err = readGNUSparseMap1x0(tr.curr) + } + return sp, err +} + // mergePAX merges well known headers according to PAX standard. // In general headers with the same name as those found // in the header struct overwrite those found in the header @@ -194,6 +312,11 @@ func parsePAX(r io.Reader) (map[string]string, error) { if err != nil { return nil, err } + + // For GNU PAX sparse format 0.0 support. + // This function transforms the sparse format 0.0 headers into sparse format 0.1 headers. + var sparseMap bytes.Buffer + headers := make(map[string]string) // Each record is constructed as // "%d %s=%s\n", length, keyword, value @@ -211,7 +334,7 @@ func parsePAX(r io.Reader) (map[string]string, error) { return nil, ErrHeader } // Extract everything between the decimal and the n -1 on the - // beginning to to eat the ' ', -1 on the end to skip the newline. + // beginning to eat the ' ', -1 on the end to skip the newline. var record []byte record, buf = buf[sp+1:n-1], buf[n:] // The first equals is guaranteed to mark the end of the key. @@ -221,7 +344,21 @@ func parsePAX(r io.Reader) (map[string]string, error) { return nil, ErrHeader } key, value := record[:eq], record[eq+1:] - headers[string(key)] = string(value) + + keyStr := string(key) + if keyStr == paxGNUSparseOffset || keyStr == paxGNUSparseNumBytes { + // GNU sparse format 0.0 special key. Write to sparseMap instead of using the headers map. + sparseMap.Write(value) + sparseMap.Write([]byte{','}) + } else { + // Normal key. Set the value in the headers map. + headers[keyStr] = string(value) + } + } + if sparseMap.Len() != 0 { + // Add sparse info to headers, chopping off the extra comma + sparseMap.Truncate(sparseMap.Len() - 1) + headers[paxGNUSparseMap] = sparseMap.String() } return headers, nil } @@ -268,8 +405,8 @@ func (tr *Reader) octal(b []byte) int64 { // skipUnread skips any unread bytes in the existing file entry, as well as any alignment padding. func (tr *Reader) skipUnread() { - nr := tr.nb + tr.pad // number of bytes to skip - tr.nb, tr.pad = 0, 0 + nr := tr.numBytes() + tr.pad // number of bytes to skip + tr.curr, tr.pad = nil, 0 if sr, ok := tr.r.(io.Seeker); ok { if _, err := sr.Seek(nr, os.SEEK_CUR); err == nil { return @@ -331,14 +468,14 @@ func (tr *Reader) readHeader() *Header { // so its magic bytes, like the rest of the block, are NULs. magic := string(s.next(8)) // contains version field as well. var format string - switch magic { - case "ustar\x0000": // POSIX tar (1003.1-1988) + switch { + case magic[:6] == "ustar\x00": // POSIX tar (1003.1-1988) if string(header[508:512]) == "tar\x00" { format = "star" } else { format = "posix" } - case "ustar \x00": // old GNU tar + case magic == "ustar \x00": // old GNU tar format = "gnu" } @@ -373,30 +510,308 @@ func (tr *Reader) readHeader() *Header { // Maximum value of hdr.Size is 64 GB (12 octal digits), // so there's no risk of int64 overflowing. - tr.nb = int64(hdr.Size) - tr.pad = -tr.nb & (blockSize - 1) // blockSize is a power of two + nb := int64(hdr.Size) + tr.pad = -nb & (blockSize - 1) // blockSize is a power of two + + // Set the current file reader. + tr.curr = ®FileReader{r: tr.r, nb: nb} + + // Check for old GNU sparse format entry. + if hdr.Typeflag == TypeGNUSparse { + // Get the real size of the file. + hdr.Size = tr.octal(header[483:495]) + + // Read the sparse map. + sp := tr.readOldGNUSparseMap(header) + if tr.err != nil { + return nil + } + // Current file is a GNU sparse file. Update the current file reader. + tr.curr = &sparseFileReader{rfr: tr.curr.(*regFileReader), sp: sp, tot: hdr.Size} + } return hdr } +// A sparseEntry holds a single entry in a sparse file's sparse map. +// A sparse entry indicates the offset and size in a sparse file of a +// block of data. +type sparseEntry struct { + offset int64 + numBytes int64 +} + +// readOldGNUSparseMap reads the sparse map as stored in the old GNU sparse format. +// The sparse map is stored in the tar header if it's small enough. If it's larger than four entries, +// then one or more extension headers are used to store the rest of the sparse map. +func (tr *Reader) readOldGNUSparseMap(header []byte) []sparseEntry { + isExtended := header[oldGNUSparseMainHeaderIsExtendedOffset] != 0 + spCap := oldGNUSparseMainHeaderNumEntries + if isExtended { + spCap += oldGNUSparseExtendedHeaderNumEntries + } + sp := make([]sparseEntry, 0, spCap) + s := slicer(header[oldGNUSparseMainHeaderOffset:]) + + // Read the four entries from the main tar header + for i := 0; i < oldGNUSparseMainHeaderNumEntries; i++ { + offset := tr.octal(s.next(oldGNUSparseOffsetSize)) + numBytes := tr.octal(s.next(oldGNUSparseNumBytesSize)) + if tr.err != nil { + tr.err = ErrHeader + return nil + } + if offset == 0 && numBytes == 0 { + break + } + sp = append(sp, sparseEntry{offset: offset, numBytes: numBytes}) + } + + for isExtended { + // There are more entries. Read an extension header and parse its entries. + sparseHeader := make([]byte, blockSize) + if _, tr.err = io.ReadFull(tr.r, sparseHeader); tr.err != nil { + return nil + } + isExtended = sparseHeader[oldGNUSparseExtendedHeaderIsExtendedOffset] != 0 + s = slicer(sparseHeader) + for i := 0; i < oldGNUSparseExtendedHeaderNumEntries; i++ { + offset := tr.octal(s.next(oldGNUSparseOffsetSize)) + numBytes := tr.octal(s.next(oldGNUSparseNumBytesSize)) + if tr.err != nil { + tr.err = ErrHeader + return nil + } + if offset == 0 && numBytes == 0 { + break + } + sp = append(sp, sparseEntry{offset: offset, numBytes: numBytes}) + } + } + return sp +} + +// readGNUSparseMap1x0 reads the sparse map as stored in GNU's PAX sparse format version 1.0. +// The sparse map is stored just before the file data and padded out to the nearest block boundary. +func readGNUSparseMap1x0(r io.Reader) ([]sparseEntry, error) { + buf := make([]byte, 2*blockSize) + sparseHeader := buf[:blockSize] + + // readDecimal is a helper function to read a decimal integer from the sparse map + // while making sure to read from the file in blocks of size blockSize + readDecimal := func() (int64, error) { + // Look for newline + nl := bytes.IndexByte(sparseHeader, '\n') + if nl == -1 { + if len(sparseHeader) >= blockSize { + // This is an error + return 0, ErrHeader + } + oldLen := len(sparseHeader) + newLen := oldLen + blockSize + if cap(sparseHeader) < newLen { + // There's more header, but we need to make room for the next block + copy(buf, sparseHeader) + sparseHeader = buf[:newLen] + } else { + // There's more header, and we can just reslice + sparseHeader = sparseHeader[:newLen] + } + + // Now that sparseHeader is large enough, read next block + if _, err := io.ReadFull(r, sparseHeader[oldLen:newLen]); err != nil { + return 0, err + } + + // Look for a newline in the new data + nl = bytes.IndexByte(sparseHeader[oldLen:newLen], '\n') + if nl == -1 { + // This is an error + return 0, ErrHeader + } + nl += oldLen // We want the position from the beginning + } + // Now that we've found a newline, read a number + n, err := strconv.ParseInt(string(sparseHeader[:nl]), 10, 0) + if err != nil { + return 0, ErrHeader + } + + // Update sparseHeader to consume this number + sparseHeader = sparseHeader[nl+1:] + return n, nil + } + + // Read the first block + if _, err := io.ReadFull(r, sparseHeader); err != nil { + return nil, err + } + + // The first line contains the number of entries + numEntries, err := readDecimal() + if err != nil { + return nil, err + } + + // Read all the entries + sp := make([]sparseEntry, 0, numEntries) + for i := int64(0); i < numEntries; i++ { + // Read the offset + offset, err := readDecimal() + if err != nil { + return nil, err + } + // Read numBytes + numBytes, err := readDecimal() + if err != nil { + return nil, err + } + + sp = append(sp, sparseEntry{offset: offset, numBytes: numBytes}) + } + + return sp, nil +} + +// readGNUSparseMap0x1 reads the sparse map as stored in GNU's PAX sparse format version 0.1. +// The sparse map is stored in the PAX headers. +func readGNUSparseMap0x1(headers map[string]string) ([]sparseEntry, error) { + // Get number of entries + numEntriesStr, ok := headers[paxGNUSparseNumBlocks] + if !ok { + return nil, ErrHeader + } + numEntries, err := strconv.ParseInt(numEntriesStr, 10, 0) + if err != nil { + return nil, ErrHeader + } + + sparseMap := strings.Split(headers[paxGNUSparseMap], ",") + + // There should be two numbers in sparseMap for each entry + if int64(len(sparseMap)) != 2*numEntries { + return nil, ErrHeader + } + + // Loop through the entries in the sparse map + sp := make([]sparseEntry, 0, numEntries) + for i := int64(0); i < numEntries; i++ { + offset, err := strconv.ParseInt(sparseMap[2*i], 10, 0) + if err != nil { + return nil, ErrHeader + } + numBytes, err := strconv.ParseInt(sparseMap[2*i+1], 10, 0) + if err != nil { + return nil, ErrHeader + } + sp = append(sp, sparseEntry{offset: offset, numBytes: numBytes}) + } + + return sp, nil +} + +// numBytes returns the number of bytes left to read in the current file's entry +// in the tar archive, or 0 if there is no current file. +func (tr *Reader) numBytes() int64 { + if tr.curr == nil { + // No current file, so no bytes + return 0 + } + return tr.curr.numBytes() +} + // Read reads from the current entry in the tar archive. // It returns 0, io.EOF when it reaches the end of that entry, // until Next is called to advance to the next entry. func (tr *Reader) Read(b []byte) (n int, err error) { - if tr.nb == 0 { + if tr.curr == nil { + return 0, io.EOF + } + n, err = tr.curr.Read(b) + if err != nil && err != io.EOF { + tr.err = err + } + return +} + +func (rfr *regFileReader) Read(b []byte) (n int, err error) { + if rfr.nb == 0 { // file consumed return 0, io.EOF } - - if int64(len(b)) > tr.nb { - b = b[0:tr.nb] + if int64(len(b)) > rfr.nb { + b = b[0:rfr.nb] } - n, err = tr.r.Read(b) - tr.nb -= int64(n) + n, err = rfr.r.Read(b) + rfr.nb -= int64(n) - if err == io.EOF && tr.nb > 0 { + if err == io.EOF && rfr.nb > 0 { err = io.ErrUnexpectedEOF } - tr.err = err return } + +// numBytes returns the number of bytes left to read in the file's data in the tar archive. +func (rfr *regFileReader) numBytes() int64 { + return rfr.nb +} + +// readHole reads a sparse file hole ending at offset toOffset +func (sfr *sparseFileReader) readHole(b []byte, toOffset int64) int { + n64 := toOffset - sfr.pos + if n64 > int64(len(b)) { + n64 = int64(len(b)) + } + n := int(n64) + for i := 0; i < n; i++ { + b[i] = 0 + } + sfr.pos += n64 + return n +} + +// Read reads the sparse file data in expanded form. +func (sfr *sparseFileReader) Read(b []byte) (n int, err error) { + if len(sfr.sp) == 0 { + // No more data fragments to read from. + if sfr.pos < sfr.tot { + // We're in the last hole + n = sfr.readHole(b, sfr.tot) + return + } + // Otherwise, we're at the end of the file + return 0, io.EOF + } + if sfr.pos < sfr.sp[0].offset { + // We're in a hole + n = sfr.readHole(b, sfr.sp[0].offset) + return + } + + // We're not in a hole, so we'll read from the next data fragment + posInFragment := sfr.pos - sfr.sp[0].offset + bytesLeft := sfr.sp[0].numBytes - posInFragment + if int64(len(b)) > bytesLeft { + b = b[0:bytesLeft] + } + + n, err = sfr.rfr.Read(b) + sfr.pos += int64(n) + + if int64(n) == bytesLeft { + // We're done with this fragment + sfr.sp = sfr.sp[1:] + } + + if err == io.EOF && sfr.pos < sfr.tot { + // We reached the end of the last fragment's data, but there's a final hole + err = nil + } + return +} + +// numBytes returns the number of bytes left to read in the sparse file's +// sparse-encoded data in the tar archive. +func (sfr *sparseFileReader) numBytes() int64 { + return sfr.rfr.nb +} diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader_test.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader_test.go index f84dbebe9..9601ffe45 100644 --- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader_test.go +++ b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader_test.go @@ -9,6 +9,7 @@ import ( "crypto/md5" "fmt" "io" + "io/ioutil" "os" "reflect" "strings" @@ -54,8 +55,92 @@ var gnuTarTest = &untarTest{ }, } +var sparseTarTest = &untarTest{ + file: "testdata/sparse-formats.tar", + headers: []*Header{ + { + Name: "sparse-gnu", + Mode: 420, + Uid: 1000, + Gid: 1000, + Size: 200, + ModTime: time.Unix(1392395740, 0), + Typeflag: 0x53, + Linkname: "", + Uname: "david", + Gname: "david", + Devmajor: 0, + Devminor: 0, + }, + { + Name: "sparse-posix-0.0", + Mode: 420, + Uid: 1000, + Gid: 1000, + Size: 200, + ModTime: time.Unix(1392342187, 0), + Typeflag: 0x30, + Linkname: "", + Uname: "david", + Gname: "david", + Devmajor: 0, + Devminor: 0, + }, + { + Name: "sparse-posix-0.1", + Mode: 420, + Uid: 1000, + Gid: 1000, + Size: 200, + ModTime: time.Unix(1392340456, 0), + Typeflag: 0x30, + Linkname: "", + Uname: "david", + Gname: "david", + Devmajor: 0, + Devminor: 0, + }, + { + Name: "sparse-posix-1.0", + Mode: 420, + Uid: 1000, + Gid: 1000, + Size: 200, + ModTime: time.Unix(1392337404, 0), + Typeflag: 0x30, + Linkname: "", + Uname: "david", + Gname: "david", + Devmajor: 0, + Devminor: 0, + }, + { + Name: "end", + Mode: 420, + Uid: 1000, + Gid: 1000, + Size: 4, + ModTime: time.Unix(1392398319, 0), + Typeflag: 0x30, + Linkname: "", + Uname: "david", + Gname: "david", + Devmajor: 0, + Devminor: 0, + }, + }, + cksums: []string{ + "6f53234398c2449fe67c1812d993012f", + "6f53234398c2449fe67c1812d993012f", + "6f53234398c2449fe67c1812d993012f", + "6f53234398c2449fe67c1812d993012f", + "b0061974914468de549a2af8ced10316", + }, +} + var untarTests = []*untarTest{ gnuTarTest, + sparseTarTest, { file: "testdata/star.tar", headers: []*Header{ @@ -386,7 +471,7 @@ func TestParsePAXHeader(t *testing.T) { func TestParsePAXTime(t *testing.T) { // Some valid PAX time values timestamps := map[string]time.Time{ - "1350244992.023960108": time.Unix(1350244992, 23960108), // The commoon case + "1350244992.023960108": time.Unix(1350244992, 23960108), // The common case "1350244992.02396010": time.Unix(1350244992, 23960100), // Lower precision value "1350244992.0239601089": time.Unix(1350244992, 23960108), // Higher precision value "1350244992": time.Unix(1350244992, 0), // Low precision value @@ -423,3 +508,236 @@ func TestMergePAX(t *testing.T) { t.Errorf("incorrect merge: got %+v, want %+v", hdr, want) } } + +func TestSparseEndToEnd(t *testing.T) { + test := sparseTarTest + f, err := os.Open(test.file) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer f.Close() + + tr := NewReader(f) + + headers := test.headers + cksums := test.cksums + nread := 0 + + // loop over all files + for ; ; nread++ { + hdr, err := tr.Next() + if hdr == nil || err == io.EOF { + break + } + + // check the header + if !reflect.DeepEqual(*hdr, *headers[nread]) { + t.Errorf("Incorrect header:\nhave %+v\nwant %+v", + *hdr, headers[nread]) + } + + // read and checksum the file data + h := md5.New() + _, err = io.Copy(h, tr) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // verify checksum + have := fmt.Sprintf("%x", h.Sum(nil)) + want := cksums[nread] + if want != have { + t.Errorf("Bad checksum on file %s:\nhave %+v\nwant %+v", hdr.Name, have, want) + } + } + if nread != len(headers) { + t.Errorf("Didn't process all files\nexpected: %d\nprocessed %d\n", len(headers), nread) + } +} + +type sparseFileReadTest struct { + sparseData []byte + sparseMap []sparseEntry + realSize int64 + expected []byte +} + +var sparseFileReadTests = []sparseFileReadTest{ + { + sparseData: []byte("abcde"), + sparseMap: []sparseEntry{ + {offset: 0, numBytes: 2}, + {offset: 5, numBytes: 3}, + }, + realSize: 8, + expected: []byte("ab\x00\x00\x00cde"), + }, + { + sparseData: []byte("abcde"), + sparseMap: []sparseEntry{ + {offset: 0, numBytes: 2}, + {offset: 5, numBytes: 3}, + }, + realSize: 10, + expected: []byte("ab\x00\x00\x00cde\x00\x00"), + }, + { + sparseData: []byte("abcde"), + sparseMap: []sparseEntry{ + {offset: 1, numBytes: 3}, + {offset: 6, numBytes: 2}, + }, + realSize: 8, + expected: []byte("\x00abc\x00\x00de"), + }, + { + sparseData: []byte("abcde"), + sparseMap: []sparseEntry{ + {offset: 1, numBytes: 3}, + {offset: 6, numBytes: 2}, + }, + realSize: 10, + expected: []byte("\x00abc\x00\x00de\x00\x00"), + }, + { + sparseData: []byte(""), + sparseMap: nil, + realSize: 2, + expected: []byte("\x00\x00"), + }, +} + +func TestSparseFileReader(t *testing.T) { + for i, test := range sparseFileReadTests { + r := bytes.NewReader(test.sparseData) + nb := int64(r.Len()) + sfr := &sparseFileReader{ + rfr: ®FileReader{r: r, nb: nb}, + sp: test.sparseMap, + pos: 0, + tot: test.realSize, + } + if sfr.numBytes() != nb { + t.Errorf("test %d: Before reading, sfr.numBytes() = %d, want %d", i, sfr.numBytes(), nb) + } + buf, err := ioutil.ReadAll(sfr) + if err != nil { + t.Errorf("test %d: Unexpected error: %v", i, err) + } + if e := test.expected; !bytes.Equal(buf, e) { + t.Errorf("test %d: Contents = %v, want %v", i, buf, e) + } + if sfr.numBytes() != 0 { + t.Errorf("test %d: After draining the reader, numBytes() was nonzero", i) + } + } +} + +func TestSparseIncrementalRead(t *testing.T) { + sparseMap := []sparseEntry{{10, 2}} + sparseData := []byte("Go") + expected := "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Go\x00\x00\x00\x00\x00\x00\x00\x00" + + r := bytes.NewReader(sparseData) + nb := int64(r.Len()) + sfr := &sparseFileReader{ + rfr: ®FileReader{r: r, nb: nb}, + sp: sparseMap, + pos: 0, + tot: int64(len(expected)), + } + + // We'll read the data 6 bytes at a time, with a hole of size 10 at + // the beginning and one of size 8 at the end. + var outputBuf bytes.Buffer + buf := make([]byte, 6) + for { + n, err := sfr.Read(buf) + if err == io.EOF { + break + } + if err != nil { + t.Errorf("Read: unexpected error %v\n", err) + } + if n > 0 { + _, err := outputBuf.Write(buf[:n]) + if err != nil { + t.Errorf("Write: unexpected error %v\n", err) + } + } + } + got := outputBuf.String() + if got != expected { + t.Errorf("Contents = %v, want %v", got, expected) + } +} + +func TestReadGNUSparseMap0x1(t *testing.T) { + headers := map[string]string{ + paxGNUSparseNumBlocks: "4", + paxGNUSparseMap: "0,5,10,5,20,5,30,5", + } + expected := []sparseEntry{ + {offset: 0, numBytes: 5}, + {offset: 10, numBytes: 5}, + {offset: 20, numBytes: 5}, + {offset: 30, numBytes: 5}, + } + + sp, err := readGNUSparseMap0x1(headers) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !reflect.DeepEqual(sp, expected) { + t.Errorf("Incorrect sparse map: got %v, wanted %v", sp, expected) + } +} + +func TestReadGNUSparseMap1x0(t *testing.T) { + // This test uses lots of holes so the sparse header takes up more than two blocks + numEntries := 100 + expected := make([]sparseEntry, 0, numEntries) + sparseMap := new(bytes.Buffer) + + fmt.Fprintf(sparseMap, "%d\n", numEntries) + for i := 0; i < numEntries; i++ { + offset := int64(2048 * i) + numBytes := int64(1024) + expected = append(expected, sparseEntry{offset: offset, numBytes: numBytes}) + fmt.Fprintf(sparseMap, "%d\n%d\n", offset, numBytes) + } + + // Make the header the smallest multiple of blockSize that fits the sparseMap + headerBlocks := (sparseMap.Len() + blockSize - 1) / blockSize + bufLen := blockSize * headerBlocks + buf := make([]byte, bufLen) + copy(buf, sparseMap.Bytes()) + + // Get an reader to read the sparse map + r := bytes.NewReader(buf) + + // Read the sparse map + sp, err := readGNUSparseMap1x0(r) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !reflect.DeepEqual(sp, expected) { + t.Errorf("Incorrect sparse map: got %v, wanted %v", sp, expected) + } +} + +func TestUninitializedRead(t *testing.T) { + test := gnuTarTest + f, err := os.Open(test.file) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer f.Close() + + tr := NewReader(f) + _, err = tr.Read([]byte{}) + if err == nil || err != io.EOF { + t.Errorf("Unexpected error: %v, wanted %v", err, io.EOF) + } + +} diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/sparse-formats.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/sparse-formats.tar new file mode 100644 index 0000000000000000000000000000000000000000..8bd4e74d50f9c8961f80a887ab7d6449e032048b GIT binary patch literal 17920 zcmeHO!BXQ!5M{6a3g^xmb&sU64qQV{sZ?#{1DvdPiv%!*Aw}}_dEEjdi|Nre#)hpG zE{}(KnjXC;wbY|&t*;k1>*dFY-EbwpT`b+-m>u zXfjaoe5W44g1VMFbuvaLV|3acePf?HHj7T34f|}^XTyHz*zDR5hW%jJ$GN!K=dPX7 zuwNSXOT&I?*sl!xm0`a!>{o{UdfWbohf`t0wKm47jd5yYoVY#C#(p&HN5g(h+o$d^ z>C~x6+ovLJp9;gi;Rj^+0U3Tkh98jO2W0pG8Gb;9ACTb()boS>@h8I{`Aj1#H@B=dZfDAvtjWMmW;RoC~7Py0M z`m*5%-1CF}@n^#y*zgB7{DBRBV8b8S@CP>hfen8^_^{DnOAo^z5MmhHr;h_0e!zww zu;B-6_yHS!z=j{N;RkH^0r&ji+3`30fen9P!ynl22R8fx0blw!^!(v@`{T)$#0AMUzUr{%bWEKK}dPBZfAtotM&Q)$6}V4GkAAL?ydIxj}Q`3 zJOATY>UD~$8khO$y?3COY_Ib_T!Mz?cSHC~#(oEVI84ue{e9LR^x69SzvU?x#e`$G z`ReZSkBilxf3HuQYO>v9_2tWYd3#C|uKGRxyy zk#CN0vO|t>vO|t?vO|t@vV)g2dr7mGGDo)W_L8o>q-!tf+DkfmNk=c~=p`M!q@$Pg+)H}y zB|Z0&o_k5py`&p2>BdW1A|f+1N!@lEFX<*ndTZ#%;H1d0PWQ;sPWQ<1PWQ+WPxo*$ zCpU9)GbcB5ax*74^K5XIR5u%)rF*!UXXCT<7;fg-2rW5AHbhJJa5K*aY3VWC%(G!y za*S-8mhRzZo{iMfW4M`TW3}WM*$8{;Fcc4%{&{rCCA9dZs{Iw=Go{iJw}H4J9rlMBkscMKka?4V*dGW zC;x{dmiMq8gvD(vpG{xk(ev}2>9_pg&wuy1`g67#*MIt_+k5+eaQ%mN-{S%QM+!~( zxc)<2*YN+U4#l|sv%B)c7PePszGeL<)ZO)vtHtH=w09GsNfox9d|WQBPwAMB1HKi$ z5#I)1l17qNl4g>25`gi0%mT0gEC34-1PB5I0fGQQfKq@`fKq@`fKq@;fJ%T$fJ%T$ zfLefBfLefBfLeekKolSf5Cw<=%mtVWFc)Ahz+8YvfJT5ufJT5u0A#CaDG)Nzv=opE zMO*%@0IdS81gZg+MP*A>0a;*L*S;zQ^1P%)r9keM))iGXNaa8-mb9xN$g|SAj;op= zlS*1t6=X?iT~QSVc~H`#(jdo4>x!y6$YPQf)rV9dQiVt*BGrggBvO?~WSR`0jpG)F zR$z95<=;=bg;QIfR|BZ{k=7%FW59w-S{C9wpVT}I{Ao4pNVj%vb z{pbH+{)aiAzW>2BZdt7HACK|hLCzZHZZvnf_-l0|IXl~}=T~SgCPR@QPL^Kc(9Lpj zv56@U!e<=Br@-+2fA>qk!2KVorji&Q8CK+X>e0g#)6 z@dUuK3}6apYu09*vX znm!5vu=b8Z0L;v^6bQklmI7jCCS}XN6`)n1l|VJX%uKdX6)-c?y7pBeFf)@Dl>##} ztt+Z(U}h#Qst0CfT31vh!CNoVqM~4CrgcSC7r2GAs4|$DXJmbGf$=ejIaDU{qjK;EfgdABYgg9smFU literal 0 HcmV?d00001 diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big-long.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big-long.tar new file mode 100644 index 0000000000000000000000000000000000000000..5960ee824784ffeacb976a9c648be41b0281508b GIT binary patch literal 4096 zcmeIuJqp7x3 0 { - copy(header[257:265], []byte("ustar\000")) + if len(prefix) > 0 && !tw.usedBinary { + copy(header[257:265], []byte("ustar\x00")) } } } diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer_test.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer_test.go index 2b9ea658d..512fab1a6 100644 --- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer_test.go +++ b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer_test.go @@ -103,6 +103,29 @@ var writerTests = []*writerTest{ }, }, }, + // The truncated test file was produced using these commands: + // dd if=/dev/zero bs=1048576 count=16384 > (longname/)*15 /16gig.txt + // tar -b 1 -c -f- (longname/)*15 /16gig.txt | dd bs=512 count=8 > writer-big-long.tar + { + file: "testdata/writer-big-long.tar", + entries: []*writerTestEntry{ + { + header: &Header{ + Name: strings.Repeat("longname/", 15) + "16gig.txt", + Mode: 0644, + Uid: 1000, + Gid: 1000, + Size: 16 << 30, + ModTime: time.Unix(1399583047, 0), + Typeflag: '0', + Uname: "guillaume", + Gname: "guillaume", + }, + // fake contents + contents: strings.Repeat("\x00", 4<<10), + }, + }, + }, // This file was produced using gnu tar 1.17 // gnutar -b 4 --format=ustar (longname/)*15 + file.txt { From aedcb76adc2b2468565b311b67f4079a128b37cd Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 15 May 2014 15:45:40 -0700 Subject: [PATCH 111/400] Add test for invalid tarball read Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- archive/archive_test.go | 29 +++++++++++++++++++++++++++++ archive/testdata/broken.tar | Bin 0 -> 13824 bytes 2 files changed, 29 insertions(+) create mode 100644 archive/testdata/broken.tar diff --git a/archive/archive_test.go b/archive/archive_test.go index 412660139..e959a2b07 100644 --- a/archive/archive_test.go +++ b/archive/archive_test.go @@ -137,3 +137,32 @@ func TestTypeXGlobalHeaderDoesNotFail(t *testing.T) { t.Fatal(err) } } + +// Some tar have both GNU specific (huge uid) and Ustar specific (long name) things. +// Not supposed to happen (should use PAX instead of Ustar for long name) but it does and it should still work. +func TestUntarUstarGnuConflict(t *testing.T) { + f, err := os.Open("testdata/broken.tar") + if err != nil { + t.Fatal(err) + } + found := false + tr := tar.NewReader(f) + // Iterate through the files in the archive. + for { + hdr, err := tr.Next() + if err == io.EOF { + // end of tar archive + break + } + if err != nil { + t.Fatal(err) + } + if hdr.Name == "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm" { + found = true + break + } + } + if !found { + t.Fatal("%s not found in the archive", "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm") + } +} diff --git a/archive/testdata/broken.tar b/archive/testdata/broken.tar new file mode 100644 index 0000000000000000000000000000000000000000..8f10ea6b87d3eb4fed572349dfe87695603b10a5 GIT binary patch literal 13824 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 literal 0 HcmV?d00001 From 8b77a5b7aedb1168707f486ed540edf3e5de8819 Mon Sep 17 00:00:00 2001 From: lalyos Date: Thu, 15 May 2014 23:52:36 +0200 Subject: [PATCH 112/400] Adding test case for symlink causes infinit loop, reproduces: dotcloud#5370 normally symlinks are created as either ln -s /path/existing /path/new-name or cd /path && ln -s ./existing new-name but one can create it this way cd /path && ln -s existing new-name this drives FollowSymlinkInScope into infinite loop Docker-DCO-1.1-Signed-off-by: Lajos Papp (github: lalyos) --- pkg/symlink/fs_test.go | 13 +++++++++++++ pkg/symlink/testdata/fs/i | 1 + 2 files changed, 14 insertions(+) create mode 120000 pkg/symlink/testdata/fs/i diff --git a/pkg/symlink/fs_test.go b/pkg/symlink/fs_test.go index 1f12aa3a6..d85fd6da7 100644 --- a/pkg/symlink/fs_test.go +++ b/pkg/symlink/fs_test.go @@ -28,6 +28,19 @@ func TestFollowSymLinkNormal(t *testing.T) { } } +func TestFollowSymLinkRelativePath(t *testing.T) { + link := "testdata/fs/i" + + rewrite, err := FollowSymlinkInScope(link, "testdata") + if err != nil { + t.Fatal(err) + } + + if expected := abs(t, "testdata/fs/a"); expected != rewrite { + t.Fatalf("Expected %s got %s", expected, rewrite) + } +} + func TestFollowSymLinkUnderLinkedDir(t *testing.T) { dir, err := ioutil.TempDir("", "docker-fs-test") if err != nil { diff --git a/pkg/symlink/testdata/fs/i b/pkg/symlink/testdata/fs/i new file mode 120000 index 000000000..2e65efe2a --- /dev/null +++ b/pkg/symlink/testdata/fs/i @@ -0,0 +1 @@ +a \ No newline at end of file From b51c366bfc963687b8cc14df614a2fc10bad6306 Mon Sep 17 00:00:00 2001 From: lalyos Date: Fri, 16 May 2014 00:25:38 +0200 Subject: [PATCH 113/400] Defend against infinite loop when following symlinks ideally it should never reach it, but there was already multiple issues with infinite loop at following symlinks. this fixes hanging unit tests Docker-DCO-1.1-Signed-off-by: Lajos Papp (github: lalyos) --- pkg/symlink/fs.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/symlink/fs.go b/pkg/symlink/fs.go index e91d33db4..4dcfdf360 100644 --- a/pkg/symlink/fs.go +++ b/pkg/symlink/fs.go @@ -3,10 +3,13 @@ package symlink import ( "fmt" "os" + "path" "path/filepath" "strings" ) +const maxLoopCounter = 100 + // FollowSymlink will follow an existing link and scope it to the root // path provided. func FollowSymlinkInScope(link, root string) (string, error) { @@ -30,7 +33,14 @@ func FollowSymlinkInScope(link, root string) (string, error) { prev = filepath.Join(prev, p) prev = filepath.Clean(prev) + loopCounter := 0 for { + loopCounter++ + + if loopCounter >= maxLoopCounter { + return "", fmt.Errorf("loopCounter reached MAX: %v", loopCounter) + } + if !strings.HasPrefix(prev, root) { // Don't resolve symlinks outside of root. For example, // we don't have to check /home in the below. From ad35d522dbfac124225e27f58bf07c61a34d78b5 Mon Sep 17 00:00:00 2001 From: lalyos Date: Fri, 25 Apr 2014 05:54:20 +0200 Subject: [PATCH 114/400] Fixes 5370 infinite/maxLoopCount loop for relative symlinks use path.IsAbs() instead of checking if first char is '/' Docker-DCO-1.1-Signed-off-by: Lajos Papp (github: lalyos) --- AUTHORS | 1 + pkg/symlink/fs.go | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index 014748e18..8f75ab85b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -189,6 +189,7 @@ Kimbro Staken Kiran Gangadharan Konstantin Pelykh Kyle Conroy +Lajos Papp Laurie Voss Liang-Chi Hsieh Lokesh Mandvekar diff --git a/pkg/symlink/fs.go b/pkg/symlink/fs.go index 4dcfdf360..257491f91 100644 --- a/pkg/symlink/fs.go +++ b/pkg/symlink/fs.go @@ -63,10 +63,9 @@ func FollowSymlinkInScope(link, root string) (string, error) { return "", err } - switch dest[0] { - case '/': + if path.IsAbs(dest) { prev = filepath.Join(root, dest) - case '.': + } else { prev, _ = filepath.Abs(prev) if prev = filepath.Clean(filepath.Join(filepath.Dir(prev), dest)); len(prev) < len(root) { From 81ffd6530aced5237c01bd13e80686da68de6637 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 15 May 2014 23:27:36 +0000 Subject: [PATCH 115/400] allow 2 docker events to be opened on the same computer over unix socket Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api/server/server.go | 2 +- server/server.go | 12 ++++++------ server/server_unit_test.go | 7 ++++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index e2a7f651a..9bf6da3fe 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -244,7 +244,7 @@ func getEvents(eng *engine.Engine, version version.Version, w http.ResponseWrite return err } - var job = eng.Job("events", r.RemoteAddr) + var job = eng.Job("events") streamJSON(job, w, true) job.Setenv("since", r.Form.Get("since")) job.Setenv("until", r.Form.Get("until")) diff --git a/server/server.go b/server/server.go index cab11bcf5..49fb00ce5 100644 --- a/server/server.go +++ b/server/server.go @@ -198,7 +198,7 @@ func (srv *Server) ContainerKill(job *engine.Job) engine.Status { return engine.StatusOK } -func (srv *Server) EvictListener(from string) { +func (srv *Server) EvictListener(from int64) { srv.Lock() if old, ok := srv.listeners[from]; ok { delete(srv.listeners, from) @@ -208,12 +208,12 @@ func (srv *Server) EvictListener(from string) { } func (srv *Server) Events(job *engine.Job) engine.Status { - if len(job.Args) != 1 { - return job.Errorf("Usage: %s FROM", job.Name) + if len(job.Args) != 0 { + return job.Errorf("Usage: %s", job.Name) } var ( - from = job.Args[0] + from = time.Now().UTC().UnixNano() since = job.GetenvInt64("since") until = job.GetenvInt64("until") timeout = time.NewTimer(time.Unix(until, 0).Sub(time.Now())) @@ -2432,7 +2432,7 @@ func NewServer(eng *engine.Engine, config *daemonconfig.Config) (*Server, error) pullingPool: make(map[string]chan struct{}), pushingPool: make(map[string]chan struct{}), events: make([]utils.JSONMessage, 0, 64), //only keeps the 64 last events - listeners: make(map[string]chan utils.JSONMessage), + listeners: make(map[int64]chan utils.JSONMessage), running: true, } daemon.SetServer(srv) @@ -2494,7 +2494,7 @@ type Server struct { pullingPool map[string]chan struct{} pushingPool map[string]chan struct{} events []utils.JSONMessage - listeners map[string]chan utils.JSONMessage + listeners map[int64]chan utils.JSONMessage Eng *engine.Engine running bool } diff --git a/server/server_unit_test.go b/server/server_unit_test.go index b471c5c58..47e4be828 100644 --- a/server/server_unit_test.go +++ b/server/server_unit_test.go @@ -1,9 +1,10 @@ package server import ( - "github.com/dotcloud/docker/utils" "testing" "time" + + "github.com/dotcloud/docker/utils" ) func TestPools(t *testing.T) { @@ -47,14 +48,14 @@ func TestPools(t *testing.T) { func TestLogEvent(t *testing.T) { srv := &Server{ events: make([]utils.JSONMessage, 0, 64), - listeners: make(map[string]chan utils.JSONMessage), + listeners: make(map[int64]chan utils.JSONMessage), } srv.LogEvent("fakeaction", "fakeid", "fakeimage") listener := make(chan utils.JSONMessage) srv.Lock() - srv.listeners["test"] = listener + srv.listeners[1337] = listener srv.Unlock() srv.LogEvent("fakeaction2", "fakeid", "fakeimage") From d35330bb7aa8473b92f6f45718aa6c38863cccc7 Mon Sep 17 00:00:00 2001 From: SvenDowideit Date: Fri, 16 May 2014 09:30:46 +1000 Subject: [PATCH 116/400] Update the cli.md docs with the output of the docker command Docker-DCO-1.1-Signed-off-by: SvenDowideit (github: SvenDowideit) --- docs/sources/reference/commandline/cli.md | 341 +++++++++++----------- 1 file changed, 174 insertions(+), 167 deletions(-) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index fcc2862b3..ec2ebf9a1 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -50,35 +50,38 @@ expect an integer, and they can only be specified once. ## daemon Usage of docker: + --api-enable-cors=false Enable CORS headers in the remote API + -b, --bridge="" Attach containers to a pre-existing network bridge + use 'none' to disable container networking + --bip="" Use this CIDR notation address for the network bridge's IP, not compatible with -b + -d, --daemon=false Enable daemon mode + -D, --debug=false Enable debug mode + --dns=[] Force docker to use specific DNS servers + --dns-search=[] Force Docker to use specific DNS search domains + -e, --exec-driver="native" Force the docker runtime to use a specific exec driver + -G, --group="docker" Group to assign the unix socket specified by -H when running in daemon mode + use '' (the empty string) to disable setting of a group + -g, --graph="/var/lib/docker" Path to use as the root of the docker runtime + -H, --host=[] The socket(s) to bind to in daemon mode + specified using one or more tcp://host:port, unix:///path/to/socket, fd://* or fd://socketfd. + --icc=true Enable inter-container communication + --ip="0.0.0.0" Default IP address to use when binding container ports + --ip-forward=true Enable net.ipv4.ip_forward + --iptables=true Enable Docker's addition of iptables rules + --mtu=0 Set the containers network MTU + if no value is provided: default to the default route MTU or 1500 if no default route is available + -p, --pidfile="/var/run/docker.pid" Path to use for daemon PID file + -r, --restart=true Restart previously running containers + -s, --storage-driver="" Force the docker runtime to use a specific storage driver + --selinux-enabled=false Enable selinux support + --tls=false Use TLS; implied by tls-verify flags + --tlscacert="/home/sven/.docker/ca.pem" Trust only remotes providing a certificate signed by the CA given here + --tlscert="/home/sven/.docker/cert.pem" Path to TLS certificate file + --tlskey="/home/sven/.docker/key.pem" Path to TLS key file + --tlsverify=false Use TLS and verify the remote (daemon: verify client, client: verify daemon) + -v, --version=false Print version information and quit - -D, --debug=false: Enable debug mode - -H, --host=[]: The socket(s) to bind to in daemon mode, specified using one or more tcp://host:port, unix:///path/to/socket, fd://* or fd://socketfd. - -G, --group="docker": Group to assign the unix socket specified by -H when running in daemon mode; use '' (the empty string) to disable setting of a group - --api-enable-cors=false: Enable CORS headers in the remote API - -b, --bridge="": Attach containers to a pre-existing network bridge; use 'none' to disable container networking - -bip="": Use this CIDR notation address for the network bridge᾿s IP, not compatible with -b - -d, --daemon=false: Enable daemon mode - --dns=[]: Force docker to use specific DNS servers - --dns-search=[]: Force Docker to use specific DNS search domains - --enable-selinux=false: Enable selinux support for running containers - -g, --graph="/var/lib/docker": Path to use as the root of the docker runtime - --icc=true: Enable inter-container communication - --ip="0.0.0.0": Default IP address to use when binding container ports - --ip-forward=true: Enable net.ipv4.ip_forward - --iptables=true: Enable Docker᾿s addition of iptables rules - -p, --pidfile="/var/run/docker.pid": Path to use for daemon PID file - -r, --restart=true: Restart previously running containers - -s, --storage-driver="": Force the docker runtime to use a specific storage driver - -e, --exec-driver="native": Force the docker runtime to use a specific exec driver - -v, --version=false: Print version information and quit - --tls=false: Use TLS; implied by tls-verify flags - --tlscacert="~/.docker/ca.pem": Trust only remotes providing a certificate signed by the CA given here - --tlscert="~/.docker/cert.pem": Path to TLS certificate file - --tlskey="~/.docker/key.pem": Path to TLS key file - --tlsverify=false: Use TLS and verify the remote (daemon: verify client, client: verify daemon) - --mtu=0: Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if no default route is available - - Options with [] may be specified multiple times. +Options with [] may be specified multiple times. The Docker daemon is the persistent process that manages containers. Docker uses the same binary for both the daemon and client. To run the @@ -126,12 +129,12 @@ like this: ## attach -Attach to a running container. + Usage: docker attach [OPTIONS] CONTAINER - Usage: docker attach CONTAINER + Attach to a running container - --no-stdin=false: Do not attach stdin - --sig-proxy=true: Proxify all received signal to the process (even in non-tty mode) + --no-stdin=false Do not attach stdin + --sig-proxy=true Proxify all received signal to the process (even in non-tty mode) The `attach` command will allow you to view or interact with any running container, detached (`-d`) @@ -185,15 +188,14 @@ To kill the container, use `docker kill`. ## build -Build a new container image from the source code at PATH - Usage: docker build [OPTIONS] PATH | URL | - - -t, --tag="": Repository name (and optionally a tag) to be applied - to the resulting image in case of success. - -q, --quiet=false: Suppress the verbose output generated by the containers. - --no-cache: Do not use the cache when building the image. - --rm=true: Remove intermediate containers after a successful build + Build a new container image from the source code at PATH + + --no-cache=false Do not use cache when building the image + -q, --quiet=false Suppress the verbose output generated by the containers + --rm=true Remove intermediate containers after a successful build + -t, --tag="" Repository name (and optionally a tag) to be applied to the resulting image in case of success Use this command to build Docker images from a Dockerfile and a "context". @@ -293,12 +295,12 @@ schema. ## commit -Create a new image from a container᾿s changes - Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]] - -m, --message="": Commit message - -a, --author="": Author (eg. "John Hannibal Smith " + Create a new image from a container's changes + + -a, --author="" Author (eg. "John Hannibal Smith " + -m, --message="" Commit message It can be useful to commit a container's file changes or settings into a new image. This allows you debug a container by running an interactive @@ -325,8 +327,7 @@ path. Paths are relative to the root of the filesystem. Usage: docker cp CONTAINER:PATH HOSTPATH - $ sudo docker cp 7bb0e258aefe:/etc/debian_version . - $ sudo docker cp blue_frog:/etc/hosts . + Copy files/folders from the PATH to the HOSTPATH ## diff @@ -334,6 +335,8 @@ List the changed files and directories in a container᾿s filesystem Usage: docker diff CONTAINER + Inspect changes on a container's filesystem + There are 3 events that are listed in the `diff`: 1. `A` - Add @@ -358,14 +361,12 @@ For example: ## events -Get real time events from the server + Usage: docker events [OPTIONS] - Usage: docker events + Get real time events from the server - --since="": Show all events created since timestamp - (either seconds since epoch, or date string as below) - --until="": Show events created before timestamp - (either seconds since epoch, or date string as below) + --since="" Show all events created since timestamp + --until="" Stream events until this timestamp ### Examples @@ -403,22 +404,22 @@ You'll need two shells for this example. ## export -Export the contents of a filesystem as a tar archive to STDOUT - Usage: docker export CONTAINER + Export the contents of a filesystem as a tar archive to STDOUT + For example: $ sudo docker export red_panda > latest.tar ## history -Show the history of an image - Usage: docker history [OPTIONS] IMAGE - --no-trunc=false: Don᾿t truncate output - -q, --quiet=false: Only show numeric IDs + Show the history of an image + + --no-trunc=false Don't truncate output + -q, --quiet=false Only show numeric IDs To see how the `docker:latest` image was built: @@ -433,13 +434,13 @@ To see how the `docker:latest` image was built: ## images -List images - Usage: docker images [OPTIONS] [NAME] - -a, --all=false: Show all images (by default filter out the intermediate image layers) - --no-trunc=false: Don᾿t truncate output - -q, --quiet=false: Only show numeric IDs + List images + + -a, --all=false Show all images (by default filter out the intermediate image layers) + --no-trunc=false Don't truncate output + -q, --quiet=false Only show numeric IDs The default `docker images` will show all top level images, their repository and tags, and their virtual size. @@ -481,8 +482,7 @@ by default. Usage: docker import URL|- [REPOSITORY[:TAG]] - Create an empty filesystem image and import the contents of the tarball - (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then optionally tag it. + Create an empty filesystem image and import the contents of the tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then optionally tag it. URLs must start with `http` and point to a single file archive (.tar, .tar.gz, .tgz, .bzip, .tar.xz, or .txz) containing a @@ -515,10 +515,12 @@ tar, then the ownerships might not get preserved. ## info -Display system-wide information. - Usage: docker info + Display system-wide information + +For example: + $ sudo docker info Containers: 292 Images: 194 @@ -536,11 +538,11 @@ ensure we know how your setup is configured. ## inspect -Return low-level information on a container/image - Usage: docker inspect CONTAINER|IMAGE [CONTAINER|IMAGE...] - -f, --format="": Format the output using the given go template. + Return low-level information on a container/image + + -f, --format="" Format the output using the given go template. By default, this will render all results in a JSON array. If a format is specified, the given template will be executed for each result. @@ -590,11 +592,11 @@ contains complex json object, so to grab it as JSON, you use ## kill -Kill a running container (send SIGKILL, or specified signal) - Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...] - -s, --signal="KILL": Signal to send to the container + Kill a running container (send SIGKILL, or specified signal) + + -s, --signal="KILL" Signal to send to the container The main process inside the container will be sent SIGKILL, or any signal specified with option `--signal`. @@ -610,11 +612,11 @@ signal specified with option `--signal`. ## load -Load an image from a tar archive on STDIN - Usage: docker load - -i, --input="": Read from a tar archive file, instead of STDIN + Load an image from a tar archive on STDIN + + -i, --input="" Read from a tar archive file, instead of STDIN Loads a tarred repository from a file or the standard input stream. Restores both images and tags. @@ -636,13 +638,13 @@ Restores both images and tags. ## login -Register or Login to the docker registry server - Usage: docker login [OPTIONS] [SERVER] - -e, --email="": Email - -p, --password="": Password - -u, --username="": Username + Register or Login to a docker registry server, if no server is specified "https://index.docker.io/v1/" is the default. + + -e, --email="" Email + -p, --password="" Password + -u, --username="" Username If you want to login to a private registry you can specify this by adding the server name. @@ -652,12 +654,12 @@ specify this by adding the server name. ## logs -Fetch the logs of a container + Usage: docker logs CONTAINER - Usage: docker logs [OPTIONS] CONTAINER + Fetch the logs of a container - -f, --follow=false: Follow log output - -t, --timestamps=false: Show timestamps + -f, --follow=false Follow log output + -t, --timestamps=false Show timestamps The `docker logs` command batch-retrieves all logs present at the time of execution. @@ -668,24 +670,24 @@ and stderr. ## port - Usage: docker port [OPTIONS] CONTAINER PRIVATE_PORT + Usage: docker port CONTAINER PRIVATE_PORT -Lookup the public-facing port which is NAT-ed to PRIVATE_PORT + Lookup the public-facing port which is NAT-ed to PRIVATE_PORT ## ps -List containers - Usage: docker ps [OPTIONS] - -a, --all=false: Show all containers. Only running containers are shown by default. - --before="": Show only container created before Id or Name, include non-running ones. - -l, --latest=false: Show only the latest created container, include non-running ones. - -n=-1: Show n last created containers, include non-running ones. - --no-trunc=false: Don᾿t truncate output - -q, --quiet=false: Only display numeric IDs - -s, --size=false: Display sizes, not to be used with -q - --since="": Show only containers created since Id or Name, include non-running ones. + List containers + + -a, --all=false Show all containers. Only running containers are shown by default. + --before="" Show only container created before Id or Name, include non-running ones. + -l, --latest=false Show only the latest created container, include non-running ones. + -n=-1 Show n last created containers, include non-running ones. + --no-trunc=false Don't truncate output + -q, --quiet=false Only display numeric IDs + -s, --size=false Display sizes + --since="" Show only containers created since Id or Name, include non-running ones. Running `docker ps` showing 2 linked containers. @@ -699,10 +701,10 @@ Running `docker ps` showing 2 linked containers. ## pull -Pull an image or a repository from the registry - Usage: docker pull NAME[:TAG] + Pull an image or a repository from the registry + Most of your images will be created on top of a base image from the [Docker.io](https://index.docker.io) registry. @@ -721,30 +723,30 @@ use `docker pull`: ## push -Push an image or a repository to the registry - Usage: docker push NAME[:TAG] + Push an image or a repository to the registry + Use `docker push` to share your images on public or private registries. ## restart -Restart a running container + Usage: docker restart [OPTIONS] CONTAINER [CONTAINER...] - Usage: docker restart [OPTIONS] NAME + Restart a running container - -t, --time=10: Number of seconds to try to stop for before killing the container. Once killed it will then be restarted. Default=10 + -t, --time=10 Number of seconds to try to stop for before killing the container. Once killed it will then be restarted. Default=10 ## rm -Remove one or more containers + Usage: docker rm [OPTIONS] CONTAINER [CONTAINER...] - Usage: docker rm [OPTIONS] CONTAINER + Remove one or more containers - -l, --link="": Remove the link instead of the actual container - -f, --force=false: Force removal of running container - -v, --volumes=false: Remove the volumes associated to the container + -f, --force=false Force removal of running container + -l, --link=false Remove the specified link and not the underlying container + -v, --volumes=false Remove the volumes associated to the container ### Known Issues (rm) @@ -776,12 +778,12 @@ delete them. Any running containers will not be deleted. ## rmi -Remove one or more images - Usage: docker rmi IMAGE [IMAGE...] - -f, --force=false: Force - --no-prune=false: Do not delete untagged parents + Remove one or more images + + -f, --force=false Force + --no-prune=false Do not delete untagged parents ### Removing tagged images @@ -813,44 +815,43 @@ removed before the image is removed. ## run -Run a command in a new container + Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...] - Usage: docker run [OPTIONS] IMAGE[:TAG] [COMMAND] [ARG...] + Run a command in a new container - -a, --attach=[] Attach to stdin, stdout or stderr. - -c, --cpu-shares=0 CPU shares (relative weight) - --cpuset="" CPUs in which to allow execution (0-3, 0,1) - --cidfile="" Write the container ID to the file - -d, --detach=false Detached mode: Run container in the background, print new container id - --dns=[] Set custom dns servers - --dns-search=[] Set custom dns search domains - -e, --env=[] Set environment variables - --entrypoint="" Overwrite the default entrypoint of the image - --env-file=[] Read in a line delimited file of ENV variables - --expose=[] Expose a port from the container without publishing it to your host - -h, --hostname="" Container host name - -i, --interactive=false Keep stdin open even if not attached - --link=[] Add link to another container (name:alias) - --lxc-conf=[] (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" - -m, --memory="" Memory limit (format: , where unit = b, k, m or g) - --name="" Assign a name to the container - --net="bridge" Set the Network mode for the container - 'bridge': creates a new network stack for the container on the docker bridge - 'none': no networking for this container - 'container:': reuses another container network stack - 'host': use the host network stack inside the contaner - -p, --publish=[] Publish a container's port to the host - format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort - (use 'docker port' to see the actual mapping) - -P, --publish-all=false Publish all exposed ports to the host interfaces - --privileged=false Give extended privileges to this container - --rm=false Automatically remove the container when it exits (incompatible with -d) - --sig-proxy=true Proxify all received signal to the process (even in non-tty mode) - -t, --tty=false Allocate a pseudo-tty - -u, --user="" Username or UID - -v, --volume=[] Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container) - --volumes-from=[] Mount volumes from the specified container(s) - -w, --workdir="" Working directory inside the container + -a, --attach=[] Attach to stdin, stdout or stderr. + -c, --cpu-shares=0 CPU shares (relative weight) + --cidfile="" Write the container ID to the file + -d, --detach=false Detached mode: Run container in the background, print new container id + --dns=[] Set custom dns servers + --dns-search=[] Set custom dns search domains + -e, --env=[] Set environment variables + --entrypoint="" Overwrite the default entrypoint of the image + --env-file=[] Read in a line delimited file of ENV variables + --expose=[] Expose a port from the container without publishing it to your host + -h, --hostname="" Container host name + -i, --interactive=false Keep stdin open even if not attached + --link=[] Add link to another container (name:alias) + --lxc-conf=[] (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" + -m, --memory="" Memory limit (format: , where unit = b, k, m or g) + --name="" Assign a name to the container + --net="bridge" Set the Network mode for the container + 'bridge': creates a new network stack for the container on the docker bridge + 'none': no networking for this container + 'container:': reuses another container network stack + 'host': use the host network stack inside the contaner + -p, --publish=[] Publish a container's port to the host + format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort + (use 'docker port' to see the actual mapping) + -P, --publish-all=false Publish all exposed ports to the host interfaces + --privileged=false Give extended privileges to this container + --rm=false Automatically remove the container when it exits (incompatible with -d) + --sig-proxy=true Proxify all received signal to the process (even in non-tty mode) + -t, --tty=false Allocate a pseudo-tty + -u, --user="" Username or UID + -v, --volume=[] Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container) + --volumes-from=[] Mount volumes from the specified container(s) + -w, --workdir="" Working directory inside the container The `docker run` command first `creates` a writeable container layer over the specified image, and then `starts` it using the specified command. That is, @@ -1061,11 +1062,11 @@ application change: ## save -Save an image to a tar archive (streamed to stdout by default) - Usage: docker save IMAGE - -o, --output="": Write to an file, instead of STDOUT + Save an image to a tar archive (streamed to stdout by default) + + -o, --output="" Write to an file, instead of STDOUT Produces a tarred repository to the standard output stream. Contains all parent layers, and all tags + versions, or specified repo:tag. @@ -1088,9 +1089,11 @@ Search [Docker.io](https://index.docker.io) for images Usage: docker search TERM - --no-trunc=false: Don᾿t truncate output - -s, --stars=0: Only displays with at least xxx stars - -t, --trusted=false: Only show trusted builds + Search the docker index for images + + --no-trunc=false Don't truncate output + -s, --stars=0 Only displays with at least xxx stars + -t, --trusted=false Only show trusted builds See [*Find Public Images on Docker.io*]( /use/workingwithrepository/#find-public-images-on-dockerio) for @@ -1098,31 +1101,31 @@ more details on finding shared images from the commandline. ## start -Start a stopped container + Usage: docker start CONTAINER [CONTAINER...] - Usage: docker start [OPTIONS] CONTAINER + Restart a stopped container - -a, --attach=false: Attach container᾿s stdout/stderr and forward all signals to the process - -i, --interactive=false: Attach container᾿s stdin + -a, --attach=false Attach container's stdout/stderr and forward all signals to the process + -i, --interactive=false Attach container's stdin ## stop -Stop a running container (Send SIGTERM, and then SIGKILL after grace period) - Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...] - -t, --time=10: Number of seconds to wait for the container to stop before killing it. + Stop a running container (Send SIGTERM, and then SIGKILL after grace period) + + -t, --time=10 Number of seconds to wait for the container to stop before killing it. The main process inside the container will receive SIGTERM, and after a grace period, SIGKILL ## tag -Tag an image into a repository - Usage: docker tag [OPTIONS] IMAGE [REGISTRYHOST/][USERNAME/]NAME[:TAG] - -f, --force=false: Force + Tag an image into a repository + + -f, --force=false Force You can group your images together using names and tags, and then upload them to [*Share Images via Repositories*]( @@ -1132,15 +1135,19 @@ them to [*Share Images via Repositories*]( Usage: docker top CONTAINER [ps OPTIONS] -Lookup the running processes of a container + Lookup the running processes of a container ## version + Usage: docker version + + Show the docker version information. + Show the version of the Docker client, daemon, and latest released version. ## wait - Usage: docker wait [OPTIONS] NAME + Usage: docker wait CONTAINER [CONTAINER...] -Block until a container stops, then print its exit code. + Block until a container stops, then print its exit code. From 9d6875d19d3926faf6287487234ad0b2f1310e9d Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Wed, 14 May 2014 18:29:08 +0000 Subject: [PATCH 117/400] Change libcontainer to drop all capabilities by default. Only keeps those that were specified in the config. This commit also explicitly adds a set of capabilities that we were silently not dropping and were assumed by the tests. Docker-DCO-1.1-Signed-off-by: Victor Marmol (github: vmarmol) --- .../native/template/default_template.go | 5 +++ .../security/capabilities/capabilities.go | 38 ++++++++++--------- pkg/libcontainer/types.go | 5 +++ 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/daemon/execdriver/native/template/default_template.go b/daemon/execdriver/native/template/default_template.go index 249c5d5fe..9f2dd17a3 100644 --- a/daemon/execdriver/native/template/default_template.go +++ b/daemon/execdriver/native/template/default_template.go @@ -26,6 +26,11 @@ func New() *libcontainer.Container { "NET_ADMIN": false, "MKNOD": true, "SYSLOG": false, + "SETUID": true, + "SETGID": true, + "CHOWN": true, + "NET_RAW": true, + "DAC_OVERRIDE": true, }, Namespaces: map[string]bool{ "NEWNS": true, diff --git a/pkg/libcontainer/security/capabilities/capabilities.go b/pkg/libcontainer/security/capabilities/capabilities.go index ad13e672c..107417ad7 100644 --- a/pkg/libcontainer/security/capabilities/capabilities.go +++ b/pkg/libcontainer/security/capabilities/capabilities.go @@ -7,32 +7,34 @@ import ( "github.com/syndtr/gocapability/capability" ) -// DropCapabilities drops capabilities for the current process based -// on the container's configuration. -func DropCapabilities(container *libcontainer.Container) error { - if drop := getCapabilitiesMask(container); len(drop) > 0 { - c, err := capability.NewPid(os.Getpid()) - if err != nil { - return err - } - c.Unset(capability.CAPS|capability.BOUNDS, drop...) +const allCapabilityTypes = capability.CAPS | capability.BOUNDS - if err := c.Apply(capability.CAPS | capability.BOUNDS); err != nil { - return err - } +// DropCapabilities drops all capabilities for the current process expect those specified in the container configuration. +func DropCapabilities(container *libcontainer.Container) error { + c, err := capability.NewPid(os.Getpid()) + if err != nil { + return err + } + + keep := getEnabledCapabilities(container) + c.Clear(allCapabilityTypes) + c.Set(allCapabilityTypes, keep...) + + if err := c.Apply(allCapabilityTypes); err != nil { + return err } return nil } -// getCapabilitiesMask returns the specific cap mask values for the libcontainer types -func getCapabilitiesMask(container *libcontainer.Container) []capability.Cap { - drop := []capability.Cap{} +// getCapabilitiesMask returns the capabilities that should not be dropped by the container. +func getEnabledCapabilities(container *libcontainer.Container) []capability.Cap { + keep := []capability.Cap{} for key, enabled := range container.CapabilitiesMask { - if !enabled { + if enabled { if c := libcontainer.GetCapability(key); c != nil { - drop = append(drop, c.Value) + keep = append(keep, c.Value) } } } - return drop + return keep } diff --git a/pkg/libcontainer/types.go b/pkg/libcontainer/types.go index 8f056c817..4c8f60c47 100644 --- a/pkg/libcontainer/types.go +++ b/pkg/libcontainer/types.go @@ -55,6 +55,11 @@ var ( {Key: "MAC_ADMIN", Value: capability.CAP_MAC_ADMIN}, {Key: "NET_ADMIN", Value: capability.CAP_NET_ADMIN}, {Key: "SYSLOG", Value: capability.CAP_SYSLOG}, + {Key: "SETUID", Value: capability.CAP_SETUID}, + {Key: "SETGID", Value: capability.CAP_SETGID}, + {Key: "CHOWN", Value: capability.CAP_CHOWN}, + {Key: "NET_RAW", Value: capability.CAP_NET_RAW}, + {Key: "DAC_OVERRIDE", Value: capability.CAP_DAC_OVERRIDE}, } ) From ddb99054bc9c04cce4ca761f9f0d822d67d614c4 Mon Sep 17 00:00:00 2001 From: Steven Burgess Date: Thu, 15 May 2014 22:23:03 -0400 Subject: [PATCH 118/400] Refactor b.tar -> busybox.tar The file was saved as busybox.tar, but the ls commands named it b.tar. Docker-DCO-1.1-Signed-off-by: Steven Burgess (github: stevenburgess) --- docs/sources/reference/commandline/cli.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index fcc2862b3..513560b11 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1074,11 +1074,11 @@ It is used to create a backup that can then be used with `docker load` $ sudo docker save busybox > busybox.tar - $ ls -sh b.tar - 2.7M b.tar + $ ls -sh busybox.tar + 2.7M busybox.tar $ sudo docker save --output busybox.tar busybox - $ ls -sh b.tar - 2.7M b.tar + $ ls -sh busybox.tar + 2.7M busybox.tar $ sudo docker save -o fedora-all.tar fedora $ sudo docker save -o fedora-latest.tar fedora:latest From 1b4915c1a8ba4724edd4e7864e85ff4fb795d04f Mon Sep 17 00:00:00 2001 From: waitingkuo Date: Fri, 16 May 2014 17:08:05 +0800 Subject: [PATCH 119/400] Update ubuntulinux.md In Ubuntu 14.04, we should use $ sudo service docker.io restart instead of $ sudo service docker restart --- docs/sources/installation/ubuntulinux.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sources/installation/ubuntulinux.md b/docs/sources/installation/ubuntulinux.md index d40e17b64..add4fb180 100644 --- a/docs/sources/installation/ubuntulinux.md +++ b/docs/sources/installation/ubuntulinux.md @@ -203,6 +203,7 @@ than `docker` should own the Unix socket with the $ sudo gpasswd -a ${USER} docker # Restart the Docker daemon. + # If you are in Ubuntu 14.04, use docker.io instead of docker $ sudo service docker restart ### Upgrade From bd6fd25dfb4cf7aae0e5e382e3bf10c9b2caa5fb Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 16 May 2014 12:46:28 +0200 Subject: [PATCH 120/400] devicemapper: Remove non-general tests Now that we have the generic graphtest tests that actually tests the driver we can remove the old mock-using tests. Almost all of these tests were disabled anyway, and the four remaining ones didn't really test much while at the same time being really fragile and making the rest of the code more complex due to the mocking setup. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- .../graphdriver/devmapper/devmapper_test.go | 286 +----- daemon/graphdriver/devmapper/driver_test.go | 939 ------------------ 2 files changed, 18 insertions(+), 1207 deletions(-) delete mode 100644 daemon/graphdriver/devmapper/driver_test.go diff --git a/daemon/graphdriver/devmapper/devmapper_test.go b/daemon/graphdriver/devmapper/devmapper_test.go index 3ffa163ce..7c97d6bb0 100644 --- a/daemon/graphdriver/devmapper/devmapper_test.go +++ b/daemon/graphdriver/devmapper/devmapper_test.go @@ -3,285 +3,35 @@ package devmapper import ( + "github.com/dotcloud/docker/daemon/graphdriver/graphtest" "testing" ) -func TestTaskCreate(t *testing.T) { - t.Skip("FIXME: not a unit test") - // Test success - taskCreate(t, DeviceInfo) - - // Test Failure - DmTaskCreate = dmTaskCreateFail - defer func() { DmTaskCreate = dmTaskCreateFct }() - if task := TaskCreate(-1); task != nil { - t.Fatalf("An error should have occured while creating an invalid task.") - } +func init() { + // Reduce the size the the base fs and loopback for the tests + DefaultDataLoopbackSize = 300 * 1024 * 1024 + DefaultMetaDataLoopbackSize = 200 * 1024 * 1024 + DefaultBaseFsSize = 300 * 1024 * 1024 } -func TestTaskRun(t *testing.T) { - t.Skip("FIXME: not a unit test") - task := taskCreate(t, DeviceInfo) - - // Test success - // Perform the RUN - if err := task.Run(); err != nil { - t.Fatal(err) - } - // Make sure we don't have error with GetInfo - if _, err := task.GetInfo(); err != nil { - t.Fatal(err) - } - - // Test failure - DmTaskRun = dmTaskRunFail - defer func() { DmTaskRun = dmTaskRunFct }() - - task = taskCreate(t, DeviceInfo) - // Perform the RUN - if err := task.Run(); err != ErrTaskRun { - t.Fatalf("An error should have occured while running task.") - } - // Make sure GetInfo also fails - if _, err := task.GetInfo(); err != ErrTaskGetInfo { - t.Fatalf("GetInfo should fail if task.Run() failed.") - } +// This avoids creating a new driver for each test if all tests are run +// Make sure to put new tests between TestDevmapperSetup and TestDevmapperTeardown +func TestDevmapperSetup(t *testing.T) { + graphtest.GetDriver(t, "devicemapper") } -func TestTaskSetName(t *testing.T) { - t.Skip("FIXME: not a unit test") - task := taskCreate(t, DeviceInfo) - - // Test success - if err := task.SetName("test"); err != nil { - t.Fatal(err) - } - - // Test failure - DmTaskSetName = dmTaskSetNameFail - defer func() { DmTaskSetName = dmTaskSetNameFct }() - - if err := task.SetName("test"); err != ErrTaskSetName { - t.Fatalf("An error should have occured while runnign SetName.") - } +func TestDevmapperCreateEmpty(t *testing.T) { + graphtest.DriverTestCreateEmpty(t, "devicemapper") } -func TestTaskSetMessage(t *testing.T) { - t.Skip("FIXME: not a unit test") - task := taskCreate(t, DeviceInfo) - - // Test success - if err := task.SetMessage("test"); err != nil { - t.Fatal(err) - } - - // Test failure - DmTaskSetMessage = dmTaskSetMessageFail - defer func() { DmTaskSetMessage = dmTaskSetMessageFct }() - - if err := task.SetMessage("test"); err != ErrTaskSetMessage { - t.Fatalf("An error should have occured while runnign SetMessage.") - } +func TestDevmapperCreateBase(t *testing.T) { + graphtest.DriverTestCreateBase(t, "devicemapper") } -func TestTaskSetSector(t *testing.T) { - t.Skip("FIXME: not a unit test") - task := taskCreate(t, DeviceInfo) - - // Test success - if err := task.SetSector(128); err != nil { - t.Fatal(err) - } - - DmTaskSetSector = dmTaskSetSectorFail - defer func() { DmTaskSetSector = dmTaskSetSectorFct }() - - // Test failure - if err := task.SetSector(0); err != ErrTaskSetSector { - t.Fatalf("An error should have occured while running SetSector.") - } +func TestDevmapperCreateSnap(t *testing.T) { + graphtest.DriverTestCreateSnap(t, "devicemapper") } -func TestTaskSetCookie(t *testing.T) { - t.Skip("FIXME: not a unit test") - var ( - cookie uint = 0 - task = taskCreate(t, DeviceInfo) - ) - - // Test success - if err := task.SetCookie(&cookie, 0); err != nil { - t.Fatal(err) - } - - // Test failure - if err := task.SetCookie(nil, 0); err != ErrNilCookie { - t.Fatalf("An error should have occured while running SetCookie with nil cookie.") - } - - DmTaskSetCookie = dmTaskSetCookieFail - defer func() { DmTaskSetCookie = dmTaskSetCookieFct }() - - if err := task.SetCookie(&cookie, 0); err != ErrTaskSetCookie { - t.Fatalf("An error should have occured while running SetCookie.") - } -} - -func TestTaskSetAddNode(t *testing.T) { - t.Skip("FIXME: not a unit test") - task := taskCreate(t, DeviceInfo) - - // Test success - if err := task.SetAddNode(0); err != nil { - t.Fatal(err) - } - - // Test failure - if err := task.SetAddNode(-1); err != ErrInvalidAddNode { - t.Fatalf("An error should have occured running SetAddNode with wrong node.") - } - - DmTaskSetAddNode = dmTaskSetAddNodeFail - defer func() { DmTaskSetAddNode = dmTaskSetAddNodeFct }() - - if err := task.SetAddNode(0); err != ErrTaskSetAddNode { - t.Fatalf("An error should have occured running SetAddNode.") - } -} - -func TestTaskSetRo(t *testing.T) { - t.Skip("FIXME: not a unit test") - task := taskCreate(t, DeviceInfo) - - // Test success - if err := task.SetRo(); err != nil { - t.Fatal(err) - } - - // Test failure - DmTaskSetRo = dmTaskSetRoFail - defer func() { DmTaskSetRo = dmTaskSetRoFct }() - - if err := task.SetRo(); err != ErrTaskSetRo { - t.Fatalf("An error should have occured running SetRo.") - } -} - -func TestTaskAddTarget(t *testing.T) { - t.Skip("FIXME: not a unit test") - task := taskCreate(t, DeviceInfo) - - // Test success - if err := task.AddTarget(0, 128, "thinp", ""); err != nil { - t.Fatal(err) - } - - // Test failure - DmTaskAddTarget = dmTaskAddTargetFail - defer func() { DmTaskAddTarget = dmTaskAddTargetFct }() - - if err := task.AddTarget(0, 128, "thinp", ""); err != ErrTaskAddTarget { - t.Fatalf("An error should have occured running AddTarget.") - } -} - -// func TestTaskGetInfo(t *testing.T) { -// task := taskCreate(t, DeviceInfo) - -// // Test success -// if _, err := task.GetInfo(); err != nil { -// t.Fatal(err) -// } - -// // Test failure -// DmTaskGetInfo = dmTaskGetInfoFail -// defer func() { DmTaskGetInfo = dmTaskGetInfoFct }() - -// if _, err := task.GetInfo(); err != ErrTaskGetInfo { -// t.Fatalf("An error should have occured running GetInfo.") -// } -// } - -// func TestTaskGetNextTarget(t *testing.T) { -// task := taskCreate(t, DeviceInfo) - -// if next, _, _, _, _ := task.GetNextTarget(0); next == 0 { -// t.Fatalf("The next target should not be 0.") -// } -// } - -/// Utils -func taskCreate(t *testing.T, taskType TaskType) *Task { - task := TaskCreate(taskType) - if task == nil { - t.Fatalf("Error creating task") - } - return task -} - -/// Failure function replacement -func dmTaskCreateFail(t int) *CDmTask { - return nil -} - -func dmTaskRunFail(task *CDmTask) int { - return -1 -} - -func dmTaskSetNameFail(task *CDmTask, name string) int { - return -1 -} - -func dmTaskSetMessageFail(task *CDmTask, message string) int { - return -1 -} - -func dmTaskSetSectorFail(task *CDmTask, sector uint64) int { - return -1 -} - -func dmTaskSetCookieFail(task *CDmTask, cookie *uint, flags uint16) int { - return -1 -} - -func dmTaskSetAddNodeFail(task *CDmTask, addNode AddNodeType) int { - return -1 -} - -func dmTaskSetRoFail(task *CDmTask) int { - return -1 -} - -func dmTaskAddTargetFail(task *CDmTask, - start, size uint64, ttype, params string) int { - return -1 -} - -func dmTaskGetInfoFail(task *CDmTask, info *Info) int { - return -1 -} - -func dmGetNextTargetFail(task *CDmTask, next uintptr, start, length *uint64, - target, params *string) uintptr { - return 0 -} - -func dmAttachLoopDeviceFail(filename string, fd *int) string { - return "" -} - -func sysGetBlockSizeFail(fd uintptr, size *uint64) sysErrno { - return 1 -} - -func dmUdevWaitFail(cookie uint) int { - return -1 -} - -func dmSetDevDirFail(dir string) int { - return -1 -} - -func dmGetLibraryVersionFail(version *string) int { - return -1 +func TestDevmapperTeardown(t *testing.T) { + graphtest.PutDriver(t) } diff --git a/daemon/graphdriver/devmapper/driver_test.go b/daemon/graphdriver/devmapper/driver_test.go deleted file mode 100644 index 0602f123a..000000000 --- a/daemon/graphdriver/devmapper/driver_test.go +++ /dev/null @@ -1,939 +0,0 @@ -// +build linux,amd64 - -package devmapper - -import ( - "fmt" - "github.com/dotcloud/docker/daemon/graphdriver" - "github.com/dotcloud/docker/daemon/graphdriver/graphtest" - "io/ioutil" - "path" - "runtime" - "strings" - "syscall" - "testing" -) - -func init() { - // Reduce the size the the base fs and loopback for the tests - DefaultDataLoopbackSize = 300 * 1024 * 1024 - DefaultMetaDataLoopbackSize = 200 * 1024 * 1024 - DefaultBaseFsSize = 300 * 1024 * 1024 -} - -// We use assignment here to get the right type -var ( - oldMounted = Mounted - oldExecRun = execRun -) - -// denyAllDevmapper mocks all calls to libdevmapper in the unit tests, and denies them by default -func denyAllDevmapper() { - oldExecRun = execRun - - // Hijack all calls to libdevmapper with default panics. - // Authorized calls are selectively hijacked in each tests. - DmTaskCreate = func(t int) *CDmTask { - panic("DmTaskCreate: this method should not be called here") - } - DmTaskRun = func(task *CDmTask) int { - panic("DmTaskRun: this method should not be called here") - } - DmTaskSetName = func(task *CDmTask, name string) int { - panic("DmTaskSetName: this method should not be called here") - } - DmTaskSetMessage = func(task *CDmTask, message string) int { - panic("DmTaskSetMessage: this method should not be called here") - } - DmTaskSetSector = func(task *CDmTask, sector uint64) int { - panic("DmTaskSetSector: this method should not be called here") - } - DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int { - panic("DmTaskSetCookie: this method should not be called here") - } - DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int { - panic("DmTaskSetAddNode: this method should not be called here") - } - DmTaskSetRo = func(task *CDmTask) int { - panic("DmTaskSetRo: this method should not be called here") - } - DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int { - panic("DmTaskAddTarget: this method should not be called here") - } - DmTaskGetInfo = func(task *CDmTask, info *Info) int { - panic("DmTaskGetInfo: this method should not be called here") - } - DmGetNextTarget = func(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr { - panic("DmGetNextTarget: this method should not be called here") - } - DmUdevWait = func(cookie uint) int { - panic("DmUdevWait: this method should not be called here") - } - DmSetDevDir = func(dir string) int { - panic("DmSetDevDir: this method should not be called here") - } - DmGetLibraryVersion = func(version *string) int { - panic("DmGetLibraryVersion: this method should not be called here") - } - DmLogInitVerbose = func(level int) { - panic("DmLogInitVerbose: this method should not be called here") - } - DmTaskDestroy = func(task *CDmTask) { - panic("DmTaskDestroy: this method should not be called here") - } - LogWithErrnoInit = func() { - panic("LogWithErrnoInit: this method should not be called here") - } -} - -func restoreAllDevmapper() { - DmGetLibraryVersion = dmGetLibraryVersionFct - DmGetNextTarget = dmGetNextTargetFct - DmLogInitVerbose = dmLogInitVerboseFct - DmSetDevDir = dmSetDevDirFct - DmTaskAddTarget = dmTaskAddTargetFct - DmTaskCreate = dmTaskCreateFct - DmTaskDestroy = dmTaskDestroyFct - DmTaskGetInfo = dmTaskGetInfoFct - DmTaskRun = dmTaskRunFct - DmTaskSetAddNode = dmTaskSetAddNodeFct - DmTaskSetCookie = dmTaskSetCookieFct - DmTaskSetMessage = dmTaskSetMessageFct - DmTaskSetName = dmTaskSetNameFct - DmTaskSetRo = dmTaskSetRoFct - DmTaskSetSector = dmTaskSetSectorFct - DmUdevWait = dmUdevWaitFct - LogWithErrnoInit = logWithErrnoInitFct - execRun = oldExecRun -} - -func denyAllSyscall() { - oldMounted = Mounted - sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) { - panic("sysMount: this method should not be called here") - } - sysUnmount = func(target string, flags int) (err error) { - panic("sysUnmount: this method should not be called here") - } - sysCloseOnExec = func(fd int) { - panic("sysCloseOnExec: this method should not be called here") - } - sysSyscall = func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { - panic("sysSyscall: this method should not be called here") - } - // Not a syscall, but forbidding it here anyway - Mounted = func(mnt string) (bool, error) { - panic("devmapper.Mounted: this method should not be called here") - } - // osOpenFile = os.OpenFile - // osNewFile = os.NewFile - // osCreate = os.Create - // osStat = os.Stat - // osIsNotExist = os.IsNotExist - // osIsExist = os.IsExist - // osMkdirAll = os.MkdirAll - // osRemoveAll = os.RemoveAll - // osRename = os.Rename - // osReadlink = os.Readlink - - // execRun = func(name string, args ...string) error { - // return exec.Command(name, args...).Run() - // } -} - -func restoreAllSyscall() { - sysMount = syscall.Mount - sysUnmount = syscall.Unmount - sysCloseOnExec = syscall.CloseOnExec - sysSyscall = syscall.Syscall - Mounted = oldMounted -} - -func mkTestDirectory(t *testing.T) string { - dir, err := ioutil.TempDir("", "docker-test-devmapper-") - if err != nil { - t.Fatal(err) - } - return dir -} - -func newDriver(t *testing.T) *Driver { - home := mkTestDirectory(t) - d, err := Init(home) - if err != nil { - t.Fatal(err) - } - return d.(*Driver) -} - -func cleanup(d *Driver) { - d.Cleanup() - osRemoveAll(d.home) -} - -type Set map[string]bool - -func (r Set) Assert(t *testing.T, names ...string) { - for _, key := range names { - required := true - if strings.HasPrefix(key, "?") { - key = key[1:] - required = false - } - if _, exists := r[key]; !exists && required { - t.Fatalf("Key not set: %s", key) - } - delete(r, key) - } - if len(r) != 0 { - t.Fatalf("Unexpected keys: %v", r) - } -} - -func TestInit(t *testing.T) { - var ( - calls = make(Set) - taskMessages = make(Set) - taskTypes = make(Set) - home = mkTestDirectory(t) - ) - defer osRemoveAll(home) - - denyAllDevmapper() - defer restoreAllDevmapper() - - func() { - DmSetDevDir = func(dir string) int { - calls["DmSetDevDir"] = true - expectedDir := "/dev" - if dir != expectedDir { - t.Fatalf("Wrong libdevmapper call\nExpected: DmSetDevDir(%v)\nReceived: DmSetDevDir(%v)\n", expectedDir, dir) - } - return 0 - } - LogWithErrnoInit = func() { - calls["DmLogWithErrnoInit"] = true - } - var task1 CDmTask - DmTaskCreate = func(taskType int) *CDmTask { - calls["DmTaskCreate"] = true - taskTypes[fmt.Sprintf("%d", taskType)] = true - return &task1 - } - DmTaskSetName = func(task *CDmTask, name string) int { - calls["DmTaskSetName"] = true - expectedTask := &task1 - if task != expectedTask { - t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetName(%v)\nReceived: DmTaskSetName(%v)\n", expectedTask, task) - } - // FIXME: use Set.AssertRegexp() - if !strings.HasPrefix(name, "docker-") && !strings.HasPrefix(name, "/dev/mapper/docker-") || - !strings.HasSuffix(name, "-pool") && !strings.HasSuffix(name, "-base") { - t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetName(%v)\nReceived: DmTaskSetName(%v)\n", "docker-...-pool", name) - } - return 1 - } - DmTaskRun = func(task *CDmTask) int { - calls["DmTaskRun"] = true - expectedTask := &task1 - if task != expectedTask { - t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskRun(%v)\nReceived: DmTaskRun(%v)\n", expectedTask, task) - } - return 1 - } - DmTaskGetInfo = func(task *CDmTask, info *Info) int { - calls["DmTaskGetInfo"] = true - expectedTask := &task1 - if task != expectedTask { - t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskGetInfo(%v)\nReceived: DmTaskGetInfo(%v)\n", expectedTask, task) - } - // This will crash if info is not dereferenceable - info.Exists = 0 - return 1 - } - DmTaskSetSector = func(task *CDmTask, sector uint64) int { - calls["DmTaskSetSector"] = true - expectedTask := &task1 - if task != expectedTask { - t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetSector(%v)\nReceived: DmTaskSetSector(%v)\n", expectedTask, task) - } - if expectedSector := uint64(0); sector != expectedSector { - t.Fatalf("Wrong libdevmapper call to DmTaskSetSector\nExpected: %v\nReceived: %v\n", expectedSector, sector) - } - return 1 - } - DmTaskSetMessage = func(task *CDmTask, message string) int { - calls["DmTaskSetMessage"] = true - expectedTask := &task1 - if task != expectedTask { - t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetSector(%v)\nReceived: DmTaskSetSector(%v)\n", expectedTask, task) - } - taskMessages[message] = true - return 1 - } - DmTaskDestroy = func(task *CDmTask) { - calls["DmTaskDestroy"] = true - expectedTask := &task1 - if task != expectedTask { - t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task) - } - } - DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int { - calls["DmTaskSetTarget"] = true - expectedTask := &task1 - if task != expectedTask { - t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task) - } - if start != 0 { - t.Fatalf("Wrong start: %d != %d", start, 0) - } - if ttype != "thin" && ttype != "thin-pool" { - t.Fatalf("Wrong ttype: %s", ttype) - } - // Quick smoke test - if params == "" { - t.Fatalf("Params should not be empty") - } - return 1 - } - fakeCookie := uint(4321) - DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int { - calls["DmTaskSetCookie"] = true - expectedTask := &task1 - if task != expectedTask { - t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task) - } - if flags != 0 { - t.Fatalf("Cookie flags should be 0 (not %x)", flags) - } - *cookie = fakeCookie - return 1 - } - DmUdevWait = func(cookie uint) int { - calls["DmUdevWait"] = true - if cookie != fakeCookie { - t.Fatalf("Wrong cookie: %d != %d", cookie, fakeCookie) - } - return 1 - } - DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int { - if addNode != AddNodeOnCreate { - t.Fatalf("Wrong AddNoteType: %v (expected %v)", addNode, AddNodeOnCreate) - } - calls["DmTaskSetAddNode"] = true - return 1 - } - execRun = func(name string, args ...string) error { - calls["execRun"] = true - if name != "mkfs.ext4" { - t.Fatalf("Expected %s to be executed, not %s", "mkfs.ext4", name) - } - return nil - } - driver, err := Init(home) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := driver.Cleanup(); err != nil { - t.Fatal(err) - } - }() - }() - // Put all tests in a function to make sure the garbage collection will - // occur. - - // Call GC to cleanup runtime.Finalizers - runtime.GC() - - calls.Assert(t, - "DmSetDevDir", - "DmLogWithErrnoInit", - "DmTaskSetName", - "DmTaskRun", - "DmTaskGetInfo", - "DmTaskDestroy", - "execRun", - "DmTaskCreate", - "DmTaskSetTarget", - "DmTaskSetCookie", - "DmUdevWait", - "DmTaskSetSector", - "DmTaskSetMessage", - "DmTaskSetAddNode", - ) - taskTypes.Assert(t, "0", "6", "17") - taskMessages.Assert(t, "create_thin 0", "set_transaction_id 0 1") -} - -func fakeInit() func(home string) (graphdriver.Driver, error) { - oldInit := Init - Init = func(home string) (graphdriver.Driver, error) { - return &Driver{ - home: home, - }, nil - } - return oldInit -} - -func restoreInit(init func(home string) (graphdriver.Driver, error)) { - Init = init -} - -func mockAllDevmapper(calls Set) { - DmSetDevDir = func(dir string) int { - calls["DmSetDevDir"] = true - return 0 - } - LogWithErrnoInit = func() { - calls["DmLogWithErrnoInit"] = true - } - DmTaskCreate = func(taskType int) *CDmTask { - calls["DmTaskCreate"] = true - return &CDmTask{} - } - DmTaskSetName = func(task *CDmTask, name string) int { - calls["DmTaskSetName"] = true - return 1 - } - DmTaskRun = func(task *CDmTask) int { - calls["DmTaskRun"] = true - return 1 - } - DmTaskGetInfo = func(task *CDmTask, info *Info) int { - calls["DmTaskGetInfo"] = true - return 1 - } - DmTaskSetSector = func(task *CDmTask, sector uint64) int { - calls["DmTaskSetSector"] = true - return 1 - } - DmTaskSetMessage = func(task *CDmTask, message string) int { - calls["DmTaskSetMessage"] = true - return 1 - } - DmTaskDestroy = func(task *CDmTask) { - calls["DmTaskDestroy"] = true - } - DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int { - calls["DmTaskSetTarget"] = true - return 1 - } - DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int { - calls["DmTaskSetCookie"] = true - return 1 - } - DmUdevWait = func(cookie uint) int { - calls["DmUdevWait"] = true - return 1 - } - DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int { - calls["DmTaskSetAddNode"] = true - return 1 - } - execRun = func(name string, args ...string) error { - calls["execRun"] = true - return nil - } -} - -func TestDriverName(t *testing.T) { - denyAllDevmapper() - defer restoreAllDevmapper() - - oldInit := fakeInit() - defer restoreInit(oldInit) - - d := newDriver(t) - if d.String() != "devicemapper" { - t.Fatalf("Expected driver name to be devicemapper got %s", d.String()) - } -} - -func TestDriverCreate(t *testing.T) { - denyAllDevmapper() - denyAllSyscall() - defer restoreAllSyscall() - defer restoreAllDevmapper() - - calls := make(Set) - mockAllDevmapper(calls) - - sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) { - calls["sysMount"] = true - // FIXME: compare the exact source and target strings (inodes + devname) - if expectedSource := "/dev/mapper/docker-"; !strings.HasPrefix(source, expectedSource) { - t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedSource, source) - } - if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) { - t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target) - } - if expectedFstype := "ext4"; fstype != expectedFstype { - t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFstype, fstype) - } - if expectedFlags := uintptr(3236757504); flags != expectedFlags { - t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags) - } - return nil - } - - sysUnmount = func(target string, flag int) error { - //calls["sysUnmount"] = true - - return nil - } - - Mounted = func(mnt string) (bool, error) { - calls["Mounted"] = true - if !strings.HasPrefix(mnt, "/tmp/docker-test-devmapper-") || !strings.HasSuffix(mnt, "/mnt/1") { - t.Fatalf("Wrong mounted call\nExpected: Mounted(%v)\nReceived: Mounted(%v)\n", "/tmp/docker-test-devmapper-.../mnt/1", mnt) - } - return false, nil - } - - sysSyscall = func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { - calls["sysSyscall"] = true - if trap != sysSysIoctl { - t.Fatalf("Unexpected syscall. Expecting SYS_IOCTL, received: %d", trap) - } - switch a2 { - case LoopSetFd: - calls["ioctl.loopsetfd"] = true - case LoopCtlGetFree: - calls["ioctl.loopctlgetfree"] = true - case LoopGetStatus64: - calls["ioctl.loopgetstatus"] = true - case LoopSetStatus64: - calls["ioctl.loopsetstatus"] = true - case LoopClrFd: - calls["ioctl.loopclrfd"] = true - case LoopSetCapacity: - calls["ioctl.loopsetcapacity"] = true - case BlkGetSize64: - calls["ioctl.blkgetsize"] = true - default: - t.Fatalf("Unexpected IOCTL. Received %d", a2) - } - return 0, 0, 0 - } - - func() { - d := newDriver(t) - - calls.Assert(t, - "DmSetDevDir", - "DmLogWithErrnoInit", - "DmTaskSetName", - "DmTaskRun", - "DmTaskGetInfo", - "execRun", - "DmTaskCreate", - "DmTaskSetTarget", - "DmTaskSetCookie", - "DmUdevWait", - "DmTaskSetSector", - "DmTaskSetMessage", - "DmTaskSetAddNode", - "sysSyscall", - "ioctl.blkgetsize", - "ioctl.loopsetfd", - "ioctl.loopsetstatus", - "?ioctl.loopctlgetfree", - ) - - if err := d.Create("1", ""); err != nil { - t.Fatal(err) - } - calls.Assert(t, - "DmTaskCreate", - "DmTaskGetInfo", - "DmTaskRun", - "DmTaskSetSector", - "DmTaskSetName", - "DmTaskSetMessage", - ) - - }() - - runtime.GC() - - calls.Assert(t, - "DmTaskDestroy", - ) -} - -func TestDriverRemove(t *testing.T) { - denyAllDevmapper() - denyAllSyscall() - defer restoreAllSyscall() - defer restoreAllDevmapper() - - calls := make(Set) - mockAllDevmapper(calls) - - sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) { - calls["sysMount"] = true - // FIXME: compare the exact source and target strings (inodes + devname) - if expectedSource := "/dev/mapper/docker-"; !strings.HasPrefix(source, expectedSource) { - t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedSource, source) - } - if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) { - t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target) - } - if expectedFstype := "ext4"; fstype != expectedFstype { - t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFstype, fstype) - } - if expectedFlags := uintptr(3236757504); flags != expectedFlags { - t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags) - } - return nil - } - sysUnmount = func(target string, flags int) (err error) { - // FIXME: compare the exact source and target strings (inodes + devname) - if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) { - t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target) - } - if expectedFlags := 0; flags != expectedFlags { - t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags) - } - return nil - } - Mounted = func(mnt string) (bool, error) { - calls["Mounted"] = true - return false, nil - } - - sysSyscall = func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { - calls["sysSyscall"] = true - if trap != sysSysIoctl { - t.Fatalf("Unexpected syscall. Expecting SYS_IOCTL, received: %d", trap) - } - switch a2 { - case LoopSetFd: - calls["ioctl.loopsetfd"] = true - case LoopCtlGetFree: - calls["ioctl.loopctlgetfree"] = true - case LoopGetStatus64: - calls["ioctl.loopgetstatus"] = true - case LoopSetStatus64: - calls["ioctl.loopsetstatus"] = true - case LoopClrFd: - calls["ioctl.loopclrfd"] = true - case LoopSetCapacity: - calls["ioctl.loopsetcapacity"] = true - case BlkGetSize64: - calls["ioctl.blkgetsize"] = true - default: - t.Fatalf("Unexpected IOCTL. Received %d", a2) - } - return 0, 0, 0 - } - - func() { - d := newDriver(t) - - calls.Assert(t, - "DmSetDevDir", - "DmLogWithErrnoInit", - "DmTaskSetName", - "DmTaskRun", - "DmTaskGetInfo", - "execRun", - "DmTaskCreate", - "DmTaskSetTarget", - "DmTaskSetCookie", - "DmUdevWait", - "DmTaskSetSector", - "DmTaskSetMessage", - "DmTaskSetAddNode", - "sysSyscall", - "ioctl.blkgetsize", - "ioctl.loopsetfd", - "ioctl.loopsetstatus", - "?ioctl.loopctlgetfree", - ) - - if err := d.Create("1", ""); err != nil { - t.Fatal(err) - } - - calls.Assert(t, - "DmTaskCreate", - "DmTaskGetInfo", - "DmTaskRun", - "DmTaskSetSector", - "DmTaskSetName", - "DmTaskSetMessage", - ) - - Mounted = func(mnt string) (bool, error) { - calls["Mounted"] = true - return true, nil - } - - if err := d.Remove("1"); err != nil { - t.Fatal(err) - } - - calls.Assert(t, - "DmTaskRun", - "DmTaskSetSector", - "DmTaskSetName", - "DmTaskSetMessage", - "DmTaskCreate", - "DmTaskGetInfo", - "DmTaskSetCookie", - "DmTaskSetTarget", - "DmTaskSetAddNode", - "DmUdevWait", - ) - }() - runtime.GC() - - calls.Assert(t, - "DmTaskDestroy", - ) -} - -func TestCleanup(t *testing.T) { - t.Skip("FIXME: not a unit test") - t.Skip("Unimplemented") - d := newDriver(t) - defer osRemoveAll(d.home) - - mountPoints := make([]string, 2) - - if err := d.Create("1", ""); err != nil { - t.Fatal(err) - } - // Mount the id - p, err := d.Get("1", "") - if err != nil { - t.Fatal(err) - } - mountPoints[0] = p - - if err := d.Create("2", "1"); err != nil { - t.Fatal(err) - } - - p, err = d.Get("2", "") - if err != nil { - t.Fatal(err) - } - mountPoints[1] = p - - // Ensure that all the mount points are currently mounted - for _, p := range mountPoints { - if mounted, err := Mounted(p); err != nil { - t.Fatal(err) - } else if !mounted { - t.Fatalf("Expected %s to be mounted", p) - } - } - - // Ensure that devices are active - for _, p := range []string{"1", "2"} { - if !d.HasActivatedDevice(p) { - t.Fatalf("Expected %s to have an active device", p) - } - } - - if err := d.Cleanup(); err != nil { - t.Fatal(err) - } - - // Ensure that all the mount points are no longer mounted - for _, p := range mountPoints { - if mounted, err := Mounted(p); err != nil { - t.Fatal(err) - } else if mounted { - t.Fatalf("Expected %s to not be mounted", p) - } - } - - // Ensure that devices are no longer activated - for _, p := range []string{"1", "2"} { - if d.HasActivatedDevice(p) { - t.Fatalf("Expected %s not be an active device", p) - } - } -} - -func TestNotMounted(t *testing.T) { - t.Skip("FIXME: not a unit test") - t.Skip("Not implemented") - d := newDriver(t) - defer cleanup(d) - - if err := d.Create("1", ""); err != nil { - t.Fatal(err) - } - - mounted, err := Mounted(path.Join(d.home, "mnt", "1")) - if err != nil { - t.Fatal(err) - } - if mounted { - t.Fatal("Id 1 should not be mounted") - } -} - -func TestMounted(t *testing.T) { - t.Skip("FIXME: not a unit test") - d := newDriver(t) - defer cleanup(d) - - if err := d.Create("1", ""); err != nil { - t.Fatal(err) - } - if _, err := d.Get("1", ""); err != nil { - t.Fatal(err) - } - - mounted, err := Mounted(path.Join(d.home, "mnt", "1")) - if err != nil { - t.Fatal(err) - } - if !mounted { - t.Fatal("Id 1 should be mounted") - } -} - -func TestInitCleanedDriver(t *testing.T) { - t.Skip("FIXME: not a unit test") - d := newDriver(t) - - if err := d.Create("1", ""); err != nil { - t.Fatal(err) - } - if _, err := d.Get("1", ""); err != nil { - t.Fatal(err) - } - - if err := d.Cleanup(); err != nil { - t.Fatal(err) - } - - driver, err := Init(d.home) - if err != nil { - t.Fatal(err) - } - d = driver.(*Driver) - defer cleanup(d) - - if _, err := d.Get("1", ""); err != nil { - t.Fatal(err) - } -} - -func TestMountMountedDriver(t *testing.T) { - t.Skip("FIXME: not a unit test") - d := newDriver(t) - defer cleanup(d) - - if err := d.Create("1", ""); err != nil { - t.Fatal(err) - } - - // Perform get on same id to ensure that it will - // not be mounted twice - if _, err := d.Get("1", ""); err != nil { - t.Fatal(err) - } - if _, err := d.Get("1", ""); err != nil { - t.Fatal(err) - } -} - -func TestGetReturnsValidDevice(t *testing.T) { - t.Skip("FIXME: not a unit test") - d := newDriver(t) - defer cleanup(d) - - if err := d.Create("1", ""); err != nil { - t.Fatal(err) - } - - if !d.HasDevice("1") { - t.Fatalf("Expected id 1 to be in device set") - } - - if _, err := d.Get("1", ""); err != nil { - t.Fatal(err) - } - - if !d.HasActivatedDevice("1") { - t.Fatalf("Expected id 1 to be activated") - } -} - -func TestDriverGetSize(t *testing.T) { - t.Skip("FIXME: not a unit test") - t.Skipf("Size is currently not implemented") - - d := newDriver(t) - defer cleanup(d) - - if err := d.Create("1", ""); err != nil { - t.Fatal(err) - } - - mountPoint, err := d.Get("1", "") - if err != nil { - t.Fatal(err) - } - - size := int64(1024) - - f, err := osCreate(path.Join(mountPoint, "test_file")) - if err != nil { - t.Fatal(err) - } - if err := f.Truncate(size); err != nil { - t.Fatal(err) - } - f.Close() - - // diffSize, err := d.DiffSize("1") - // if err != nil { - // t.Fatal(err) - // } - // if diffSize != size { - // t.Fatalf("Expected size %d got %d", size, diffSize) - // } -} - -func assertMap(t *testing.T, m map[string]bool, keys ...string) { - for _, key := range keys { - if _, exists := m[key]; !exists { - t.Fatalf("Key not set: %s", key) - } - delete(m, key) - } - if len(m) != 0 { - t.Fatalf("Unexpected keys: %v", m) - } -} - -// This avoids creating a new driver for each test if all tests are run -// Make sure to put new tests between TestDevmapperSetup and TestDevmapperTeardown -func TestDevmapperSetup(t *testing.T) { - graphtest.GetDriver(t, "devicemapper") -} - -func TestDevmapperCreateEmpty(t *testing.T) { - graphtest.DriverTestCreateEmpty(t, "devicemapper") -} - -func TestDevmapperCreateBase(t *testing.T) { - graphtest.DriverTestCreateBase(t, "devicemapper") -} - -func TestDevmapperCreateSnap(t *testing.T) { - graphtest.DriverTestCreateSnap(t, "devicemapper") -} - -func TestDevmapperTeardown(t *testing.T) { - graphtest.PutDriver(t) -} From 408f050d648da0b64d353146a1be59827d76bf70 Mon Sep 17 00:00:00 2001 From: Timothy Hobbs Date: Fri, 16 May 2014 11:45:20 +0000 Subject: [PATCH 121/400] Typo in execdrivers.go five => give Docker-DCO-1.1-Signed-off-by: Timothy Hobbs (github: timthelion) --- daemon/execdriver/execdrivers/execdrivers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/execdriver/execdrivers/execdrivers.go b/daemon/execdriver/execdrivers/execdrivers.go index 18db1f802..2e18454a0 100644 --- a/daemon/execdriver/execdrivers/execdrivers.go +++ b/daemon/execdriver/execdrivers/execdrivers.go @@ -12,7 +12,7 @@ import ( func NewDriver(name, root, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) { switch name { case "lxc": - // we want to five the lxc driver the full docker root because it needs + // we want to give the lxc driver the full docker root because it needs // to access and write config and template files in /var/lib/docker/containers/* // to be backwards compatible return lxc.NewDriver(root, sysInfo.AppArmor) From 00e1adfeada87100e5e88707309bcdcd674082d6 Mon Sep 17 00:00:00 2001 From: Bernerd Schaefer Date: Wed, 14 May 2014 11:49:06 +0200 Subject: [PATCH 122/400] nsinit.Init() restores parent death signal before exec Docker-DCO-1.1-Signed-off-by: Bernerd Schaefer (github: bernerdschaefer) --- pkg/libcontainer/nsinit/init.go | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 3bbcfcc65..85ca08c79 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -85,12 +85,53 @@ func Init(container *libcontainer.Container, uncleanRootfs, consolePath string, return err } } + + pdeathSignal, err := system.GetParentDeathSignal() + if err != nil { + return fmt.Errorf("get parent death signal %s", err) + } + if err := FinalizeNamespace(container); err != nil { return fmt.Errorf("finalize namespace %s", err) } + + // FinalizeNamespace can change user/group which clears the parent death + // signal, so we restore it here. + if err := RestoreParentDeathSignal(pdeathSignal); err != nil { + return fmt.Errorf("restore parent death signal %s", err) + } + return system.Execv(args[0], args[0:], container.Env) } +// RestoreParentDeathSignal sets the parent death signal to old. +func RestoreParentDeathSignal(old int) error { + if old == 0 { + return nil + } + + current, err := system.GetParentDeathSignal() + if err != nil { + return fmt.Errorf("get parent death signal %s", err) + } + + if old == current { + return nil + } + + if err := system.ParentDeathSignal(uintptr(old)); err != nil { + return fmt.Errorf("set parent death signal %s", err) + } + + // Signal self if parent is already dead. Does nothing if running in a new + // PID namespace, as Getppid will always return 0. + if syscall.Getppid() == 1 { + return syscall.Kill(syscall.Getpid(), syscall.Signal(old)) + } + + return nil +} + // SetupUser changes the groups, gid, and uid for the user inside the container func SetupUser(u string) error { uid, gid, suppGids, err := user.GetUserGroupSupplementary(u, syscall.Getuid(), syscall.Getgid()) From 6a1d76bc7bc589b53530c03720022f0095b65d55 Mon Sep 17 00:00:00 2001 From: Bernerd Schaefer Date: Wed, 14 May 2014 11:50:15 +0200 Subject: [PATCH 123/400] nsinit.DefaultCreateCommand sets Pdeathsig to SIGKILL Docker-DCO-1.1-Signed-off-by: Bernerd Schaefer (github: bernerdschaefer) --- pkg/libcontainer/nsinit/exec.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 03e0f4b9a..fbc751204 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -123,6 +123,7 @@ func DefaultCreateCommand(container *libcontainer.Container, console, rootfs, da command.Env = append(os.Environ(), env...) system.SetCloneFlags(command, uintptr(GetNamespaceFlags(container.Namespaces))) + command.SysProcAttr.Pdeathsig = syscall.SIGKILL command.ExtraFiles = []*os.File{pipe} return command From 39d244a593aad63be58d8b2e452715e25135f255 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 16 May 2014 14:10:02 +0200 Subject: [PATCH 124/400] devmapper: Remove sys.go mocking framework This is no longer needed, as the tests are gone. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- .../graphdriver/devmapper/attach_loopback.go | 23 ++++---- daemon/graphdriver/devmapper/deviceset.go | 50 ++++++++-------- daemon/graphdriver/devmapper/devmapper.go | 26 +++++---- daemon/graphdriver/devmapper/driver.go | 8 +-- daemon/graphdriver/devmapper/ioctl.go | 17 +++--- daemon/graphdriver/devmapper/mount.go | 14 +++-- daemon/graphdriver/devmapper/sys.go | 57 ------------------- 7 files changed, 74 insertions(+), 121 deletions(-) delete mode 100644 daemon/graphdriver/devmapper/sys.go diff --git a/daemon/graphdriver/devmapper/attach_loopback.go b/daemon/graphdriver/devmapper/attach_loopback.go index 23339076e..d2ab8c3a4 100644 --- a/daemon/graphdriver/devmapper/attach_loopback.go +++ b/daemon/graphdriver/devmapper/attach_loopback.go @@ -4,6 +4,9 @@ package devmapper import ( "fmt" + "os" + "syscall" + "github.com/dotcloud/docker/utils" ) @@ -14,7 +17,7 @@ func stringToLoopName(src string) [LoNameSize]uint8 { } func getNextFreeLoopbackIndex() (int, error) { - f, err := osOpenFile("/dev/loop-control", osORdOnly, 0644) + f, err := os.OpenFile("/dev/loop-control", os.O_RDONLY, 0644) if err != nil { return 0, err } @@ -27,27 +30,27 @@ func getNextFreeLoopbackIndex() (int, error) { return index, err } -func openNextAvailableLoopback(index int, sparseFile *osFile) (loopFile *osFile, err error) { +func openNextAvailableLoopback(index int, sparseFile *os.File) (loopFile *os.File, err error) { // Start looking for a free /dev/loop for { target := fmt.Sprintf("/dev/loop%d", index) index++ - fi, err := osStat(target) + fi, err := os.Stat(target) if err != nil { - if osIsNotExist(err) { + if os.IsNotExist(err) { utils.Errorf("There are no more loopback device available.") } return nil, ErrAttachLoopbackDevice } - if fi.Mode()&osModeDevice != osModeDevice { + if fi.Mode()&os.ModeDevice != os.ModeDevice { utils.Errorf("Loopback device %s is not a block device.", target) continue } // OpenFile adds O_CLOEXEC - loopFile, err = osOpenFile(target, osORdWr, 0644) + loopFile, err = os.OpenFile(target, os.O_RDWR, 0644) if err != nil { utils.Errorf("Error openning loopback device: %s", err) return nil, ErrAttachLoopbackDevice @@ -58,7 +61,7 @@ func openNextAvailableLoopback(index int, sparseFile *osFile) (loopFile *osFile, loopFile.Close() // If the error is EBUSY, then try the next loopback - if err != sysEBusy { + if err != syscall.EBUSY { utils.Errorf("Cannot set up loopback device %s: %s", target, err) return nil, ErrAttachLoopbackDevice } @@ -80,8 +83,8 @@ func openNextAvailableLoopback(index int, sparseFile *osFile) (loopFile *osFile, } // attachLoopDevice attaches the given sparse file to the next -// available loopback device. It returns an opened *osFile. -func attachLoopDevice(sparseName string) (loop *osFile, err error) { +// available loopback device. It returns an opened *os.File. +func attachLoopDevice(sparseName string) (loop *os.File, err error) { // Try to retrieve the next available loopback device via syscall. // If it fails, we discard error and start loopking for a @@ -92,7 +95,7 @@ func attachLoopDevice(sparseName string) (loop *osFile, err error) { } // OpenFile adds O_CLOEXEC - sparseFile, err := osOpenFile(sparseName, osORdWr, 0644) + sparseFile, err := os.OpenFile(sparseName, os.O_RDWR, 0644) if err != nil { utils.Errorf("Error openning sparse file %s: %s", sparseName, err) return nil, ErrAttachLoopbackDevice diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 79563c108..744f1e11b 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -8,6 +8,8 @@ import ( "fmt" "io" "io/ioutil" + "os" + "os/exec" "path" "path/filepath" "strconv" @@ -135,7 +137,7 @@ func (devices *DeviceSet) hasImage(name string) bool { dirname := devices.loopbackDir() filename := path.Join(dirname, name) - _, err := osStat(filename) + _, err := os.Stat(filename) return err == nil } @@ -147,16 +149,16 @@ func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) { dirname := devices.loopbackDir() filename := path.Join(dirname, name) - if err := osMkdirAll(dirname, 0700); err != nil && !osIsExist(err) { + if err := os.MkdirAll(dirname, 0700); err != nil && !os.IsExist(err) { return "", err } - if _, err := osStat(filename); err != nil { - if !osIsNotExist(err) { + if _, err := os.Stat(filename); err != nil { + if !os.IsNotExist(err) { return "", err } utils.Debugf("Creating loopback file %s for device-manage use", filename) - file, err := osOpenFile(filename, osORdWr|osOCreate, 0600) + file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600) if err != nil { return "", err } @@ -175,7 +177,7 @@ func (devices *DeviceSet) allocateTransactionId() uint64 { } func (devices *DeviceSet) removeMetadata(info *DevInfo) error { - if err := osRemoveAll(devices.metadataFile(info)); err != nil { + if err := os.RemoveAll(devices.metadataFile(info)); err != nil { return fmt.Errorf("Error removing metadata file %s: %s", devices.metadataFile(info), err) } return nil @@ -204,7 +206,7 @@ func (devices *DeviceSet) saveMetadata(info *DevInfo) error { if err := tmpFile.Close(); err != nil { return fmt.Errorf("Error closing metadata file %s: %s", tmpFile.Name(), err) } - if err := osRename(tmpFile.Name(), devices.metadataFile(info)); err != nil { + if err := os.Rename(tmpFile.Name(), devices.metadataFile(info)); err != nil { return fmt.Errorf("Error committing metadata file %s: %s", tmpFile.Name(), err) } @@ -271,9 +273,9 @@ func (devices *DeviceSet) activateDeviceIfNeeded(info *DevInfo) error { func (devices *DeviceSet) createFilesystem(info *DevInfo) error { devname := info.DevName() - err := execRun("mkfs.ext4", "-E", "discard,lazy_itable_init=0,lazy_journal_init=0", devname) + err := exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0,lazy_journal_init=0", devname).Run() if err != nil { - err = execRun("mkfs.ext4", "-E", "discard,lazy_itable_init=0", devname) + err = exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0", devname).Run() } if err != nil { utils.Debugf("\n--->Err: %s\n", err) @@ -298,7 +300,7 @@ func (devices *DeviceSet) initMetaData() error { // Migrate old metadatafile jsonData, err := ioutil.ReadFile(devices.oldMetadataFile()) - if err != nil && !osIsNotExist(err) { + if err != nil && !os.IsNotExist(err) { utils.Debugf("\n--->Err: %s\n", err) return err } @@ -319,7 +321,7 @@ func (devices *DeviceSet) initMetaData() error { devices.saveMetadata(info) } } - if err := osRename(devices.oldMetadataFile(), devices.oldMetadataFile()+".migrated"); err != nil { + if err := os.Rename(devices.oldMetadataFile(), devices.oldMetadataFile()+".migrated"); err != nil { return err } @@ -408,11 +410,11 @@ func (devices *DeviceSet) setupBaseImage() error { func setCloseOnExec(name string) { if fileInfos, _ := ioutil.ReadDir("/proc/self/fd"); fileInfos != nil { for _, i := range fileInfos { - link, _ := osReadlink(filepath.Join("/proc/self/fd", i.Name())) + link, _ := os.Readlink(filepath.Join("/proc/self/fd", i.Name())) if link == name { fd, err := strconv.Atoi(i.Name()) if err == nil { - sysCloseOnExec(fd) + syscall.CloseOnExec(fd) } } } @@ -440,7 +442,7 @@ func (devices *DeviceSet) ResizePool(size int64) error { datafilename := path.Join(dirname, "data") metadatafilename := path.Join(dirname, "metadata") - datafile, err := osOpenFile(datafilename, osORdWr, 0) + datafile, err := os.OpenFile(datafilename, os.O_RDWR, 0) if datafile == nil { return err } @@ -461,7 +463,7 @@ func (devices *DeviceSet) ResizePool(size int64) error { } defer dataloopback.Close() - metadatafile, err := osOpenFile(metadatafilename, osORdWr, 0) + metadatafile, err := os.OpenFile(metadatafilename, os.O_RDWR, 0) if metadatafile == nil { return err } @@ -504,17 +506,17 @@ func (devices *DeviceSet) ResizePool(size int64) error { func (devices *DeviceSet) initDevmapper(doInit bool) error { logInit(devices) - if err := osMkdirAll(devices.metadataDir(), 0700); err != nil && !osIsExist(err) { + if err := os.MkdirAll(devices.metadataDir(), 0700); err != nil && !os.IsExist(err) { return err } // Set the device prefix from the device id and inode of the docker root dir - st, err := osStat(devices.root) + st, err := os.Stat(devices.root) if err != nil { return fmt.Errorf("Error looking up dir %s: %s", devices.root, err) } - sysSt := toSysStatT(st.Sys()) + sysSt := st.Sys().(*syscall.Stat_t) // "reg-" stands for "regular file". // In the future we might use "dev-" for "device file", etc. // docker-maj,min[-inode] stands for: @@ -845,7 +847,7 @@ func (devices *DeviceSet) Shutdown() error { // We use MNT_DETACH here in case it is still busy in some running // container. This means it'll go away from the global scope directly, // and the device will be released when that container dies. - if err := sysUnmount(info.mountPath, syscall.MNT_DETACH); err != nil { + if err := syscall.Unmount(info.mountPath, syscall.MNT_DETACH); err != nil { utils.Debugf("Shutdown unmounting %s, error: %s\n", info.mountPath, err) } @@ -903,13 +905,13 @@ func (devices *DeviceSet) MountDevice(hash, path, mountLabel string) error { return fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err) } - var flags uintptr = sysMsMgcVal + var flags uintptr = syscall.MS_MGC_VAL mountOptions := label.FormatMountLabel("discard", mountLabel) - err = sysMount(info.DevName(), path, "ext4", flags, mountOptions) - if err != nil && err == sysEInval { + err = syscall.Mount(info.DevName(), path, "ext4", flags, mountOptions) + if err != nil && err == syscall.EINVAL { mountOptions = label.FormatMountLabel("", mountLabel) - err = sysMount(info.DevName(), path, "ext4", flags, mountOptions) + err = syscall.Mount(info.DevName(), path, "ext4", flags, mountOptions) } if err != nil { return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err) @@ -946,7 +948,7 @@ func (devices *DeviceSet) UnmountDevice(hash string) error { } utils.Debugf("[devmapper] Unmount(%s)", info.mountPath) - if err := sysUnmount(info.mountPath, 0); err != nil { + if err := syscall.Unmount(info.mountPath, 0); err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } diff --git a/daemon/graphdriver/devmapper/devmapper.go b/daemon/graphdriver/devmapper/devmapper.go index 86af653b4..315f0c0db 100644 --- a/daemon/graphdriver/devmapper/devmapper.go +++ b/daemon/graphdriver/devmapper/devmapper.go @@ -5,9 +5,11 @@ package devmapper import ( "errors" "fmt" - "github.com/dotcloud/docker/utils" + "os" "runtime" "syscall" + + "github.com/dotcloud/docker/utils" ) type DevmapperLogger interface { @@ -184,7 +186,7 @@ func (t *Task) GetNextTarget(next uintptr) (nextPtr uintptr, start uint64, start, length, targetType, params } -func getLoopbackBackingFile(file *osFile) (uint64, uint64, error) { +func getLoopbackBackingFile(file *os.File) (uint64, uint64, error) { loopInfo, err := ioctlLoopGetStatus64(file.Fd()) if err != nil { utils.Errorf("Error get loopback backing file: %s\n", err) @@ -193,7 +195,7 @@ func getLoopbackBackingFile(file *osFile) (uint64, uint64, error) { return loopInfo.loDevice, loopInfo.loInode, nil } -func LoopbackSetCapacity(file *osFile) error { +func LoopbackSetCapacity(file *os.File) error { if err := ioctlLoopSetCapacity(file.Fd(), 0); err != nil { utils.Errorf("Error loopbackSetCapacity: %s", err) return ErrLoopbackSetCapacity @@ -201,20 +203,20 @@ func LoopbackSetCapacity(file *osFile) error { return nil } -func FindLoopDeviceFor(file *osFile) *osFile { +func FindLoopDeviceFor(file *os.File) *os.File { stat, err := file.Stat() if err != nil { return nil } - targetInode := stat.Sys().(*sysStatT).Ino - targetDevice := stat.Sys().(*sysStatT).Dev + targetInode := stat.Sys().(*syscall.Stat_t).Ino + targetDevice := stat.Sys().(*syscall.Stat_t).Dev for i := 0; true; i++ { path := fmt.Sprintf("/dev/loop%d", i) - file, err := osOpenFile(path, osORdWr, 0) + file, err := os.OpenFile(path, os.O_RDWR, 0) if err != nil { - if osIsNotExist(err) { + if os.IsNotExist(err) { return nil } @@ -284,7 +286,7 @@ func RemoveDevice(name string) error { return nil } -func GetBlockDeviceSize(file *osFile) (uint64, error) { +func GetBlockDeviceSize(file *os.File) (uint64, error) { size, err := ioctlBlkGetSize64(file.Fd()) if err != nil { utils.Errorf("Error getblockdevicesize: %s", err) @@ -294,7 +296,7 @@ func GetBlockDeviceSize(file *osFile) (uint64, error) { } func BlockDeviceDiscard(path string) error { - file, err := osOpenFile(path, osORdWr, 0) + file, err := os.OpenFile(path, os.O_RDWR, 0) if err != nil { return err } @@ -317,7 +319,7 @@ func BlockDeviceDiscard(path string) error { } // This is the programmatic example of "dmsetup create" -func createPool(poolName string, dataFile, metadataFile *osFile) error { +func createPool(poolName string, dataFile, metadataFile *os.File) error { task, err := createTask(DeviceCreate, poolName) if task == nil { return err @@ -347,7 +349,7 @@ func createPool(poolName string, dataFile, metadataFile *osFile) error { return nil } -func reloadPool(poolName string, dataFile, metadataFile *osFile) error { +func reloadPool(poolName string, dataFile, metadataFile *os.File) error { task, err := createTask(DeviceReload, poolName) if task == nil { return err diff --git a/daemon/graphdriver/devmapper/driver.go b/daemon/graphdriver/devmapper/driver.go index 9f240d96e..609971cda 100644 --- a/daemon/graphdriver/devmapper/driver.go +++ b/daemon/graphdriver/devmapper/driver.go @@ -26,7 +26,7 @@ type Driver struct { home string } -var Init = func(home string) (graphdriver.Driver, error) { +func Init(home string) (graphdriver.Driver, error) { deviceSet, err := NewDeviceSet(home, true) if err != nil { return nil, err @@ -94,7 +94,7 @@ func (d *Driver) Get(id, mountLabel string) (string, error) { mp := path.Join(d.home, "mnt", id) // Create the target directories if they don't exist - if err := osMkdirAll(mp, 0755); err != nil && !osIsExist(err) { + if err := os.MkdirAll(mp, 0755); err != nil && !os.IsExist(err) { return "", err } @@ -104,13 +104,13 @@ func (d *Driver) Get(id, mountLabel string) (string, error) { } rootFs := path.Join(mp, "rootfs") - if err := osMkdirAll(rootFs, 0755); err != nil && !osIsExist(err) { + if err := os.MkdirAll(rootFs, 0755); err != nil && !os.IsExist(err) { d.DeviceSet.UnmountDevice(id) return "", err } idFile := path.Join(mp, "id") - if _, err := osStat(idFile); err != nil && osIsNotExist(err) { + if _, err := os.Stat(idFile); err != nil && os.IsNotExist(err) { // Create an "id" file with the container/image id in it to help reconscruct this in case // of later problems if err := ioutil.WriteFile(idFile, []byte(id), 0600); err != nil { diff --git a/daemon/graphdriver/devmapper/ioctl.go b/daemon/graphdriver/devmapper/ioctl.go index 30bafff94..8f403da2b 100644 --- a/daemon/graphdriver/devmapper/ioctl.go +++ b/daemon/graphdriver/devmapper/ioctl.go @@ -3,11 +3,12 @@ package devmapper import ( + "syscall" "unsafe" ) func ioctlLoopCtlGetFree(fd uintptr) (int, error) { - index, _, err := sysSyscall(sysSysIoctl, fd, LoopCtlGetFree, 0) + index, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, LoopCtlGetFree, 0) if err != 0 { return 0, err } @@ -15,21 +16,21 @@ func ioctlLoopCtlGetFree(fd uintptr) (int, error) { } func ioctlLoopSetFd(loopFd, sparseFd uintptr) error { - if _, _, err := sysSyscall(sysSysIoctl, loopFd, LoopSetFd, sparseFd); err != 0 { + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, loopFd, LoopSetFd, sparseFd); err != 0 { return err } return nil } func ioctlLoopSetStatus64(loopFd uintptr, loopInfo *LoopInfo64) error { - if _, _, err := sysSyscall(sysSysIoctl, loopFd, LoopSetStatus64, uintptr(unsafe.Pointer(loopInfo))); err != 0 { + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, loopFd, LoopSetStatus64, uintptr(unsafe.Pointer(loopInfo))); err != 0 { return err } return nil } func ioctlLoopClrFd(loopFd uintptr) error { - if _, _, err := sysSyscall(sysSysIoctl, loopFd, LoopClrFd, 0); err != 0 { + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, loopFd, LoopClrFd, 0); err != 0 { return err } return nil @@ -38,14 +39,14 @@ func ioctlLoopClrFd(loopFd uintptr) error { func ioctlLoopGetStatus64(loopFd uintptr) (*LoopInfo64, error) { loopInfo := &LoopInfo64{} - if _, _, err := sysSyscall(sysSysIoctl, loopFd, LoopGetStatus64, uintptr(unsafe.Pointer(loopInfo))); err != 0 { + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, loopFd, LoopGetStatus64, uintptr(unsafe.Pointer(loopInfo))); err != 0 { return nil, err } return loopInfo, nil } func ioctlLoopSetCapacity(loopFd uintptr, value int) error { - if _, _, err := sysSyscall(sysSysIoctl, loopFd, LoopSetCapacity, uintptr(value)); err != 0 { + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, loopFd, LoopSetCapacity, uintptr(value)); err != 0 { return err } return nil @@ -53,7 +54,7 @@ func ioctlLoopSetCapacity(loopFd uintptr, value int) error { func ioctlBlkGetSize64(fd uintptr) (int64, error) { var size int64 - if _, _, err := sysSyscall(sysSysIoctl, fd, BlkGetSize64, uintptr(unsafe.Pointer(&size))); err != 0 { + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, BlkGetSize64, uintptr(unsafe.Pointer(&size))); err != 0 { return 0, err } return size, nil @@ -64,7 +65,7 @@ func ioctlBlkDiscard(fd uintptr, offset, length uint64) error { r[0] = offset r[1] = length - if _, _, err := sysSyscall(sysSysIoctl, fd, BlkDiscard, uintptr(unsafe.Pointer(&r[0]))); err != 0 { + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, BlkDiscard, uintptr(unsafe.Pointer(&r[0]))); err != 0 { return err } return nil diff --git a/daemon/graphdriver/devmapper/mount.go b/daemon/graphdriver/devmapper/mount.go index 4f19109bf..6de9e46c8 100644 --- a/daemon/graphdriver/devmapper/mount.go +++ b/daemon/graphdriver/devmapper/mount.go @@ -3,25 +3,27 @@ package devmapper import ( + "os" "path/filepath" + "syscall" ) // FIXME: this is copy-pasted from the aufs driver. // It should be moved into the core. -var Mounted = func(mountpoint string) (bool, error) { - mntpoint, err := osStat(mountpoint) +func Mounted(mountpoint string) (bool, error) { + mntpoint, err := os.Stat(mountpoint) if err != nil { - if osIsNotExist(err) { + if os.IsNotExist(err) { return false, nil } return false, err } - parent, err := osStat(filepath.Join(mountpoint, "..")) + parent, err := os.Stat(filepath.Join(mountpoint, "..")) if err != nil { return false, err } - mntpointSt := toSysStatT(mntpoint.Sys()) - parentSt := toSysStatT(parent.Sys()) + mntpointSt := mntpoint.Sys().(*syscall.Stat_t) + parentSt := parent.Sys().(*syscall.Stat_t) return mntpointSt.Dev != parentSt.Dev, nil } diff --git a/daemon/graphdriver/devmapper/sys.go b/daemon/graphdriver/devmapper/sys.go deleted file mode 100644 index 5a9ab4d74..000000000 --- a/daemon/graphdriver/devmapper/sys.go +++ /dev/null @@ -1,57 +0,0 @@ -// +build linux,amd64 - -package devmapper - -import ( - "os" - "os/exec" - "syscall" -) - -type ( - sysStatT syscall.Stat_t - sysErrno syscall.Errno - - osFile struct{ *os.File } -) - -var ( - sysMount = syscall.Mount - sysUnmount = syscall.Unmount - sysCloseOnExec = syscall.CloseOnExec - sysSyscall = syscall.Syscall - - osOpenFile = func(name string, flag int, perm os.FileMode) (*osFile, error) { - f, err := os.OpenFile(name, flag, perm) - return &osFile{File: f}, err - } - osOpen = func(name string) (*osFile, error) { f, err := os.Open(name); return &osFile{File: f}, err } - osNewFile = os.NewFile - osCreate = os.Create - osStat = os.Stat - osIsNotExist = os.IsNotExist - osIsExist = os.IsExist - osMkdirAll = os.MkdirAll - osRemoveAll = os.RemoveAll - osRename = os.Rename - osReadlink = os.Readlink - - execRun = func(name string, args ...string) error { return exec.Command(name, args...).Run() } -) - -const ( - sysMsMgcVal = syscall.MS_MGC_VAL - sysMsRdOnly = syscall.MS_RDONLY - sysEInval = syscall.EINVAL - sysSysIoctl = syscall.SYS_IOCTL - sysEBusy = syscall.EBUSY - - osORdOnly = os.O_RDONLY - osORdWr = os.O_RDWR - osOCreate = os.O_CREATE - osModeDevice = os.ModeDevice -) - -func toSysStatT(i interface{}) *sysStatT { - return (*sysStatT)(i.(*syscall.Stat_t)) -} From b8e338144e90a6bb76110ab04cc216966640a3f4 Mon Sep 17 00:00:00 2001 From: unclejack Date: Fri, 16 May 2014 15:23:47 +0300 Subject: [PATCH 125/400] run: pull only latest when no tag specified This makes Docker pull only the image tagged as latest when no tag has been specified. This makes Docker pull only the image it'll run. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- api/client/commands.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/client/commands.go b/api/client/commands.go index f60b6cebd..502c1be69 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -1840,6 +1840,10 @@ func (cli *DockerCli) CmdRun(args ...string) error { v := url.Values{} repos, tag := utils.ParseRepositoryTag(config.Image) + // pull only the image tagged 'latest' if no tag was specified + if tag == "" { + tag = "latest" + } v.Set("fromImage", repos) v.Set("tag", tag) From a598dba1346c9f2e878e8250a9d641a2206cb4e5 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Fri, 16 May 2014 09:08:59 -0400 Subject: [PATCH 126/400] Wait for pidfile to exist when starting with Redhat init script Fixes #5359 Docker-DCO-1.1-Signed-off-by: Chris St. Pierre (github: stpierre) --- contrib/init/sysvinit-redhat/docker | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contrib/init/sysvinit-redhat/docker b/contrib/init/sysvinit-redhat/docker index 2b75c6903..7d056aaca 100755 --- a/contrib/init/sysvinit-redhat/docker +++ b/contrib/init/sysvinit-redhat/docker @@ -49,6 +49,11 @@ start() { $exec -d $other_args &>> $logfile & pid=$! touch $lockfile + # wait for the pidfile to exist. see + # https://github.com/dotcloud/docker/issues/5359 + while [ ! -f $pidfile ]; do + sleep 1 + done success echo else From 1ee423bd5dd7abf6f5f4662132b77fdebf14529a Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Fri, 16 May 2014 09:36:54 -0400 Subject: [PATCH 127/400] added timeout when waiting for docker pidfile Docker-DCO-1.1-Signed-off-by: Chris St. Pierre (github: stpierre) --- contrib/init/sysvinit-redhat/docker | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/contrib/init/sysvinit-redhat/docker b/contrib/init/sysvinit-redhat/docker index 7d056aaca..06699f6ab 100755 --- a/contrib/init/sysvinit-redhat/docker +++ b/contrib/init/sysvinit-redhat/docker @@ -3,7 +3,7 @@ # /etc/rc.d/init.d/docker # # Daemon for docker.io -# +# # chkconfig: 2345 95 95 # description: Daemon for docker.io @@ -49,10 +49,12 @@ start() { $exec -d $other_args &>> $logfile & pid=$! touch $lockfile - # wait for the pidfile to exist. see + # wait up to 10 seconds for the pidfile to exist. see # https://github.com/dotcloud/docker/issues/5359 - while [ ! -f $pidfile ]; do + tries=0 + while [ ! -f $pidfile -a $tries -lt 10 ]; do sleep 1 + tries=$((tries + 1)) done success echo From ba0c8292917560b45f840f187c2a8f452550705d Mon Sep 17 00:00:00 2001 From: unclejack Date: Fri, 16 May 2014 15:04:19 +0300 Subject: [PATCH 128/400] contrib/init/upstart: start on local-filesystems This changes the upstart init script to start on `local-filesystems`. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- contrib/init/upstart/docker.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/init/upstart/docker.conf b/contrib/init/upstart/docker.conf index e27d77e14..db00e4f47 100644 --- a/contrib/init/upstart/docker.conf +++ b/contrib/init/upstart/docker.conf @@ -1,6 +1,6 @@ description "Docker daemon" -start on filesystem +start on local-filesystems stop on runlevel [!2345] limit nofile 524288 1048576 limit nproc 524288 1048576 From 8b31824ad6118ed505b43443816c1492e3d81c8c Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Fri, 16 May 2014 03:31:16 -0700 Subject: [PATCH 129/400] server/buildfile: correct daemon.Create comments s/and start it// Docker-DCO-1.1-Signed-off-by: Johan Euphrosine (github: proppy) --- server/buildfile.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/buildfile.go b/server/buildfile.go index faaac0d3d..9387a8787 100644 --- a/server/buildfile.go +++ b/server/buildfile.go @@ -637,7 +637,7 @@ func (b *buildFile) CmdAdd(args string) error { } } - // Create the container and start it + // Create the container container, _, err := b.daemon.Create(b.config, "") if err != nil { return err @@ -666,7 +666,7 @@ func (b *buildFile) create() (*daemon.Container, error) { } b.config.Image = b.image - // Create the container and start it + // Create the container c, _, err := b.daemon.Create(b.config, "") if err != nil { return nil, err From d787f2731e4242f244e88f047032ad9650f1f8d7 Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Thu, 15 May 2014 21:36:15 -0700 Subject: [PATCH 130/400] fix panic when passing empty environment Docker-DCO-1.1-Signed-off-by: Sridhar Ratnakumar (github: srid) --- pkg/libcontainer/nsinit/init.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 3bbcfcc65..af60d739c 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -152,6 +152,9 @@ func LoadContainerEnvironment(container *libcontainer.Container) error { os.Clearenv() for _, pair := range container.Env { p := strings.SplitN(pair, "=", 2) + if len(p) < 2 { + return fmt.Errorf("invalid environment '%v'", pair) + } if err := os.Setenv(p[0], p[1]); err != nil { return err } From 2e0d47e5c4c63323623c38cf0ca8d63efab8b493 Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Fri, 16 May 2014 14:31:43 -0400 Subject: [PATCH 131/400] Add description of --volume read-only/read-write to docker-run man page. Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) --- contrib/man/md/docker-run.1.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/contrib/man/md/docker-run.1.md b/contrib/man/md/docker-run.1.md index 56364f9d5..3cf56c24b 100644 --- a/contrib/man/md/docker-run.1.md +++ b/contrib/man/md/docker-run.1.md @@ -190,18 +190,28 @@ interactive shell. The default is value is false. Set a username or UID for the container. -**-v**, **-volume**=*volume* - Bind mount a volume to the container. The **-v** option can be used one or +**-v**, **-volume**=*volume*[:ro|:rw] + Bind mount a volume to the container. + +The **-v** option can be used one or more times to add one or more mounts to a container. These mounts can then be -used in other containers using the **--volumes-from** option. See examples. +used in other containers using the **--volumes-from** option. +The volume may be optionally suffixed with :ro or :rw to mount the volumes in +read-only or read-write mode, respectively. By default, the volumes are mounted +read-write. See examples. -**--volumes-from**=*container-id* +**--volumes-from**=*container-id*[:ro|:rw] Will mount volumes from the specified container identified by container-id. Once a volume is mounted in a one container it can be shared with other containers using the **--volumes-from** option when running those other containers. The volumes can be shared even if the original container with the -mount is not running. +mount is not running. + +The container ID may be optionally suffixed with :ro or +:rw to mount the volumes in read-only or read-write mode, respectively. By +default, the volumes are mounted in the same mode (read write or read only) as +the reference container. **-w**, **-workdir**=*directory* From 311614952ea64315075d827c880debbdfb503640 Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Fri, 16 May 2014 12:44:42 -0700 Subject: [PATCH 132/400] archive: update TarFilter comments Docker-DCO-1.1-Signed-off-by: Johan Euphrosine (github: proppy) --- archive/archive.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/archive/archive.go b/archive/archive.go index e21b10ca0..a22da4ee5 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -6,9 +6,6 @@ import ( "compress/gzip" "errors" "fmt" - "github.com/dotcloud/docker/pkg/system" - "github.com/dotcloud/docker/utils" - "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" "io" "io/ioutil" "os" @@ -17,6 +14,10 @@ import ( "path/filepath" "strings" "syscall" + + "github.com/dotcloud/docker/pkg/system" + "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) type ( @@ -309,8 +310,11 @@ func escapeName(name string) string { return string(escaped) } -// Tar creates an archive from the directory at `path`, only including files whose relative -// paths are included in `filter`. If `filter` is nil, then all files are included. +// TarFilter creates an archive from the directory at `srcPath` with `options`, and returns it as a +// stream of bytes. +// +// Files are included according to `options.Includes`, default to including all files. +// Stream is compressed according to `options.Compression', default to Uncompressed. func TarFilter(srcPath string, options *TarOptions) (io.ReadCloser, error) { pipeReader, pipeWriter := io.Pipe() From a42e451e10bc7383c82933eb4073e70aaf5aaa8f Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Fri, 16 May 2014 14:23:37 -0700 Subject: [PATCH 133/400] chore(graph/graph): remove dead code This code was introduced with commented out sections. Just delete them. Original commit: 18fc707fdf06aeb50fa5250f59f0ef4597d7cf73 Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- graph/graph.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/graph/graph.go b/graph/graph.go index 5de9cbe7a..edca42f42 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -263,8 +263,6 @@ func SetupInitLayer(initLayer string) error { "/etc/hostname": "file", "/dev/console": "file", "/etc/mtab": "/proc/mounts", - // "var/run": "dir", - // "var/lock": "dir", } { parts := strings.Split(pth, "/") prev := "/" From 366f084eb0864db6dcf5cc47e434b3c6f43500d7 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 27 Mar 2014 19:11:23 +0000 Subject: [PATCH 134/400] add test Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- integration/commands_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/integration/commands_test.go b/integration/commands_test.go index 5b967b68c..7164cc5af 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -1163,3 +1163,27 @@ func TestCmdKill(t *testing.T) { closeWrap(stdin, stdinPipe, stdout, stdoutPipe) } + +func TestRunTTYCommitRun(t *testing.T) { + cli := api.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + + defer cleanup(globalEngine, t) + + ch := make(chan struct{}) + go func() { + defer close(ch) + cli.CmdRun("-t", unitTestImageID, "/bin/ls") + }() + + container := waitContainerStart(t, 10*time.Second) + time.Sleep(500 * time.Millisecond) + setTimeout(t, "Waiting for container timedout", 5*time.Second, func() { + <-ch + }) + + cli.CmdCommit(container.ID, "ttytest") + + if err := cli.CmdRun("ttytest", "/bin/ls"); err != nil { + t.Fatal(err) + } +} From 2c8b63cb754128a64083426e5c3e6016118e78df Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 27 Mar 2014 19:16:03 +0000 Subject: [PATCH 135/400] do not merge -i or -t options Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- integration-cli/docker_cli_commit_test.go | 21 +++++++++++++ integration/commands_test.go | 37 +++++------------------ runconfig/merge.go | 9 ------ 3 files changed, 28 insertions(+), 39 deletions(-) diff --git a/integration-cli/docker_cli_commit_test.go b/integration-cli/docker_cli_commit_test.go index e99379231..7635add91 100644 --- a/integration-cli/docker_cli_commit_test.go +++ b/integration-cli/docker_cli_commit_test.go @@ -62,3 +62,24 @@ func TestCommitNewFile(t *testing.T) { logDone("commit - commit file and read") } + +func TestCommitTTY(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-t", "--name", "tty", "busybox", "/bin/ls") + + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + + cmd = exec.Command(dockerBinary, "commit", "tty", "ttytest") + imageId, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err) + } + imageId = strings.Trim(imageId, "\r\n") + + cmd = exec.Command(dockerBinary, "run", "ttytest", "/bin/ls") + + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } +} diff --git a/integration/commands_test.go b/integration/commands_test.go index 7164cc5af..b91ba603a 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -3,12 +3,6 @@ package docker import ( "bufio" "fmt" - "github.com/dotcloud/docker/api/client" - "github.com/dotcloud/docker/daemon" - "github.com/dotcloud/docker/engine" - "github.com/dotcloud/docker/image" - "github.com/dotcloud/docker/pkg/term" - "github.com/dotcloud/docker/utils" "io" "io/ioutil" "os" @@ -19,6 +13,13 @@ import ( "syscall" "testing" "time" + + "github.com/dotcloud/docker/api/client" + "github.com/dotcloud/docker/daemon" + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/image" + "github.com/dotcloud/docker/pkg/term" + "github.com/dotcloud/docker/utils" ) func closeWrap(args ...io.Closer) error { @@ -1163,27 +1164,3 @@ func TestCmdKill(t *testing.T) { closeWrap(stdin, stdinPipe, stdout, stdoutPipe) } - -func TestRunTTYCommitRun(t *testing.T) { - cli := api.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) - - defer cleanup(globalEngine, t) - - ch := make(chan struct{}) - go func() { - defer close(ch) - cli.CmdRun("-t", unitTestImageID, "/bin/ls") - }() - - container := waitContainerStart(t, 10*time.Second) - time.Sleep(500 * time.Millisecond) - setTimeout(t, "Waiting for container timedout", 5*time.Second, func() { - <-ch - }) - - cli.CmdCommit(container.ID, "ttytest") - - if err := cli.CmdRun("ttytest", "/bin/ls"); err != nil { - t.Fatal(err) - } -} diff --git a/runconfig/merge.go b/runconfig/merge.go index 1240dbcac..252a2fe43 100644 --- a/runconfig/merge.go +++ b/runconfig/merge.go @@ -65,15 +65,6 @@ func Merge(userConf, imageConf *Config) error { } } - if !userConf.Tty { - userConf.Tty = imageConf.Tty - } - if !userConf.OpenStdin { - userConf.OpenStdin = imageConf.OpenStdin - } - if !userConf.StdinOnce { - userConf.StdinOnce = imageConf.StdinOnce - } if userConf.Env == nil || len(userConf.Env) == 0 { userConf.Env = imageConf.Env } else { From 3118952e0a63b42b830fc846a56b891d14902976 Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Fri, 16 May 2014 12:15:21 -0700 Subject: [PATCH 136/400] archive: simplify DetectCompression Docker-DCO-1.1-Signed-off-by: Johan Euphrosine (github: proppy) --- archive/archive.go | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/archive/archive.go b/archive/archive.go index 5d29ce819..c72849097 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -42,26 +42,12 @@ const ( ) func DetectCompression(source []byte) Compression { - sourceLen := len(source) for compression, m := range map[Compression][]byte{ Bzip2: {0x42, 0x5A, 0x68}, Gzip: {0x1F, 0x8B, 0x08}, Xz: {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}, } { - fail := false - if len(m) > sourceLen { - utils.Debugf("Len too short") - continue - } - i := 0 - for _, b := range m { - if b != source[i] { - fail = true - break - } - i++ - } - if !fail { + if bytes.Compare(m, source[:len(m)]) == 0 { return compression } } From 67ce59a806007cd28d96fd11208c4a113d1b92e0 Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Fri, 16 May 2014 12:47:50 -0700 Subject: [PATCH 137/400] archive: fix panic if len(source) < len(m) Docker-DCO-1.1-Signed-off-by: Johan Euphrosine (github: proppy) --- archive/archive.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/archive/archive.go b/archive/archive.go index c72849097..1eda6c2de 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -47,6 +47,10 @@ func DetectCompression(source []byte) Compression { Gzip: {0x1F, 0x8B, 0x08}, Xz: {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}, } { + if len(source) < len(m) { + utils.Debugf("Len too short") + continue + } if bytes.Compare(m, source[:len(m)]) == 0 { return compression } From 4a3aefbb5232433098c9d27423a10031eb17ff93 Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Fri, 16 May 2014 16:26:28 -0700 Subject: [PATCH 138/400] archive: add missing bytes package Docker-DCO-1.1-Signed-off-by: Johan Euphrosine (github: proppy) --- archive/archive.go | 1 + 1 file changed, 1 insertion(+) diff --git a/archive/archive.go b/archive/archive.go index 1eda6c2de..160ea0737 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -2,6 +2,7 @@ package archive import ( "bufio" + "bytes" "compress/bzip2" "compress/gzip" "errors" From 46578a2359714e32d61af4efaeea86b7ff770359 Mon Sep 17 00:00:00 2001 From: unclejack Date: Tue, 13 May 2014 21:39:59 +0300 Subject: [PATCH 139/400] integcli: resolve full path to docker binary Setting dockerBinary to the full path of the Docker binary is a good idea and this is now done in the test code. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- integration-cli/docker_test_vars.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/integration-cli/docker_test_vars.go b/integration-cli/docker_test_vars.go index f8bd5c116..78e0685a9 100644 --- a/integration-cli/docker_test_vars.go +++ b/integration-cli/docker_test_vars.go @@ -1,7 +1,9 @@ package main import ( + "fmt" "os" + "os/exec" ) // the docker binary to use @@ -18,6 +20,15 @@ var workingDirectory string func init() { if dockerBin := os.Getenv("DOCKER_BINARY"); dockerBin != "" { dockerBinary = dockerBin + } else { + whichCmd := exec.Command("which", "docker") + out, _, err := runCommandWithOutput(whichCmd) + if err == nil { + dockerBinary = stripTrailingCharacters(out) + } else { + fmt.Printf("ERROR: couldn't resolve full path to the Docker binary") + os.Exit(1) + } } if registryImage := os.Getenv("REGISTRY_IMAGE"); registryImage != "" { registryImageName = registryImage From 1dedcd0d376f57abae5cadd38116c1aca2821330 Mon Sep 17 00:00:00 2001 From: unclejack Date: Sat, 10 May 2014 10:58:45 +0300 Subject: [PATCH 140/400] add ValidateContextDirectory to utils/utils.go This commit adds a function which can be used to ensure all contents of a directory can be accessed. This function doesn't follow symlinks to check if they're pointing to files which exist. Such symlinks can be useful later. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- utils/utils.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/utils/utils.go b/utils/utils.go index 191c85206..c3ae4e569 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1067,3 +1067,40 @@ func TreeSize(dir string) (size int64, err error) { }) return } + +// ValidateContextDirectory checks if all the contents of the directory +// can be read and returns an error if some files can't be read +// symlinks which point to non-existing files don't trigger an error +func ValidateContextDirectory(srcPath string) error { + var finalError error + + filepath.Walk(filepath.Join(srcPath, "."), func(filePath string, f os.FileInfo, err error) error { + // skip this directory/file if it's not in the path, it won't get added to the context + _, err = filepath.Rel(srcPath, filePath) + if err != nil && os.IsPermission(err) { + return nil + } + + if _, err := os.Stat(filePath); err != nil && os.IsPermission(err) { + finalError = fmt.Errorf("can't stat '%s'", filePath) + return err + } + // skip checking if symlinks point to non-existing files, such symlinks can be useful + lstat, _ := os.Lstat(filePath) + if lstat.Mode()&os.ModeSymlink == os.ModeSymlink { + return err + } + + if !f.IsDir() { + currentFile, err := os.Open(filePath) + if err != nil && os.IsPermission(err) { + finalError = fmt.Errorf("no permission to read from '%s'", filePath) + return err + } else { + currentFile.Close() + } + } + return nil + }) + return finalError +} From 33d5b38d62f48edcadbe239595e242300f3ecd27 Mon Sep 17 00:00:00 2001 From: unclejack Date: Sat, 10 May 2014 11:03:53 +0300 Subject: [PATCH 141/400] use ValidateContextDirectory to validate context This commit makes the Docker cli client use ValidateContextDirectory before attempting to create a tarball out of the context. This ensures we avoid errors such as "unexpected EOF" during the upload of the context. This check is done before uploading any data and can save time and bandwidth for remote Docker daemons. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- api/client/commands.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api/client/commands.go b/api/client/commands.go index 502c1be69..f7eca7d35 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -161,6 +161,9 @@ func (cli *DockerCli) CmdBuild(args ...string) error { if _, err = os.Stat(filename); os.IsNotExist(err) { return fmt.Errorf("no Dockerfile found in %s", cmd.Arg(0)) } + if err = utils.ValidateContextDirectory(root); err != nil { + return fmt.Errorf("Error checking context is accessible: '%s'. Please check permissions and try again.", err) + } context, err = archive.Tar(root, archive.Uncompressed) } var body io.Reader From f5b1afae74a62ba9a4c89f3372dd6e5e5cb89dbf Mon Sep 17 00:00:00 2001 From: unclejack Date: Tue, 13 May 2014 21:49:12 +0300 Subject: [PATCH 142/400] add test for issue #5270 Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- .../inaccessibledirectory/Dockerfile | 2 + .../directoryWeCantStat/bar | 1 + .../inaccessiblefile/Dockerfile | 2 + .../inaccessiblefile/fileWithoutReadAccess | 1 + .../linksdirectory/Dockerfile | 2 + .../linksdirectory/g | 1 + integration-cli/docker_cli_build_test.go | 88 +++++++++++++++++++ 7 files changed, 97 insertions(+) create mode 100644 integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessibledirectory/Dockerfile create mode 100644 integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessibledirectory/directoryWeCantStat/bar create mode 100644 integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessiblefile/Dockerfile create mode 100644 integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessiblefile/fileWithoutReadAccess create mode 100644 integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/linksdirectory/Dockerfile create mode 120000 integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/linksdirectory/g diff --git a/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessibledirectory/Dockerfile b/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessibledirectory/Dockerfile new file mode 100644 index 000000000..0964b8e87 --- /dev/null +++ b/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessibledirectory/Dockerfile @@ -0,0 +1,2 @@ +FROM busybox +ADD . /foo/ diff --git a/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessibledirectory/directoryWeCantStat/bar b/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessibledirectory/directoryWeCantStat/bar new file mode 100644 index 000000000..257cc5642 --- /dev/null +++ b/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessibledirectory/directoryWeCantStat/bar @@ -0,0 +1 @@ +foo diff --git a/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessiblefile/Dockerfile b/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessiblefile/Dockerfile new file mode 100644 index 000000000..0964b8e87 --- /dev/null +++ b/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessiblefile/Dockerfile @@ -0,0 +1,2 @@ +FROM busybox +ADD . /foo/ diff --git a/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessiblefile/fileWithoutReadAccess b/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessiblefile/fileWithoutReadAccess new file mode 100644 index 000000000..b25f9a2a1 --- /dev/null +++ b/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessiblefile/fileWithoutReadAccess @@ -0,0 +1 @@ +should make `docker build` throw an error diff --git a/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/linksdirectory/Dockerfile b/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/linksdirectory/Dockerfile new file mode 100644 index 000000000..0964b8e87 --- /dev/null +++ b/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/linksdirectory/Dockerfile @@ -0,0 +1,2 @@ +FROM busybox +ADD . /foo/ diff --git a/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/linksdirectory/g b/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/linksdirectory/g new file mode 120000 index 000000000..5fc3f3392 --- /dev/null +++ b/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/linksdirectory/g @@ -0,0 +1 @@ +../../../../../../../../../../../../../../../../../../../azA \ No newline at end of file diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index cd3e06d60..c4d6f1292 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -2,8 +2,10 @@ package main import ( "fmt" + "os" "os/exec" "path/filepath" + "strings" "testing" ) @@ -119,6 +121,92 @@ func TestAddWholeDirToRoot(t *testing.T) { logDone("build - add whole directory to root") } +// Issue #5270 - ensure we throw a better error than "unexpected EOF" +// when we can't access files in the context. +func TestBuildWithInaccessibleFilesInContext(t *testing.T) { + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestBuildWithInaccessibleFilesInContext") + addUserCmd := exec.Command("adduser", "unprivilegeduser") + out, _, err := runCommandWithOutput(addUserCmd) + errorOut(err, t, fmt.Sprintf("failed to add user: %v %v", out, err)) + + { + // This is used to ensure we detect inaccessible files early during build in the cli client + pathToInaccessibleFileBuildDirectory := filepath.Join(buildDirectory, "inaccessiblefile") + pathToFileWithoutReadAccess := filepath.Join(pathToInaccessibleFileBuildDirectory, "fileWithoutReadAccess") + + err = os.Chown(pathToFileWithoutReadAccess, 0, 0) + errorOut(err, t, fmt.Sprintf("failed to chown file to root: %s", err)) + err = os.Chmod(pathToFileWithoutReadAccess, 0700) + errorOut(err, t, fmt.Sprintf("failed to chmod file to 700: %s", err)) + + buildCommandStatement := fmt.Sprintf("%s build -t inaccessiblefiles .", dockerBinary) + buildCmd := exec.Command("su", "unprivilegeduser", "-c", buildCommandStatement) + buildCmd.Dir = pathToInaccessibleFileBuildDirectory + out, exitCode, err := runCommandWithOutput(buildCmd) + if err == nil || exitCode == 0 { + t.Fatalf("build should have failed: %s %s", err, out) + } + + // check if we've detected the failure before we started building + if !strings.Contains(out, "no permission to read from ") { + t.Fatalf("output should've contained the string: no permission to read from ") + } + + if !strings.Contains(out, "Error checking context is accessible") { + t.Fatalf("output should've contained the string: Error checking context is accessible") + } + } + { + // This is used to ensure we detect inaccessible directories early during build in the cli client + pathToInaccessibleDirectoryBuildDirectory := filepath.Join(buildDirectory, "inaccessibledirectory") + pathToDirectoryWithoutReadAccess := filepath.Join(pathToInaccessibleDirectoryBuildDirectory, "directoryWeCantStat") + pathToFileInDirectoryWithoutReadAccess := filepath.Join(pathToDirectoryWithoutReadAccess, "bar") + + err = os.Chown(pathToDirectoryWithoutReadAccess, 0, 0) + errorOut(err, t, fmt.Sprintf("failed to chown directory to root: %s", err)) + err = os.Chmod(pathToDirectoryWithoutReadAccess, 0444) + errorOut(err, t, fmt.Sprintf("failed to chmod directory to 755: %s", err)) + err = os.Chmod(pathToFileInDirectoryWithoutReadAccess, 0700) + errorOut(err, t, fmt.Sprintf("failed to chmod file to 444: %s", err)) + + buildCommandStatement := fmt.Sprintf("%s build -t inaccessiblefiles .", dockerBinary) + buildCmd := exec.Command("su", "unprivilegeduser", "-c", buildCommandStatement) + buildCmd.Dir = pathToInaccessibleDirectoryBuildDirectory + out, exitCode, err := runCommandWithOutput(buildCmd) + if err == nil || exitCode == 0 { + t.Fatalf("build should have failed: %s %s", err, out) + } + + // check if we've detected the failure before we started building + if !strings.Contains(out, "can't stat") { + t.Fatalf("output should've contained the string: can't access %s", out) + } + + if !strings.Contains(out, "Error checking context is accessible") { + t.Fatalf("output should've contained the string: Error checking context is accessible") + } + + } + { + // This is used to ensure we don't follow links when checking if everything in the context is accessible + // This test doesn't require that we run commands as an unprivileged user + pathToDirectoryWhichContainsLinks := filepath.Join(buildDirectory, "linksdirectory") + + buildCmd := exec.Command(dockerBinary, "build", "-t", "testlinksok", ".") + buildCmd.Dir = pathToDirectoryWhichContainsLinks + out, exitCode, err := runCommandWithOutput(buildCmd) + if err != nil || exitCode != 0 { + t.Fatalf("build should have worked: %s %s", err, out) + } + + deleteImages("testlinksok") + + } + deleteImages("inaccessiblefiles") + logDone("build - ADD from context with inaccessible files must fail") + logDone("build - ADD from context with accessible links must work") +} + // TODO: TestCaching // TODO: TestADDCacheInvalidation From 42734394b0ec238c88bc3ef09454df411b8f3776 Mon Sep 17 00:00:00 2001 From: Derek Date: Fri, 16 May 2014 16:46:22 -0700 Subject: [PATCH 143/400] Remove Trailing Whitespace in User-Agent After removed, the User-Agent shows in log like this: [debug] http.go:160 https://index.docker.io/v1/repositories/busybox/images -- HEADERS: map[User-Agent:[docker/0.11.1-dev go/go1.2.2 git-commit/8887e00-dirty kernel/3.14.3-n1 os/linux arch/amd64]] The code also moved all validation work into validVersion, to keep the main logic as clean. Docker-DCO-1.1-Signed-off-by: Derek (github: crquan) --- utils/http.go | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/utils/http.go b/utils/http.go index 68e93d8eb..e19363379 100644 --- a/utils/http.go +++ b/utils/http.go @@ -1,7 +1,6 @@ package utils import ( - "bytes" "io" "net/http" "strings" @@ -15,11 +14,13 @@ type VersionInfo interface { } func validVersion(version VersionInfo) bool { - stopChars := " \t\r\n/" - if strings.ContainsAny(version.Name(), stopChars) { + const stopChars = " \t\r\n/" + name := version.Name() + vers := version.Version() + if len(name) == 0 || strings.ContainsAny(name, stopChars) { return false } - if strings.ContainsAny(version.Version(), stopChars) { + if len(vers) == 0 || strings.ContainsAny(vers, stopChars) { return false } return true @@ -36,27 +37,18 @@ func appendVersions(base string, versions ...VersionInfo) string { return base } - var buf bytes.Buffer + verstrs := make([]string, 0, 1+len(versions)) if len(base) > 0 { - buf.Write([]byte(base)) + verstrs = append(verstrs, base) } for _, v := range versions { - name := []byte(v.Name()) - version := []byte(v.Version()) - - if len(name) == 0 || len(version) == 0 { - continue - } if !validVersion(v) { continue } - buf.Write([]byte(v.Name())) - buf.Write([]byte("/")) - buf.Write([]byte(v.Version())) - buf.Write([]byte(" ")) + verstrs = append(verstrs, v.Name()+"/"+v.Version()) } - return buf.String() + return strings.Join(verstrs, " ") } // HTTPRequestDecorator is used to change an instance of From 92614928cecd48b241011e614fa856c4fdbac1f6 Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Sat, 17 May 2014 00:44:10 +0000 Subject: [PATCH 144/400] Make libcontainer's CapabilitiesMask into a []string (Capabilities). Docker-DCO-1.1-Signed-off-by: Victor Marmol (github: vmarmol) --- .../execdriver/native/configuration/parse.go | 11 +++++-- .../native/configuration/parse_test.go | 25 +++++++++++----- daemon/execdriver/native/create.go | 4 +-- .../native/template/default_template.go | 29 +++++-------------- pkg/libcontainer/container.go | 26 ++++++++--------- pkg/libcontainer/container.json | 21 ++------------ pkg/libcontainer/container_test.go | 21 +++++++++----- .../security/capabilities/capabilities.go | 10 +++---- pkg/libcontainer/types.go | 8 +++++ 9 files changed, 75 insertions(+), 80 deletions(-) diff --git a/daemon/execdriver/native/configuration/parse.go b/daemon/execdriver/native/configuration/parse.go index 3767f5a86..f18a60f79 100644 --- a/daemon/execdriver/native/configuration/parse.go +++ b/daemon/execdriver/native/configuration/parse.go @@ -109,12 +109,19 @@ func memorySwap(container *libcontainer.Container, context interface{}, value st } func addCap(container *libcontainer.Container, context interface{}, value string) error { - container.CapabilitiesMask[value] = true + container.Capabilities = append(container.Capabilities, value) return nil } func dropCap(container *libcontainer.Container, context interface{}, value string) error { - container.CapabilitiesMask[value] = false + // If the capability is specified multiple times, remove all instances. + for i, capability := range container.Capabilities { + if capability == value { + container.Capabilities = append(container.Capabilities[:i], container.Capabilities[i+1:]...) + } + } + + // The capability wasn't found so we will drop it anyways. return nil } diff --git a/daemon/execdriver/native/configuration/parse_test.go b/daemon/execdriver/native/configuration/parse_test.go index 1b0316b68..5524adb85 100644 --- a/daemon/execdriver/native/configuration/parse_test.go +++ b/daemon/execdriver/native/configuration/parse_test.go @@ -4,8 +4,19 @@ import ( "testing" "github.com/dotcloud/docker/daemon/execdriver/native/template" + "github.com/dotcloud/docker/pkg/libcontainer" ) +// Checks whether the expected capability is specified in the capabilities. +func hasCapability(expected string, capabilities []string) bool { + for _, capability := range capabilities { + if capability == expected { + return true + } + } + return false +} + func TestSetReadonlyRootFs(t *testing.T) { var ( container = template.New() @@ -39,10 +50,10 @@ func TestConfigurationsDoNotConflict(t *testing.T) { t.Fatal(err) } - if !container1.CapabilitiesMask["NET_ADMIN"] { + if !hasCapability("NET_ADMIN", container1.Capabilities) { t.Fatal("container one should have NET_ADMIN enabled") } - if container2.CapabilitiesMask["NET_ADMIN"] { + if hasCapability("NET_ADMIN", container2.Capabilities) { t.Fatal("container two should not have NET_ADMIN enabled") } } @@ -138,10 +149,10 @@ func TestAddCap(t *testing.T) { t.Fatal(err) } - if !container.CapabilitiesMask["MKNOD"] { + if !hasCapability("MKNOD", container.Capabilities) { t.Fatal("container should have MKNOD enabled") } - if !container.CapabilitiesMask["SYS_ADMIN"] { + if !hasCapability("SYS_ADMIN", container.Capabilities) { t.Fatal("container should have SYS_ADMIN enabled") } } @@ -154,14 +165,12 @@ func TestDropCap(t *testing.T) { } ) // enabled all caps like in privileged mode - for key := range container.CapabilitiesMask { - container.CapabilitiesMask[key] = true - } + container.Capabilities = libcontainer.GetAllCapabilities() if err := ParseConfiguration(container, nil, opts); err != nil { t.Fatal(err) } - if container.CapabilitiesMask["MKNOD"] { + if hasCapability("MKNOD", container.Capabilities) { t.Fatal("container should not have MKNOD enabled") } } diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index 5adae30db..76816e0b9 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -98,9 +98,7 @@ func (d *driver) createNetwork(container *libcontainer.Container, c *execdriver. } func (d *driver) setPrivileged(container *libcontainer.Container) error { - for key := range container.CapabilitiesMask { - container.CapabilitiesMask[key] = true - } + container.Capabilities = libcontainer.GetAllCapabilities() container.Cgroups.DeviceAccess = true delete(container.Context, "restrictions") diff --git a/daemon/execdriver/native/template/default_template.go b/daemon/execdriver/native/template/default_template.go index 32d901d87..66cfa88a3 100644 --- a/daemon/execdriver/native/template/default_template.go +++ b/daemon/execdriver/native/template/default_template.go @@ -9,28 +9,13 @@ import ( // New returns the docker default configuration for libcontainer func New() *libcontainer.Container { container := &libcontainer.Container{ - CapabilitiesMask: map[string]bool{ - "SETPCAP": false, - "SYS_MODULE": false, - "SYS_RAWIO": false, - "SYS_PACCT": false, - "SYS_ADMIN": false, - "SYS_NICE": false, - "SYS_RESOURCE": false, - "SYS_TIME": false, - "SYS_TTY_CONFIG": false, - "AUDIT_WRITE": false, - "AUDIT_CONTROL": false, - "MAC_OVERRIDE": false, - "MAC_ADMIN": false, - "NET_ADMIN": false, - "MKNOD": true, - "SYSLOG": false, - "SETUID": true, - "SETGID": true, - "CHOWN": true, - "NET_RAW": true, - "DAC_OVERRIDE": true, + Capabilities: []string{ + "MKNOD", + "SETUID", + "SETGID", + "CHOWN", + "NET_RAW", + "DAC_OVERRIDE", }, Namespaces: map[string]bool{ "NEWNS": true, diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index 7328f5544..0ea8d37c2 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -11,19 +11,19 @@ type Context map[string]string // Container defines configuration options for how a // container is setup inside a directory and how a process should be executed type Container struct { - Hostname string `json:"hostname,omitempty"` // hostname - ReadonlyFs bool `json:"readonly_fs,omitempty"` // set the containers rootfs as readonly - NoPivotRoot bool `json:"no_pivot_root,omitempty"` // this can be enabled if you are running in ramdisk - User string `json:"user,omitempty"` // user to execute the process as - WorkingDir string `json:"working_dir,omitempty"` // current working directory - Env []string `json:"environment,omitempty"` // environment to set - Tty bool `json:"tty,omitempty"` // setup a proper tty or not - Namespaces map[string]bool `json:"namespaces,omitempty"` // namespaces to apply - CapabilitiesMask map[string]bool `json:"capabilities_mask,omitempty"` // capabilities to drop - Networks []*Network `json:"networks,omitempty"` // nil for host's network stack - Cgroups *cgroups.Cgroup `json:"cgroups,omitempty"` // cgroups - Context Context `json:"context,omitempty"` // generic context for specific options (apparmor, selinux) - Mounts Mounts `json:"mounts,omitempty"` + Hostname string `json:"hostname,omitempty"` // hostname + ReadonlyFs bool `json:"readonly_fs,omitempty"` // set the containers rootfs as readonly + NoPivotRoot bool `json:"no_pivot_root,omitempty"` // this can be enabled if you are running in ramdisk + User string `json:"user,omitempty"` // user to execute the process as + WorkingDir string `json:"working_dir,omitempty"` // current working directory + Env []string `json:"environment,omitempty"` // environment to set + Tty bool `json:"tty,omitempty"` // setup a proper tty or not + Namespaces map[string]bool `json:"namespaces,omitempty"` // namespaces to apply + Capabilities []string `json:"capabilities,omitempty"` // capabilities given to the container + Networks []*Network `json:"networks,omitempty"` // nil for host's network stack + Cgroups *cgroups.Cgroup `json:"cgroups,omitempty"` // cgroups + Context Context `json:"context,omitempty"` // generic context for specific options (apparmor, selinux) + Mounts Mounts `json:"mounts,omitempty"` } // Network defines configuration for a container's networking stack diff --git a/pkg/libcontainer/container.json b/pkg/libcontainer/container.json index 33d79600d..07950fe58 100644 --- a/pkg/libcontainer/container.json +++ b/pkg/libcontainer/container.json @@ -24,24 +24,9 @@ "mtu": 1500 } ], - "capabilities_mask": { - "SYSLOG": false, - "MKNOD": true, - "NET_ADMIN": false, - "MAC_ADMIN": false, - "MAC_OVERRIDE": false, - "AUDIT_CONTROL": false, - "AUDIT_WRITE": false, - "SYS_TTY_CONFIG": false, - "SETPCAP": false, - "SYS_MODULE": false, - "SYS_RAWIO": false, - "SYS_PACCT": false, - "SYS_ADMIN": false, - "SYS_NICE": false, - "SYS_RESOURCE": false, - "SYS_TIME": false - }, + "capabilities": [ + "MKNOD" + ], "cgroups": { "name": "docker-koye", "parent": "docker" diff --git a/pkg/libcontainer/container_test.go b/pkg/libcontainer/container_test.go index c02385af3..b3f240740 100644 --- a/pkg/libcontainer/container_test.go +++ b/pkg/libcontainer/container_test.go @@ -6,6 +6,16 @@ import ( "testing" ) +// Checks whether the expected capability is specified in the capabilities. +func hasCapability(expected string, capabilities []string) bool { + for _, capability := range capabilities { + if capability == expected { + return true + } + } + return false +} + func TestContainerJsonFormat(t *testing.T) { f, err := os.Open("container.json") if err != nil { @@ -37,22 +47,17 @@ func TestContainerJsonFormat(t *testing.T) { t.Fail() } - if _, exists := container.CapabilitiesMask["SYS_ADMIN"]; !exists { - t.Log("capabilities mask should contain SYS_ADMIN") - t.Fail() - } - - if container.CapabilitiesMask["SYS_ADMIN"] { + if hasCapability("SYS_ADMIN", container.Capabilities) { t.Log("SYS_ADMIN should not be enabled in capabilities mask") t.Fail() } - if !container.CapabilitiesMask["MKNOD"] { + if !hasCapability("MKNOD", container.Capabilities) { t.Log("MKNOD should be enabled in capabilities mask") t.Fail() } - if container.CapabilitiesMask["SYS_CHROOT"] { + if hasCapability("SYS_CHROOT", container.Capabilities) { t.Log("capabilities mask should not contain SYS_CHROOT") t.Fail() } diff --git a/pkg/libcontainer/security/capabilities/capabilities.go b/pkg/libcontainer/security/capabilities/capabilities.go index 107417ad7..ba72070f5 100644 --- a/pkg/libcontainer/security/capabilities/capabilities.go +++ b/pkg/libcontainer/security/capabilities/capabilities.go @@ -26,14 +26,12 @@ func DropCapabilities(container *libcontainer.Container) error { return nil } -// getCapabilitiesMask returns the capabilities that should not be dropped by the container. +// getEnabledCapabilities returns the capabilities that should not be dropped by the container. func getEnabledCapabilities(container *libcontainer.Container) []capability.Cap { keep := []capability.Cap{} - for key, enabled := range container.CapabilitiesMask { - if enabled { - if c := libcontainer.GetCapability(key); c != nil { - keep = append(keep, c.Value) - } + for _, capability := range container.Capabilities { + if c := libcontainer.GetCapability(capability); c != nil { + keep = append(keep, c.Value) } } return keep diff --git a/pkg/libcontainer/types.go b/pkg/libcontainer/types.go index 4c8f60c47..ed5cce483 100644 --- a/pkg/libcontainer/types.go +++ b/pkg/libcontainer/types.go @@ -123,6 +123,14 @@ func GetCapability(key string) *Capability { return nil } +func GetAllCapabilities() []string { + output := make([]string, len(capabilityList)) + for i, capability := range capabilityList { + output[i] = capability.String() + } + return output +} + // Contains returns true if the specified Capability is // in the slice func (c Capabilities) Contains(capp string) bool { From a0070f0c17b4f65bdfc5368b93d0fa8636eda03d Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Sat, 17 May 2014 01:16:07 +0000 Subject: [PATCH 145/400] add support for CAP_FOWNER Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- pkg/libcontainer/types.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/libcontainer/types.go b/pkg/libcontainer/types.go index ed5cce483..07694e9f5 100644 --- a/pkg/libcontainer/types.go +++ b/pkg/libcontainer/types.go @@ -60,6 +60,7 @@ var ( {Key: "CHOWN", Value: capability.CAP_CHOWN}, {Key: "NET_RAW", Value: capability.CAP_NET_RAW}, {Key: "DAC_OVERRIDE", Value: capability.CAP_DAC_OVERRIDE}, + {Key: "FOWNER", Value: capability.CAP_FOWNER}, } ) From ab6027324d1b31d3f8fb880a014091435b68dd07 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 16 May 2014 19:03:08 -0700 Subject: [PATCH 146/400] Make Cristian maintainer of integration-cli (he wrote it) Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- integration-cli/MAINTAINERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 integration-cli/MAINTAINERS diff --git a/integration-cli/MAINTAINERS b/integration-cli/MAINTAINERS new file mode 100644 index 000000000..53c8a1185 --- /dev/null +++ b/integration-cli/MAINTAINERS @@ -0,0 +1 @@ +Cristian Staretu (github: unclejack) From 4bf03a0fac48a06298afa149d4339245736810b6 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Sat, 17 May 2014 03:51:02 -0400 Subject: [PATCH 147/400] gocapability: upstream fix for unsporrted caps @vmarmol has made the fix upstream for not failing if the capability being dropped is returned as invalid from the syscall, which is the case when the capability is not supported on the host. This is a blocker presently for RHEL6.5 on CAP_SYSLOG. We have patched around this in our RPM for the time being, but this is the proper fix. See also https://github.com/dotcloud/docker/pull/5810 Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- hack/vendor.sh | 2 +- .../syndtr/gocapability/capability/capability_linux.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/hack/vendor.sh b/hack/vendor.sh index 79322cd9a..28c9fd40f 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -45,7 +45,7 @@ clone git github.com/gorilla/context 708054d61e5 clone git github.com/gorilla/mux 9b36453141c -clone git github.com/syndtr/gocapability 3454319be2 +clone git github.com/syndtr/gocapability 3c85049eae clone hg code.google.com/p/go.net 84a4013f96e0 diff --git a/vendor/src/github.com/syndtr/gocapability/capability/capability_linux.go b/vendor/src/github.com/syndtr/gocapability/capability/capability_linux.go index 3aaae5973..c5f335f7f 100644 --- a/vendor/src/github.com/syndtr/gocapability/capability/capability_linux.go +++ b/vendor/src/github.com/syndtr/gocapability/capability/capability_linux.go @@ -388,6 +388,11 @@ func (c *capsV3) Apply(kind CapType) (err error) { } err = prctl(syscall.PR_CAPBSET_DROP, uintptr(i), 0, 0, 0) if err != nil { + // Ignore EINVAL since the capability may not be supported in this system. + if errno, ok := err.(syscall.Errno); ok && errno == syscall.EINVAL { + err = nil + continue + } return } } From 58c11ee0a8be74562b0e6f88ee000afdf9ddf025 Mon Sep 17 00:00:00 2001 From: unclejack Date: Sat, 17 May 2014 17:25:56 +0300 Subject: [PATCH 148/400] integcli: speed up TestBuildSixtySteps This improves the TestBuildSixtySteps test by switching from busybox to scratch and simply adding a file. This lowers the execution time of that test from 20 seconds to 5 seconds. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- .../TestBuildSixtySteps/Dockerfile | 120 +++++++++--------- .../build_tests/TestBuildSixtySteps/foo | 1 + 2 files changed, 61 insertions(+), 60 deletions(-) create mode 100644 integration-cli/build_tests/TestBuildSixtySteps/foo diff --git a/integration-cli/build_tests/TestBuildSixtySteps/Dockerfile b/integration-cli/build_tests/TestBuildSixtySteps/Dockerfile index 89b66f4f1..6a2bcab30 100644 --- a/integration-cli/build_tests/TestBuildSixtySteps/Dockerfile +++ b/integration-cli/build_tests/TestBuildSixtySteps/Dockerfile @@ -1,60 +1,60 @@ -FROM busybox -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" -RUN echo "foo" +FROM scratch +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / +ADD foo / diff --git a/integration-cli/build_tests/TestBuildSixtySteps/foo b/integration-cli/build_tests/TestBuildSixtySteps/foo new file mode 100644 index 000000000..789819226 --- /dev/null +++ b/integration-cli/build_tests/TestBuildSixtySteps/foo @@ -0,0 +1 @@ +a From 3e2b97ef26467228f6018e19f517ed535e107026 Mon Sep 17 00:00:00 2001 From: Timothy Date: Sat, 17 May 2014 17:37:05 +0000 Subject: [PATCH 149/400] Add readme file for the api directory See issue #5873 Docker-DCO-1.1-Signed-off-by: Timothy (github: https://github.com/timthelion) --- api/README.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 api/README.md diff --git a/api/README.md b/api/README.md new file mode 100644 index 000000000..3ef33f8c2 --- /dev/null +++ b/api/README.md @@ -0,0 +1,5 @@ +This directory contains code pertaining to the Docker API: + + - Used by the docker client when comunicating with the docker deamon + + - Used by third party tools wishing to interface with the docker deamon From 28fc387cf0c933128d0adad6d633c8f4a719d8ee Mon Sep 17 00:00:00 2001 From: Timothy Date: Sat, 17 May 2014 17:48:07 +0000 Subject: [PATCH 150/400] Add readme file for the archive directory See issue #5873 Docker-DCO-1.1-Signed-off-by: Timothy (github: https://github.com/timthelion) --- archive/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 archive/README.md diff --git a/archive/README.md b/archive/README.md new file mode 100644 index 000000000..4eb0c0418 --- /dev/null +++ b/archive/README.md @@ -0,0 +1,3 @@ +This code provides helper functions for dealing with archive files. + +**TODO**: Move this to either `pkg` or (if not possible) to `utils`. From b175b4dd434f6b16c0966d9c62be3d63cc8238bd Mon Sep 17 00:00:00 2001 From: Timothy Date: Sat, 17 May 2014 17:56:02 +0000 Subject: [PATCH 151/400] Add readme for daemon directory See issue #5873 Docker-DCO-1.1-Signed-off-by: Timothy (github: https://github.com/timthelion) --- daemon/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 daemon/README.md diff --git a/daemon/README.md b/daemon/README.md new file mode 100644 index 000000000..64bfcb55e --- /dev/null +++ b/daemon/README.md @@ -0,0 +1,10 @@ +This directory contains code pertaining to running containers and storing images + +Code pertaining to running containers: + + - execdriver + - networkdriver + +Code pertaining to storing images: + + - graphdriver From 8dfd4b677b60bc2b5de0214bb3cef1a04da12bed Mon Sep 17 00:00:00 2001 From: Timothy Date: Sat, 17 May 2014 18:01:47 +0000 Subject: [PATCH 152/400] Add readme for the daemonconfig directory See Issue #5873 Docker-DCO-1.1-Signed-off-by: Timothy (github: https://github.com/timthelion) --- daemonconfig/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 daemonconfig/README.md diff --git a/daemonconfig/README.md b/daemonconfig/README.md new file mode 100644 index 000000000..488e7c7ca --- /dev/null +++ b/daemonconfig/README.md @@ -0,0 +1,3 @@ +This directory contains code pertaining to the configuration of the docker deamon + +These are the configuration settings that you pass to the docker daemon when you launch it with say: `docker -d -e lxc` From f9728de7a3a5bca5db800edf17d8bc3b39358b77 Mon Sep 17 00:00:00 2001 From: Timothy Date: Sat, 17 May 2014 18:12:44 +0000 Subject: [PATCH 153/400] Add README.md file for sysinit directory Note, this text is just copied from sysinit.go See Issue #5873 Docker-DCO-1.1-Signed-off-by: Timothy (github: https://github.com/timthelion) --- sysinit/README.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 sysinit/README.md diff --git a/sysinit/README.md b/sysinit/README.md new file mode 100644 index 000000000..c28d0298b --- /dev/null +++ b/sysinit/README.md @@ -0,0 +1,4 @@ +Sys Init code + +This code is run INSIDE the container and is responsible for setting +up the environment before running the actual process From a52db7d8802f3cfd8a88e1bcf0a25f59458f7558 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 17 May 2014 22:18:08 +0200 Subject: [PATCH 154/400] Fixed description and keywords on Ubuntu installation docs Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- docs/sources/installation/ubuntulinux.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/installation/ubuntulinux.md b/docs/sources/installation/ubuntulinux.md index d40e17b64..d0e37a270 100644 --- a/docs/sources/installation/ubuntulinux.md +++ b/docs/sources/installation/ubuntulinux.md @@ -1,6 +1,6 @@ page_title: Installation on Ubuntu -page_description: Please note this project is currently under heavy development. It should not be used in production. -page_keywords: Docker, Docker documentation, requirements, virtualbox, vagrant, git, ssh, putty, cygwin, linux +page_description: Instructions for installing Docker on Ubuntu. +page_keywords: Docker, Docker documentation, requirements, virtualbox, installation, ubuntu # Ubuntu From 82712ed67ef9c189ccc8837ef8469272c91f6fcf Mon Sep 17 00:00:00 2001 From: Jonathan McCrohan Date: Sun, 18 May 2014 01:49:58 +0100 Subject: [PATCH 155/400] client: rearrange docker version output Rearrange docker version output so that server output matches client output Docker-DCO-1.1-Signed-off-by: Jonathan McCrohan (github: jmccrohan) --- api/client/commands.go | 2 +- integration-cli/docker_cli_version_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index 502c1be69..532a20cb3 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -385,8 +385,8 @@ func (cli *DockerCli) CmdVersion(args ...string) error { if apiVersion := remoteVersion.Get("ApiVersion"); apiVersion != "" { fmt.Fprintf(cli.out, "Server API version: %s\n", apiVersion) } - fmt.Fprintf(cli.out, "Git commit (server): %s\n", remoteVersion.Get("GitCommit")) fmt.Fprintf(cli.out, "Go version (server): %s\n", remoteVersion.Get("GoVersion")) + fmt.Fprintf(cli.out, "Git commit (server): %s\n", remoteVersion.Get("GitCommit")) release := utils.GetReleaseVersion() if release != "" { fmt.Fprintf(cli.out, "Last stable version: %s", release) diff --git a/integration-cli/docker_cli_version_test.go b/integration-cli/docker_cli_version_test.go index f18d5bede..822c2b817 100644 --- a/integration-cli/docker_cli_version_test.go +++ b/integration-cli/docker_cli_version_test.go @@ -24,8 +24,8 @@ func TestVersionEnsureSucceeds(t *testing.T) { "Git commit (client):", "Server version:", "Server API version:", - "Git commit (server):", "Go version (server):", + "Git commit (server):", "Last stable version:", } From 3cec63d56f9de6332aa3f33502695283d4feb054 Mon Sep 17 00:00:00 2001 From: Jonathan McCrohan Date: Sun, 18 May 2014 02:00:53 +0100 Subject: [PATCH 156/400] client: Rip out HTTP check from docker version For background to this change please see: https://github.com/dotcloud/docker/issues/4802 https://github.com/dotcloud/docker/pull/5670 Docker-DCO-1.1-Signed-off-by: Jonathan McCrohan (github: jmccrohan) --- api/client/commands.go | 8 -------- integration-cli/docker_cli_version_test.go | 1 - utils/utils.go | 16 ---------------- 3 files changed, 25 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index 532a20cb3..647516da6 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -387,14 +387,6 @@ func (cli *DockerCli) CmdVersion(args ...string) error { } fmt.Fprintf(cli.out, "Go version (server): %s\n", remoteVersion.Get("GoVersion")) fmt.Fprintf(cli.out, "Git commit (server): %s\n", remoteVersion.Get("GitCommit")) - release := utils.GetReleaseVersion() - if release != "" { - fmt.Fprintf(cli.out, "Last stable version: %s", release) - if (dockerversion.VERSION != "" || remoteVersion.Exists("Version")) && (strings.Trim(dockerversion.VERSION, "-dev") != release || strings.Trim(remoteVersion.Get("Version"), "-dev") != release) { - fmt.Fprintf(cli.out, ", please update docker") - } - fmt.Fprintf(cli.out, "\n") - } return nil } diff --git a/integration-cli/docker_cli_version_test.go b/integration-cli/docker_cli_version_test.go index 822c2b817..7f1838e5d 100644 --- a/integration-cli/docker_cli_version_test.go +++ b/integration-cli/docker_cli_version_test.go @@ -26,7 +26,6 @@ func TestVersionEnsureSucceeds(t *testing.T) { "Server API version:", "Go version (server):", "Git commit (server):", - "Last stable version:", } for _, linePrefix := range stringsToCheck { diff --git a/utils/utils.go b/utils/utils.go index 191c85206..6e3a3c1b6 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -821,22 +821,6 @@ func ParseHost(defaultHost string, defaultUnix, addr string) (string, error) { return fmt.Sprintf("%s://%s:%d", proto, host, port), nil } -func GetReleaseVersion() string { - resp, err := http.Get("https://get.docker.io/latest") - if err != nil { - return "" - } - defer resp.Body.Close() - if resp.ContentLength > 24 || resp.StatusCode != 200 { - return "" - } - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return "" - } - return strings.TrimSpace(string(body)) -} - // Get a repos name and returns the right reposName + tag // The tag can be confusing because of a port in a repository name. // Ex: localhost.localdomain:5000/samalba/hipache:latest From 21e36ab36cd0beb192ce95469639f659b4877641 Mon Sep 17 00:00:00 2001 From: Adam Singer Date: Sat, 17 May 2014 22:04:41 -0700 Subject: [PATCH 157/400] command line nit --- docs/sources/installation/mac.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/mac.md b/docs/sources/installation/mac.md index 816536256..45e67e8d0 100644 --- a/docs/sources/installation/mac.md +++ b/docs/sources/installation/mac.md @@ -153,7 +153,7 @@ option, ports 49000-49900, and run the following command. If you feel the need to connect to the VM, you can simply run: - $ ./boot2docker ssh + $ boot2docker ssh # User: docker # Pwd: tcuser From 72d1e40c4a3b190319cfa5cb44b5e6f1694100fc Mon Sep 17 00:00:00 2001 From: Alexandr Morozov Date: Sat, 17 May 2014 22:43:31 +0400 Subject: [PATCH 158/400] Check uid ranges Fixes #5647 Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- docs/sources/reference/run.md | 2 ++ integration-cli/docker_cli_run_test.go | 45 ++++++++++++++++++++++++++ pkg/user/user.go | 15 +++++++++ 3 files changed, 62 insertions(+) diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index 7587c70ba..aa3d941b1 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -439,6 +439,8 @@ but the operator can override it: -u="": Username or UID +> **Note:** if you pass numeric uid, it must be in range 0-2147483647. + ## WORKDIR The default working directory for running binaries within a container is the diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index a439c5f38..154b72e27 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -544,6 +544,51 @@ func TestUserByID(t *testing.T) { logDone("run - user by id") } +func TestUserByIDBig(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-u", "2147483648", "busybox", "id") + + out, _, err := runCommandWithOutput(cmd) + if err == nil { + t.Fatal("No error, but must be.", out) + } + if !strings.Contains(out, "Uids and gids must be in range") { + t.Fatalf("expected error about uids range, got %s", out) + } + deleteAllContainers() + + logDone("run - user by id, id too big") +} + +func TestUserByIDNegative(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-u", "-1", "busybox", "id") + + out, _, err := runCommandWithOutput(cmd) + if err == nil { + t.Fatal("No error, but must be.", out) + } + if !strings.Contains(out, "Uids and gids must be in range") { + t.Fatalf("expected error about uids range, got %s", out) + } + deleteAllContainers() + + logDone("run - user by id, id negative") +} + +func TestUserByIDZero(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-u", "0", "busybox", "id") + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + if !strings.Contains(out, "uid=0(root) gid=0(root) groups=10(wheel)") { + t.Fatalf("expected daemon user got %s", out) + } + deleteAllContainers() + + logDone("run - user by id, zero uid") +} + func TestUserNotFound(t *testing.T) { cmd := exec.Command(dockerBinary, "run", "-u", "notme", "busybox", "id") diff --git a/pkg/user/user.go b/pkg/user/user.go index 1672f7e67..df4710122 100644 --- a/pkg/user/user.go +++ b/pkg/user/user.go @@ -9,6 +9,15 @@ import ( "strings" ) +const ( + minId = 0 + maxId = 1<<31 - 1 //for 32-bit systems compatibility +) + +var ( + ErrRange = fmt.Errorf("Uids and gids must be in range %d-%d", minId, maxId) +) + type User struct { Name string Pass string @@ -194,6 +203,9 @@ func GetUserGroupSupplementary(userSpec string, defaultUid int, defaultGid int) // not numeric - we have to bail return 0, 0, nil, fmt.Errorf("Unable to find user %v", userArg) } + if uid < minId || uid > maxId { + return 0, 0, nil, ErrRange + } // if userArg couldn't be found in /etc/passwd but is numeric, just roll with it - this is legit } @@ -226,6 +238,9 @@ func GetUserGroupSupplementary(userSpec string, defaultUid int, defaultGid int) // not numeric - we have to bail return 0, 0, nil, fmt.Errorf("Unable to find group %v", groupArg) } + if gid < minId || gid > maxId { + return 0, 0, nil, ErrRange + } // if groupArg couldn't be found in /etc/group but is numeric, just roll with it - this is legit } From 7f9178c6d15417ed8b32976b7ca075422c8160da Mon Sep 17 00:00:00 2001 From: Adam Singer Date: Sat, 17 May 2014 23:16:23 -0700 Subject: [PATCH 159/400] removing grep command that depends on perl, better for boot2docker Docker-DCO-1.1-Signed-off-by: Adam Singer (github: financeCoding) --- docs/sources/examples/couchdb_data_volumes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/examples/couchdb_data_volumes.md b/docs/sources/examples/couchdb_data_volumes.md index 17490487a..ec1a0d947 100644 --- a/docs/sources/examples/couchdb_data_volumes.md +++ b/docs/sources/examples/couchdb_data_volumes.md @@ -28,7 +28,7 @@ We're assuming your Docker host is reachable at `localhost`. If not, replace `localhost` with the public IP of your Docker host. $ HOST=localhost - $ URL="http://$HOST:$(sudo docker port $COUCH1 5984 | grep -Po '\d+$')/_utils/" + $ URL="http://$HOST:$(sudo docker port $COUCH1 5984 | grep -o '[1-9][0-9]*$')/_utils/" $ echo "Navigate to $URL in your browser, and use the couch interface to add data" ## Create second database @@ -40,7 +40,7 @@ This time, we're requesting shared access to `$COUCH1`'s volumes. ## Browse data on the second database $ HOST=localhost - $ URL="http://$HOST:$(sudo docker port $COUCH2 5984 | grep -Po '\d+$')/_utils/" + $ URL="http://$HOST:$(sudo docker port $COUCH2 5984 | grep -o '[1-9][0-9]*$')/_utils/" $ echo "Navigate to $URL in your browser. You should see the same data as in the first database"'!' Congratulations, you are now running two Couchdb containers, completely From 89c4748b8351eaf070ce722fdb7fe0c0dcec2819 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sun, 18 May 2014 13:26:13 -0600 Subject: [PATCH 160/400] Update AUTHORS and .mailmap Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- .mailmap | 38 ++++++++++++---- AUTHORS | 134 ++++++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 148 insertions(+), 24 deletions(-) diff --git a/.mailmap b/.mailmap index a34fc4823..683758650 100644 --- a/.mailmap +++ b/.mailmap @@ -6,14 +6,16 @@ Guillaume J. Charmes + - -Thatcher Peskens dhrp -Thatcher Peskens dhrp +Thatcher Peskens +Thatcher Peskens +Thatcher Peskens dhrp Jérôme Petazzoni jpetazzo Jérôme Petazzoni -Joffrey F - +Joffrey F +Joffrey F +Joffrey F Tim Terhorst Andy Smith @@ -23,7 +25,6 @@ Andy Smith -Thatcher Peskens Walter Stanish @@ -54,7 +55,26 @@ Jean-Baptiste Dalido - - -Sven Dowideit ¨Sven <¨SvenDowideit@home.org.au¨> + + +Sven Dowideit +Sven Dowideit +Sven Dowideit +Sven Dowideit <¨SvenDowideit@home.org.au¨> unclejack + +Alexandr Morozov + +O.S. Tezer + +Roberto G. Hashioka + + + + + +Sridhar Ratnakumar +Sridhar Ratnakumar +Liang-Chi Hsieh +Aleksa Sarai +Will Weaver diff --git a/AUTHORS b/AUTHORS index 8f75ab85b..10f01fb58 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,46 +1,62 @@ # This file lists all individuals having contributed content to the repository. -# If you're submitting a patch, please add your name here in alphabetical order as part of the patch. -# -# For a list of active project maintainers, see the MAINTAINERS file. -# +# For how it is generated, see `.mailmap`. + Aanand Prasad Aaron Feng +Aaron Huslage Abel Muiño +Adam Miller +Adam Singer +Aditya +Adrian Mouat +alambike Aleksa Sarai Alexander Larsson +Alexandr Morozov +Alexey Kotlyarov Alexey Shamrin Alex Gaynor Alexis THOMAS +almoehi Al Tobey +amangoel Andrea Luzzardi Andreas Savvides Andreas Tiefenthaler +Andrea Turli Andrew Duckworth Andrew Macgregor Andrew Munsell Andrews Medina +Andrew Williams Andy Chambers andy diller Andy Goldstein +Andy Kipp Andy Rothfusz Andy Smith Anthony Bishopric Anton Nikitin Antony Messerli apocas -Arnaud Porterie +Arnaud Porterie Asbjørn Enge +Barnaby Gray Barry Allard Bartłomiej Piotrowski +Benjamin Atkin Benoit Chesneau Ben Sargent Ben Toews Ben Wiklund +Bernerd Schaefer Bhiraj Butala +bin liu Bouke Haarsma Brandon Liu Brandon Philips Brian Dorsey +Brian Flad Brian Goff Brian McCallister Brian Olsen @@ -48,11 +64,15 @@ Brian Shumate Briehan Lombaard Bruno Bigras Bryan Matsuo +Bryan Murphy Caleb Spare Calen Pennington +Cameron Boehmer Carl X. Su Charles Hooper Charles Lindsay +Charles Merriam +Charlie Lewis Chia-liang Kao Chris St. Pierre Christopher Currie @@ -63,6 +83,7 @@ Colin Dunklau Colin Rice Cory Forsyth cressie176 +Dafydd Crosby Dan Buch Dan Hirsch Daniel Exner @@ -74,30 +95,45 @@ Daniel Nordberg Daniel Robinson Daniel Von Fange Daniel YC Lin +Dan Keder +Dan McPherson +Danny Berger Danny Yates +Dan Stine +Dan Walsh +Dan Williams Darren Coxall +Darren Shepherd David Anderson David Calavera +David Gageot David Mcanulty +David Röthlisberger David Sissitka Deni Bertovic Dinesh Subhraveti +Djibril Koné dkumor Dmitry Demeshchuk +Dolph Mathews Dominik Honnef Don Spaulding Dražen Lučanin Dr Nic Williams Dustin Sallings Edmund Wagner +Eiichi Tsukata +Eivind Uggedal Elias Probst Emil Hernvall Emily Rose Eric Hanchrow Eric Lee Eric Myhre +Erik Hollensbe Erno Hopearuoho eugenkrizo +Evan Hazlett Evan Krall Evan Phoenix Evan Wies @@ -108,6 +144,7 @@ Fabio Rehm Fabrizio Regini Faiz Khan Fareed Dudhia +Felix Rabe Fernando Flavio Castelli Francisco Souza @@ -119,8 +156,11 @@ Gabe Rosenhouse Gabriel Monroy Galen Sampson Gareth Rushgrove +Geoffrey Bachelet Gereon Frey +German DZ Gert van Valkenhoef +Goffert van Gool Graydon Hoare Greg Thornton grunny @@ -129,28 +169,40 @@ Gurjeet Singh Guruprasad Harley Laue Hector Castro +Hobofan Hunter Blanks +Ian Truslove +ILYA Khlopotov inglesp Isaac Dupree +Isabel Jimenez Isao Jonas +Jack Danger Canty +jakedt Jake Moshenko James Allen James Carr +James DeFelice +James Harrison Fisher James Mills James Turnbull jaseg Jason McVetta +Jason Plum Jean-Baptiste Barth Jean-Baptiste Dalido Jeff Lindsay Jeremy Grosser Jérôme Petazzoni Jesse Dubay +Jilles Oldenbeuving Jim Alateras Jimmy Cuadra Joe Beda +Joel Handwell +Joe Shaw Joe Van Dyk -Joffrey F +Joffrey F Johan Euphrosine Johannes 'fish' Ziemke Johan Rydberg @@ -159,7 +211,9 @@ John Feminella John Gardiner Myers John Warwick Jonas Pfenniger +Jonathan McCrohan Jonathan Mueller +Jonathan Pares Jonathan Rudenberg Jon Wedaman Joost Cassee @@ -174,13 +228,17 @@ Julien Barbier Julien Dubois Justin Force Justin Plock +Justin Simonelis Karan Lyons Karl Grzeszczak +Kato Kazuyoshi Kawsar Saiyeed Keli Hu Ken Cochrane +Ken ICHIKAWA Kevin Clark Kevin J. Lynagh +Kevin Menard Kevin Wallace Keyvan Fatehi kim0 @@ -189,15 +247,20 @@ Kimbro Staken Kiran Gangadharan Konstantin Pelykh Kyle Conroy -Lajos Papp +lalyos +Lance Chen +Lars R. Damerow Laurie Voss +Lewis Peckover Liang-Chi Hsieh Lokesh Mandvekar Louis Opter lukaspustina +lukemarsden Mahesh Tiyyagura Manuel Meurer Manuel Woelker +Marc Abramowitz Marc Kuo Marco Hennings Marcus Farkas @@ -209,23 +272,32 @@ Marko Mikulicic Markus Fix Martijn van Oosterhout Martin Redmond +Mason Malone +Mateusz Sulima Mathieu Le Marec - Pasquet Matt Apperson Matt Bachmann Matt Haggard Matthew Mueller +Matthias Klumpp +Matthias Kühnle mattymo Maxime Petazzoni Maxim Treskin +Max Shytikov meejah +Michael Brown Michael Crosby Michael Gorsuch +Michael Neale Michael Stapelberg Miguel Angel Fernández Mike Gaffney +Mike MacCana Mike Naberezny Mikhail Sobolev Mohit Soni +Morgante Pell Morten Siebuhr Nan Monnand Deng Nate Jones @@ -237,22 +309,26 @@ Nick Stenning Nick Stinemates Nicolas Dudebout Nicolas Kaiser +noducks Nolan Darilek odk- Oguz Bilgic Ole Reifschneider -O.S.Tezer +O.S. Tezer pandrew Pascal Borreli pattichen +Paul Annesley Paul Bowsher Paul Hammond +Paul Jimenez Paul Lietar Paul Morie Paul Nasrat Paul Peter Braden Peter Waller +Phillip Alexander Phil Spitler Piergiuliano Bossi Pierre-Alain RIVIERE @@ -260,6 +336,8 @@ Piotr Bogdan pysqz Quentin Brossard Rafal Jeczalik +Rajat Pandit +Ralph Bean Ramkumar Ramachandra Ramon van Alteren Renato Riccieri Santos Zannon @@ -269,54 +347,71 @@ Richo Healey Rick Bradley Robert Obryk Roberto G. Hashioka -Roberto Hashioka +robpc Rodrigo Vaz Roel Van Nyen Roger Peppe +Rohit Jnagal +Roland Moriz +Rovanion Luckey +Ryan Aslett Ryan Fowler Ryan O'Donnell Ryan Seto +Ryan Thomas Sam Alba Sam J Sharpe +Sam Rijs Samuel Andaya Scott Bessler +Scott Collier Sean Cronin Sean P. Kane +Sébastien Stormacq Shawn Landden Shawn Siefkas Shih-Yuan Lee -shin- Silas Sewell Simon Taranto +Sindhu S Sjoerd Langkemper -Solomon Hykes +Solomon Hykes Song Gao +Soulou Sridatta Thatipamala Sridhar Ratnakumar Steeve Morin Stefan Praszalowicz +Steven Burgess sudosurootdev -Sven Dowideit +Sven Dowideit Sylvain Bellemare tang0th Tatsuki Sugiura Tehmasp Chaudhri -Thatcher Peskens +Thatcher Peskens Thermionix Thijs Terlouw Thomas Bikeev Thomas Frössman Thomas Hansen Thomas LEVEIL +Thomas Schroeter Tianon Gravi -Tim Bosse +Tibor Vass +Tim Bosse +Timothy Hobbs +Tim Ruffles Tim Terhorst +tjmehta Tobias Bieniek Tobias Schmidt Tobias Schwab Todd Lunter +Tom Fotherby Tom Hulihan Tommaso Visconti +Tony Daws Travis Cline Tyler Brock Tzu-Jung Lee @@ -325,26 +420,35 @@ unclejack vgeta Victor Coisne Victor Lyuboslavsky +Victor Marmol Victor Vieux +Viktor Vojnovski Vincent Batts Vincent Bernat +Vincent Mayers Vincent Woo Vinod Kulkarni +Vishnu Kannan Vitor Monteiro Vivek Agarwal +Vladimir Bulyga Vladimir Kirillov -Vladimir Rutsky +Vladimir Rutsky +Walter Leibbrandt Walter Stanish WarheadsSE Wes Morgan Will Dietz William Delanoue +William Henry Will Rouesnel Will Weaver Xiuming Chen Yang Bai +Yasunori Mahata Yurii Rashkovskii Zain Memon Zaiste! Zilin Du zimbatm +zqh From 88afc8992f2ebd2fd95d87dfff720ff946183975 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 17 May 2014 22:28:39 +0200 Subject: [PATCH 161/400] Added basic Debian installation page Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- docs/mkdocs.yml | 1 + docs/sources/installation.md | 3 +- docs/sources/installation/debian.md | 77 +++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 docs/sources/installation/debian.md diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 32d085279..ca1a4d74e 100755 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -37,6 +37,7 @@ pages: - ['installation/mac.md', 'Installation', 'Mac OS X'] - ['installation/ubuntulinux.md', 'Installation', 'Ubuntu'] - ['installation/rhel.md', 'Installation', 'Red Hat Enterprise Linux'] +- ['installation/debian.md', 'Installation', 'Debian'] - ['installation/gentoolinux.md', 'Installation', 'Gentoo'] - ['installation/google.md', 'Installation', 'Google Cloud Platform'] - ['installation/rackspace.md', 'Installation', 'Rackspace Cloud'] diff --git a/docs/sources/installation.md b/docs/sources/installation.md index 66b28b2b3..1c3c72659 100644 --- a/docs/sources/installation.md +++ b/docs/sources/installation.md @@ -12,6 +12,7 @@ techniques for installing Docker all the time. - [Ubuntu](ubuntulinux/) - [Red Hat Enterprise Linux](rhel/) - [Fedora](fedora/) + - [Debian](debian/) - [Arch Linux](archlinux/) - [CRUX Linux](cruxlinux/) - [Gentoo](gentoolinux/) @@ -22,4 +23,4 @@ techniques for installing Docker all the time. - [Amazon EC2](amazon/) - [Rackspace Cloud](rackspace/) - [Google Cloud Platform](google/) - - [Binaries](binaries/) \ No newline at end of file + - [Binaries](binaries/) diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md new file mode 100644 index 000000000..3deda4763 --- /dev/null +++ b/docs/sources/installation/debian.md @@ -0,0 +1,77 @@ +page_title: Installation on Debian +page_description: Instructions for installing Docker on Debian +page_keywords: Docker, Docker documentation, installation, debian + +# Debian + +> **Note**: +> Docker is still under heavy development! We don't recommend using it in +> production yet, but we're getting closer with each release. Please see +> our blog post, [Getting to Docker 1.0]( +> http://blog.docker.io/2013/08/getting-to-docker-1-0/) + +Docker is supported on the following versions of Debian: + + - [*Debian 8.0 Jessie (64-bit)*](#debian-jessie-8-64-bit) + +## Debian Jessie 8.0 (64-bit) + +Debian 8 comes with a 3.14.0 Linux kernel, and a `docker.io` package which +installs all its prerequisites from Debian's repository. + +> **Note**: +> Debian contains a much older KDE3/GNOME2 package called ``docker``, so the +> package and the executable are called ``docker.io``. + +### Installation + +To install the latest Debian package (may not be the latest Docker release): + + $ sudo apt-get update + $ sudo apt-get install docker.io + $ sudo ln -sf /usr/bin/docker.io /usr/local/bin/docker + +To verify that everything has worked as expected: + + $ sudo docker run -i -t ubuntu /bin/bash + +Which should download the `ubuntu` image, and then start `bash` in a container. + +> **Note**: +> If you want to enable memory and swap accounting see +> [this](/installation/ubuntulinux/#memory-and-swap-accounting). + +### Giving non-root access + +The `docker` daemon always runs as the `root` user, and since Docker +version 0.5.2, the `docker` daemon binds to a Unix socket instead of a +TCP port. By default that Unix socket is owned by the user `root`, and +so, by default, you can access it with `sudo`. + +Starting in version 0.5.3, if you (or your Docker installer) create a +Unix group called `docker` and add users to it, then the `docker` daemon +will make the ownership of the Unix socket read/writable by the `docker` +group when the daemon starts. The `docker` daemon must always run as the +root user, but if you run the `docker` client as a user in the `docker` +group then you don't need to add `sudo` to all the client commands. From +Docker 0.9.0 you can use the `-G` flag to specify an alternative group. + +> **Warning**: +> The `docker` group (or the group specified with the `-G` flag) is +> `root`-equivalent; see [*Docker Daemon Attack Surface*]( +> /articles/security/#dockersecurity-daemon) details. + +**Example:** + + # Add the docker group if it doesn't already exist. + $ sudo groupadd docker + + # Add the connected user "${USER}" to the docker group. + # Change the user name to match your preferred user. + # You may have to logout and log back in again for + # this to take effect. + $ sudo gpasswd -a ${USER} docker + + # Restart the Docker daemon. + $ sudo service docker restart + From f8974b5cfc87f7775019e3df2d09ad68f2119772 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sun, 18 May 2014 18:55:58 -0400 Subject: [PATCH 162/400] Fixed sudo section to match Debian installation doc Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- docs/sources/installation/ubuntulinux.md | 29 +++++++++++------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/docs/sources/installation/ubuntulinux.md b/docs/sources/installation/ubuntulinux.md index d40e17b64..541b4da6f 100644 --- a/docs/sources/installation/ubuntulinux.md +++ b/docs/sources/installation/ubuntulinux.md @@ -169,26 +169,23 @@ World*](/examples/hello_world/#hello-world) example. ### Giving non-root access -The `docker` daemon always runs as the root user, -and since Docker version 0.5.2, the `docker` daemon -binds to a Unix socket instead of a TCP port. By default that Unix -socket is owned by the user *root*, and so, by default, you can access -it with `sudo`. +The `docker` daemon always runs as the `root` user, and since Docker +version 0.5.2, the `docker` daemon binds to a Unix socket instead of a +TCP port. By default that Unix socket is owned by the user `root`, and +so, by default, you can access it with `sudo`. Starting in version 0.5.3, if you (or your Docker installer) create a -Unix group called *docker* and add users to it, then the -`docker` daemon will make the ownership of the Unix -socket read/writable by the *docker* group when the daemon starts. The -`docker` daemon must always run as the root user, -but if you run the `docker` client as a user in the -*docker* group then you don't need to add `sudo` to -all the client commands. As of 0.9.0, you can specify that a group other -than `docker` should own the Unix socket with the -`-G` option. +Unix group called `docker` and add users to it, then the `docker` daemon +will make the ownership of the Unix socket read/writable by the `docker` +group when the daemon starts. The `docker` daemon must always run as the +`root` user, but if you run the `docker` client as a user in the +`docker` group then you don't need to add `sudo` to all the client +commands. From Docker 0.9.0 you can use the `-G` flag to specify an +alternative group. > **Warning**: -> The *docker* group (or the group specified with `-G`) is -> root-equivalent; see [*Docker Daemon Attack Surface*]( +> The `docker` group (or the group specified with the `-G` flag) is +> `root`-equivalent; see [*Docker Daemon Attack Surface*]( > /articles/security/#dockersecurity-daemon) details. **Example:** From ca9c35cdf772ddb121447b3877dbcf8caa4c5cdb Mon Sep 17 00:00:00 2001 From: Brandon Rhodes Date: Sat, 17 May 2014 23:30:24 -0400 Subject: [PATCH 163/400] Expand the Advanced Networking page to all options For issue #5658 this rewrite of the networking page explains what every single option (unless I missed one!) does both for the Docker server and also the Docker client when submitting a "docker run". I somehow thought that, when I was done, there would be a lot more about setting up topologies like I did for my Foundations of Python Network Programming network of Docker containers. More about making routers and firewalls that NAT and so forth. But, at least for this draft, I think that setting up subnets and setting up point-to-point links is most of what Docker users will need unless they are doing something exotic. We can always expand later. Docker-DCO-1.1-Signed-off-by: Brandon Rhodes (github: brandon-rhodes) Improve networking.md documentation per @jamtur01 Besides catching some typos and awkward sentences, @jamtur01 had several more thorough changes to suggest: * He illustrated the correct way to delimit "Note" paragraphs in Docker documentation. * He helped point out where I was presuming an Ubuntu host running Docker, so that I could re-word those sections to be specific that the advice only applied to Ubuntu (these mostly related to how to set server-wide options and restart the server). * He is happy to have "Ethernet" capitalized even where Linux documentation would render it with an ugly lower-case first letter. Docker-DCO-1.1-Signed-off-by: Brandon Rhodes (github: brandon-rhodes) Improve networking.md per ideas from @SvenDowideit A response to a bit of further discussion of pull request #5884. --- docs/sources/use/networking.md | 752 ++++++++++++++++++++++++++++----- 1 file changed, 658 insertions(+), 94 deletions(-) diff --git a/docs/sources/use/networking.md b/docs/sources/use/networking.md index db60fe843..5b76233ef 100644 --- a/docs/sources/use/networking.md +++ b/docs/sources/use/networking.md @@ -1,135 +1,699 @@ -page_title: Configure Networking +page_title: Network Configuration page_description: Docker networking page_keywords: network, networking, bridge, docker, documentation -# Configure Networking +# Network Configuration -## Introduction +## TL;DR -Docker uses Linux bridge capabilities to provide network connectivity to -containers. The `docker0` bridge interface is managed by Docker for this -purpose. When the Docker daemon starts it: +When Docker starts, it creates a virtual interface named `docker0` on +the host machine. It randomly chooses an address and subnet from the +private range defined by [RFC 1918](http://tools.ietf.org/html/rfc1918) +that are not in use on the host machine, and assigns it to `docker0`. +Docker made the choice `172.17.42.1/16` when I started it a few minutes +ago, for example — a 16-bit netmask providing 65,534 addresses for the +host machine and its containers. - - Creates the `docker0` bridge if not present - - Searches for an IP address range which doesn't overlap with an existing route - - Picks an IP in the selected range - - Assigns this IP to the `docker0` bridge +But `docker0` is no ordinary interface. It is a virtual *Ethernet +bridge* that automatically forwards packets between any other network +interfaces that are attached to it. This lets containers communicate +both with the host machine and with each other. Every time Docker +creates a container, it creates a pair of “peer” interfaces that are +like opposite ends of a pipe — a packet send on one will be received on +the other. It gives one of the peers to the container to become its +`eth0` interface and keeps the other peer, with a unique name like +`vethAQI2QT`, out in the namespace of the host machine. By binding +every `veth*` interface to the `docker0` bridge, Docker creates a +virtual subnet shared between the host machine and every Docker +container. - +The remaining sections of this document explain all of the ways that you +can use Docker options and — in advanced cases — raw Linux networking +commands to tweak, supplement, or entirely replace Docker’s default +networking configuration. - # List host bridges - $ sudo brctl show - bridge name bridge id STP enabled interfaces - docker0 8000.000000000000 no +## Quick Guide to the Options - # Show docker0 IP address - $ sudo ifconfig docker0 - docker0 Link encap:Ethernet HWaddr xx:xx:xx:xx:xx:xx - inet addr:172.17.42.1 Bcast:0.0.0.0 Mask:255.255.0.0 +Here is a quick list of the networking-related Docker command-line +options, in case it helps you find the section below that you are +looking for. -At runtime, a [*specific kind of virtual interface*](#vethxxxx-device) -is given to each container which is then bonded to the `docker0` bridge. -Each container also receives a dedicated IP address from the same range -as `docker0`. The `docker0` IP address is used as the default gateway -for the container. +Some networking command-line options can only be supplied to the Docker +server when it starts up, and cannot be changed once it is running: - # Run a container - $ sudo docker run -t -i -d base /bin/bash - 52f811c5d3d69edddefc75aff5a4525fc8ba8bcfa1818132f9dc7d4f7c7e78b4 + * `-b BRIDGE` or `--bridge=BRIDGE` — see + [Building your own bridge](#bridge-building) - $ sudo brctl show - bridge name bridge id STP enabled interfaces - docker0 8000.fef213db5a66 no vethQCDY1N + * `--bip=CIDR` — see + [Customizing docker0](#docker0) -Above, `docker0` acts as a bridge for the `vethQCDY1N` interface which -is dedicated to the `52f811c5d3d6` container. + * `-H SOCKET...` or `--host=SOCKET...` — + This might sound like it would affect container networking, + but it actually faces in the other direction: + it tells the Docker server over what channels + it should be willing to receive commands + like “run container” and “stop container.” + To learn about the option, + read [Bind Docker to another host/port or a Unix socket](../basics/#bind-docker-to-another-hostport-or-a-unix-socket) + over in the Basics document. -## How to use a specific IP address range + * `--icc=true|false` — see + [Communication between containers](#between-containers) -Docker will try hard to find an IP range that is not used by the host. -Even though it works for most cases, it's not bullet-proof and sometimes -you need to have more control over the IP addressing scheme. + * `--ip=IP_ADDRESS` — see + [Binding container ports](#binding-ports) -For this purpose, Docker allows you to manage the `docker0` bridge or -your own one using the `-b=` parameter. + * `--ip-forward=true|false` — see + [Communication between containers](#between-containers) -In this scenario: + * `--iptables=true|false` — see + [Communication between containers](#between-containers) - - Ensure Docker is stopped - - Create your own bridge (`bridge0` for example) - - Assign a specific IP to this bridge - - Start Docker with the `-b=bridge0` parameter + * `--mtu=BYTES` — see + [Customizing docker0](#docker0) - +There are two networking options that can be supplied either at startup +or when `docker run` is invoked. When provided at startup, set the +default value that `docker run` will later use if the options are not +specified: - # Stop Docker - $ sudo service docker stop + * `--dns=IP_ADDRESS...` — see + [Configuring DNS](#dns) - # Clean docker0 bridge and - # add your very own bridge0 - $ sudo ifconfig docker0 down - $ sudo brctl addbr bridge0 - $ sudo ifconfig bridge0 192.168.227.1 netmask 255.255.255.0 + * `--dns-search=DOMAIN...` — see + [Configuring DNS](#dns) - # Edit your Docker startup file - $ echo "DOCKER_OPTS=\"-b=bridge0\"" >> /etc/default/docker +Finally, several networking options can only be provided when calling +`docker run` because they specify something specific to one container: - # Start Docker - $ sudo service docker start + * `-h HOSTNAME` or `--hostname=HOSTNAME` — see + [Configuring DNS](#dns) and + [How Docker networks a container](#container-networking) - # Ensure bridge0 IP is not changed by Docker - $ sudo ifconfig bridge0 - bridge0 Link encap:Ethernet HWaddr xx:xx:xx:xx:xx:xx - inet addr:192.168.227.1 Bcast:192.168.227.255 Mask:255.255.255.0 + * `--link=CONTAINER_NAME:ALIAS` — see + [Configuring DNS](#dns) and + [Communication between containers](#between-containers) - # Run a container - docker run -i -t base /bin/bash + * `--net=bridge|none|container:NAME_or_ID|host` — see + [How Docker networks a container](#container-networking) - # Container IP in the 192.168.227/24 range - root@261c272cd7d5:/# ifconfig eth0 - eth0 Link encap:Ethernet HWaddr xx:xx:xx:xx:xx:xx - inet addr:192.168.227.5 Bcast:192.168.227.255 Mask:255.255.255.0 + * `-p SPEC` or `--publish=SPEC` — see + [Binding container ports](#binding-ports) - # bridge0 IP as the default gateway - root@261c272cd7d5:/# route -n - Kernel IP routing table - Destination Gateway Genmask Flags Metric Ref Use Iface - 0.0.0.0 192.168.227.1 0.0.0.0 UG 0 0 0 eth0 - 192.168.227.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 + * `-P` or `--publish-all=true|false` — see + [Binding container ports](#binding-ports) - # hits CTRL+P then CTRL+Q to detach +The following sections tackle all of the above topics in an order that +moves roughly from simplest to most complex. + +## Configuring DNS + +How can Docker supply each container with a hostname and DNS +configuration, without having to build a custom image with the hostname +written inside? Its trick is to overlay three crucial `/etc` files +inside the container with virtual files where it can write fresh +information. You can see this by running `mount` inside a container: + + $$ mount + ... + /dev/disk/by-uuid/1fec...ebdf on /etc/hostname type ext4 ... + /dev/disk/by-uuid/1fec...ebdf on /etc/hosts type ext4 ... + tmpfs on /etc/resolv.conf type tmpfs ... + ... + +This arrangement allows Docker to do clever things like keep +`resolv.conf` up to date across all containers when the host machine +receives new configuration over DHCP later. The exact details of how +Docker maintains these files inside the container can change from one +Docker version to the next, so you should leave the files themselves +alone and use the following Docker options instead. + +Four different options affect container domain name services. + + * `-h HOSTNAME` or `--hostname=HOSTNAME` — sets the hostname by which + the container knows itself. This is written into `/etc/hostname`, + into `/etc/hosts` as the name of the container’s host-facing IP + address, and is the name that `/bin/bash` inside the container will + display inside its prompt. But the hostname is not easy to see from + outside the container. It will not appear in `docker ps` nor in the + `/etc/hosts` file of any other container. + + * `--link=CONTAINER_NAME:ALIAS` — using this option as you `run` a + container gives the new container’s `/etc/hosts` an extra entry + named `ALIAS` that points to the IP address of the container named + `CONTAINER_NAME`. This lets processes inside the new container + connect to the hostname `ALIAS` without having to know its IP. The + `--link=` option is discussed in more detail below, in the section + [Communication between containers](#between-containers). + + * `--dns=IP_ADDRESS...` — sets the IP addresses added as `server` + lines to the container's `/etc/resolv.conf` file. Processes in the + container, when confronted with a hostname not in `/etc/hosts`, will + connect to these IP addresses on port 53 looking for name resolution + services. + + * `--dns-search=DOMAIN...` — sets the domain names that are searched + when a bare unqualified hostname is used inside of the container, by + writing `search` lines into the container’s `/etc/resolv.conf`. + When a container process attempts to access `host` and the search + domain `exmaple.com` is set, for instance, the DNS logic will not + only look up `host` but also `host.example.com`. + +Note that Docker, in the absence of either of the last two options +above, will make `/etc/resolv.conf` inside of each container look like +the `/etc/resolv.conf` of the host machine where the `docker` daemon is +running. The options then modify this default configuration. + +## Communication between containers + +Whether two containers can communicate is governed, at the operating +system level, by three factors. + +1. Does the network topology even connect the containers’ network + interfaces? By default Docker will attach all containers to a + single `docker0` bridge, providing a path for packets to travel + between them. See the later sections of this document for other + possible topologies. + +2. Is the host machine willing to forward IP packets? This is governed + by the `ip_forward` system parameter. Packets can only pass between + containers if this parameter is `1`. Usually you will simply leave + the Docker server at its default setting `--ip-forward=true` and + Docker will go set `ip_forward` to `1` for you when the server + starts up. To check the setting or turn it on manually: + + # Usually not necessary: turning on forwarding, + # on the host where your Docker server is running + + $ cat /proc/sys/net/ipv4/ip_forward + 0 + $ sudo echo 1 > /proc/sys/net/ipv4/ip_forward + $ cat /proc/sys/net/ipv4/ip_forward + 1 + +3. Do your `iptables` allow this particular connection to be made? + Docker will never make changes to your system `iptables` rules if + you set `--iptables=false` when the daemon starts. Otherwise the + Docker server will add a default rule to the `FORWARD` chain with a + blanket `ACCEPT` policy if you retain the default `--icc=true`, or + else will set the policy to `DROP` if `--icc=false`. + +Nearly everyone using Docker will want `ip_forward` to be on, to at +least make communication *possible* between containers. But it is a +strategic question whether to leave `--icc=true` or change it to +`--icc=false` (on Ubuntu, by editing the `DOCKER_OPTS` variable in +`/etc/default/docker` and restarting the Docker server) so that +`iptables` will protect other containers — and the main host — from +having arbitrary ports probed or accessed by a container that gets +compromised. + +If you choose the most secure setting of `--icc=false`, then how can +containers communicate in those cases where you *want* them to provide +each other services? + +The answer is the `--link=CONTAINER_NAME:ALIAS` option, which was +mentioned in the previous section because of its effect upon name +services. If the Docker daemon is running with both `--icc=false` and +`--iptables=true` then, when it sees `docker run` invoked with the +`--link=` option, the Docker server will insert a pair of `iptables` +`ACCEPT` rules so that the new container can connect to the ports +exposed by the other container — the ports that it mentioned in the +`EXPOSE` lines of its `Dockerfile`. Docker has more documentation on +this subject — see the [Link Containers](working_with_links_names.md) +page for further details. + +> **Note**: +> The value `CONTAINER_NAME` in `--link=` must either be an +> auto-assigned Docker name like `stupefied_pare` or else the name you +> assigned with `--name=` when you ran `docker run`. It cannot be a +> hostname, which Docker will not recognize in the context of the +> `--link=` option. + +You can run the `iptables` command on your Docker host to see whether +the `FORWARD` chain has a default policy of `ACCEPT` or `DROP`: + + # When --icc=false, you should see a DROP rule: + + $ sudo iptables -L -n + ... + Chain FORWARD (policy ACCEPT) + target prot opt source destination + DROP all -- 0.0.0.0/0 0.0.0.0/0 + ... + + # When a --link= has been created under --icc=false, + # you should see port-specific ACCEPT rules overriding + # the subsequent DROP policy for all other packets: + + $ sudo iptables -L -n + ... + Chain FORWARD (policy ACCEPT) + target prot opt source destination + ACCEPT tcp -- 172.17.0.2 172.17.0.3 tcp spt:80 + ACCEPT tcp -- 172.17.0.3 172.17.0.2 tcp dpt:80 + DROP all -- 0.0.0.0/0 0.0.0.0/0 + +> **Note**: +> Docker is careful that its host-wide `iptables` rules fully expose +> containers to each other’s raw IP addresses, so connections from one +> container to another should always appear to be originating from the +> first container’s own IP address. + +## Binding container ports to the host + +By default Docker containers can make connections to the outside world, +but the outside world cannot connect to containers. Each outgoing +connection will appear to originate from one of the host machine’s own +IP addresses thanks to an `iptables` masquerading rule on the host +machine that the Docker server creates when it starts: + + # You can see that the Docker server creates a + # masquerade rule that let containers connect + # to IP addresses in the outside world: + + $ sudo iptables -t nat -L -n + ... + Chain POSTROUTING (policy ACCEPT) + target prot opt source destination + MASQUERADE all -- 172.17.0.0/16 !172.17.0.0/16 + ... + +But if you want containers to accept incoming connections, you will need +to provide special options when invoking `docker run`. These options +are covered in more detail on the [Redirect Ports](port_redirection.md) +page. There are two approaches. + +First, you can supply `-P` or `--publish-all=true|false` to `docker run` +which is a blanket operation that identifies every port with an `EXPOSE` +line in the image’s `Dockerfile` and maps it to a host port somewhere in +the range 49000–49900. This tends to be a bit inconvenient, since you +then have to run other `docker` sub-commands to learn which external +port a given service was mapped to. + +More convenient is the `-p SPEC` or `--publish=SPEC` option which lets +you be explicit about exactly which external port on the Docker server — +which can be any port at all, not just those in the 49000–49900 block — +you want mapped to which port in the container. + +Either way, you should be able to peek at what Docker has accomplished +in your network stack by examining your NAT tables. + + # What your NAT rules might look like when Docker + # is finished setting up a -P forward: + + $ iptables -t nat -L -n + ... + Chain DOCKER (2 references) + target prot opt source destination + DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:49153 to:172.17.0.2:80 + + # What your NAT rules might look like when Docker + # is finished setting up a -p 80:80 forward: + + Chain DOCKER (2 references) + target prot opt source destination + DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80 + +You can see that Docker has exposed these container ports on `0.0.0.0`, +the wildcard IP address that will match any possible incoming port on +the host machine. If you want to be more restrictive and only allow +container services to be contacted through a specific external interface +on the host machine, you have two choices. When you invoke `docker run` +you can use either `-p IP:host_port:container_port` or `-p IP::port` to +specify the external interface for one particular binding. + +Or if you always want Docker port forwards to bind to one specific IP +address, you can edit your system-wide Docker server settings (on +Ubuntu, by editing `DOCKER_OPTS` in `/etc/default/docker`) and add the +option `--ip=IP_ADDRESS`. Remember to restart your Docker server after +editing this setting. + +Again, this topic is covered without all of these low-level networking +details in the [Redirect Ports](port_redirection.md) document if you +would like to use that as your port redirection reference instead. + +## Customizing docker0 + +By default, the Docker server creates and configures the host system’s +`docker0` interface as an *Ethernet bridge* inside the Linux kernel that +can pass packets back and forth between other physical or virtual +network interfaces so that they behave as a single Ethernet network. + +Docker configures `docker0` with an IP address and netmask so the host +machine can both receive and send packets to containers connected to the +bridge, and gives it an MTU — the *maximum transmission unit* or largest +packet length that the interface will allow — of either 1,500 bytes or +else a more specific value copied from the Docker host’s interface that +supports its default route. Both are configurable at server startup: + + * `--bip=CIDR` — supply a specific IP address and netmask for the + `docker0` bridge, using standard CIDR notation like + `192.168.1.5/24`. + + * `--mtu=BYTES` — override the maximum packet length on `docker0`. + +On Ubuntu you would add these to the `DOCKER_OPTS` setting in +`/etc/default/docker` on your Docker host and restarting the Docker +service. + +Once you have one or more containers up and running, you can confirm +that Docker has properly connected them to the `docker0` bridge by +running the `brctl` command on the host machine and looking at the +`interfaces` column of the output. Here is a host with two different +containers connected: # Display bridge info + $ sudo brctl show - bridge name bridge id STP enabled interfaces - bridge0 8000.fe7c2e0faebd no vethAQI2QT + bridge name bridge id STP enabled interfaces + docker0 8000.3a1d7362b4ee no veth65f9 + vethdda6 -## Container intercommunication +If the `brctl` command is not installed on your Docker host, then on +Ubuntu you should be able to run `sudo apt-get install bridge-utils` to +install it. -The value of the Docker daemon's `icc` parameter determines whether -containers can communicate with each other over the bridge network. +Finally, the `docker0` Ethernet bridge settings are used every time you +create a new container. Docker selects a free IP address from the range +available on the bridge each time you `docker run` a new container, and +configures the container’s `eth0` interface with that IP address and the +bridge’s netmask. The Docker host’s own IP address on the bridge is +used as the default gateway by which each container reaches the rest of +the Internet. - - The default, `-icc=true` allows containers to communicate with each other. - - `-icc=false` means containers are isolated from each other. + # The network, as seen from a container -Docker uses `iptables` under the hood to either accept or drop -communication between containers. + $ sudo docker run -i -t --rm base /bin/bash -## What is the vethXXXX device? + $$ ip addr show eth0 + 24: eth0: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 + link/ether 32:6f:e0:35:57:91 brd ff:ff:ff:ff:ff:ff + inet 172.17.0.3/16 scope global eth0 + valid_lft forever preferred_lft forever + inet6 fe80::306f:e0ff:fe35:5791/64 scope link + valid_lft forever preferred_lft forever -Well. Things get complicated here. + $$ ip route + default via 172.17.42.1 dev eth0 + 172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.3 -The `vethXXXX` interface is the host side of a point-to-point link -between the host and the corresponding container; the other side of the -link is the container's `eth0` interface. This pair (host `vethXXX` and -container `eth0`) are connected like a tube. Everything that comes in -one side will come out the other side. + $$ exit -All the plumbing is delegated to Linux network capabilities (check the -`ip link` command) and the namespaces infrastructure. +Remember that the Docker host will not be willing to forward container +packets out on to the Internet unless its `ip_forward` system setting is +`1` — see the section above on [Communication between +containers](#between-containers) for details. -## I want more +## Building your own bridge -Jérôme Petazzoni has create `pipework` to connect together containers in -arbitrarily complex scenarios: -[https://github.com/jpetazzo/pipework](https://github.com/jpetazzo/pipework) +If you want to take Docker out of the business of creating its own +Ethernet bridge entirely, you can set up your own bridge before starting +Docker and use `-b BRIDGE` or `--bridge=BRIDGE` to tell Docker to use +your bridge instead. If you already have Docker up and running with its +old `bridge0` still configured, you will probably want to begin by +stopping the service and removing the interface: + + # Stopping Docker and removing docker0 + + $ sudo service docker stop + $ sudo ip link set dev docker0 down + $ sudo brctl delbr docker0 + +Then, before starting the Docker service, create your own bridge and +give it whatever configuration you want. Here we will create a simple +enough bridge that we really could just have used the options in the +previous section to customize `docker0`, but it will be enough to +illustrate the technique. + + # Create our own bridge + + $ sudo brctl addbr bridge0 + $ sudo ip addr add 192.168.5.1/24 dev bridge0 + $ sudo ip link set dev bridge0 up + + # Confirming that our bridge is up and running + + $ ip addr show bridge0 + 4: bridge0: mtu 1500 qdisc noop state UP group default + link/ether 66:38:d0:0d:76:18 brd ff:ff:ff:ff:ff:ff + inet 192.168.5.1/24 scope global bridge0 + valid_lft forever preferred_lft forever + + # Tell Docker about it and restart (on Ubuntu) + + $ echo 'DOCKER_OPTS="-b=bridge0"' >> /etc/default/docker + $ sudo service docker start + +The result should be that the Docker server starts successfully and is +now prepared to bind containers to the new bridge. After pausing to +verify the bridge’s configuration, try creating a container — you will +see that its IP address is in your new IP address range, which Docker +will have auto-detected. + +Just as we learned in the previous section, you can use the `brctl show` +command to see Docker add and remove interfaces from the bridge as you +start and stop containers, and can run `ip addr` and `ip route` inside a +container to see that it has been given an address in the bridge’s IP +address range and has been told to use the Docker host’s IP address on +the bridge as its default gateway to the rest of the Internet. + +## How Docker networks a container + +While Docker is under active development and continues to tweak and +improve its network configuration logic, the shell commands in this +section are rough equivalents to the steps that Docker takes when +configuring networking for each new container. + +Let’s review a few basics. + +To communicate using the Internet Protocol (IP), a machine needs access +to at least one network interface at which packets can be sent and +received, and a routing table that defines the range of IP addresses +reachable through that interface. Network interfaces do not have to be +physical devices. In fact, the `lo` loopback interface available on +every Linux machine (and inside each Docker container) is entirely +virtual — the Linux kernel simply copies loopback packets directly from +the sender’s memory into the receiver’s memory. + +Docker uses special virtual interfaces to let containers communicate +with the host machine — pairs of virtual interfaces called “peers” that +are linked inside of the host machine’s kernel so that packets can +travel between them. They are simple to create, as we will see in a +moment. + +The steps with which Docker configures a container are: + +1. Create a pair of peer virtual interfaces. + +2. Give one of them a unique name like `veth65f9`, keep it inside of + the main Docker host, and bind it to `docker0` or whatever bridge + Docker is supposed to be using. + +3. Toss the other interface over the wall into the new container (which + will already have been provided with an `lo` interface) and rename + it to the much prettier name `eth0` since, inside of the container’s + separate and unique network interface namespace, there are no + physical interfaces with which this name could collide. + +4. Give the container’s `eth0` a new IP address from within the + bridge’s range of network addresses, and set its default route to + the IP address that the Docker host owns on the bridge. + +With these steps complete, the container now possesses an `eth0` +(virtual) network card and will find itself able to communicate with +other containers and the rest of the Internet. + +You can opt out of the above process for a particular container by +giving the `--net=` option to `docker run`, which takes four possible +values. + + * `--net=bridge` — The default action, that connects the container to + the Docker bridge as described above. + + * `--net=host` — Tells Docker to skip placing the container inside of + a separate network stack. In essence, this choice tells Docker to + **not containerize the container’s networking**! While container + processes will still be confined to their own filesystem and process + list and resource limits, a quick `ip addr` command will show you + that, network-wise, they live “outside” in the main Docker host and + have full access to its network interfaces. Note that this does + **not** let the container reconfigure the host network stack — that + would require `--privileged=true` — but it does let container + processes open low-numbered ports like any other root process. + + * `--net=container:NAME_or_ID` — Tells Docker to put this container’s + processes inside of the network stack that has already been created + inside of another container. The new container’s processes will be + confined to their own filesystem and process list and resource + limits, but will share the same IP address and port numbers as the + first container, and processes on the two containers will be able to + connect to each other over the loopback interface. + + * `--net=none` — Tells Docker to put the container inside of its own + network stack but not to take any steps to configure its network, + leaving you free to build any of the custom configurations explored + in the last few sections of this document. + +To get an idea of the steps that are necessary if you use `--net=none` +as described in that last bullet point, here are the commands that you +would run to reach roughly the same configuration as if you had let +Docker do all of the configuration: + + # At one shell, start a container and + # leave its shell idle and running + + $ sudo docker run -i -t --rm --net=none base /bin/bash + root@63f36fc01b5f:/# + + # At another shell, learn the container process ID + # and create its namespace entry in /var/run/netns/ + # for the "ip netns" command we will be using below + + $ sudo docker inspect -f '{{.State.Pid}}' 63f36fc01b5f + 2778 + $ pid=2778 + $ sudo mkdir -p /var/run/netns + $ sudo ln -s /proc/$pid/ns/net /var/run/netns/$pid + + # Check the bridge’s IP address and netmask + + $ ip addr show docker0 + 21: docker0: ... + inet 172.17.42.1/16 scope global docker0 + ... + + # Create a pair of "peer" interfaces A and B, + # bind the A end to the bridge, and bring it up + + $ sudo ip link add A type veth peer name B + $ sudo brctl addif docker0 A + $ sudo ip link set A up + + # Place B inside the container's network namespace, + # rename to eth0, and activate it with a free IP + + $ sudo ip link set B netns $pid + $ sudo ip netns exec $pid ip link set dev B name eth0 + $ sudo ip netns exec $pid ip link set eth0 up + $ sudo ip netns exec $pid ip addr add 172.17.42.99/16 dev eth0 + $ sudo ip netns exec $pid ip route add default via 172.17.42.1 + +At this point your container should be able to perform networking +operations as usual. + +When you finally exit the shell and Docker cleans up the container, the +network namespace is destroyed along with our virtual `eth0` — whose +destruction in turn destroys interface `A` out in the Docker host and +automatically un-registers it from the `docker0` bridge. So everything +gets cleaned up without our having to run any extra commands! Well, +almost everything: + + # Clean up dangling symlinks in /var/run/netns + + find -L /var/run/netns -type l -delete + +Also note that while the script above used modern `ip` command instead +of old deprecated wrappers like `ipconfig` and `route`, these older +commands would also have worked inside of our container. The `ip addr` +command can be typed as `ip a` if you are in a hurry. + +Finally, note the importance of the `ip netns exec` command, which let +us reach inside and configure a network namespace as root. The same +commands would not have worked if run inside of the container, because +part of safe containerization is that Docker strips container processes +of the right to configure their own networks. Using `ip netns exec` is +what let us finish up the configuration without having to take the +dangerous step of running the container itself with `--privileged=true`. + +## Tools and Examples + +Before diving into the following sections on custom network topologies, +you might be interested in glancing at a few external tools or examples +of the same kinds of configuration. Here are two: + + * Jérôme Petazzoni has create a `pipework` shell script to help you + connect together containers in arbitrarily complex scenarios: + + + * Brandon Rhodes has created a whole network topology of Docker + containers for the next edition of Foundations of Python Network + Programming that includes routing, NAT’d firewalls, and servers that + offer HTTP, SMTP, POP, IMAP, Telnet, SSH, and FTP: + + +Both tools use networking commands very much like the ones you saw in +the previous section, and will see in the following sections. + +## Building a point-to-point connection + +By default, Docker attaches all containers to the virtual subnet +implemented by `docker0`. You can create containers that are each +connected to some different virtual subnet by creating your own bridge +as shown in [Building your own bridge](#bridge-building), starting each +container with `docker run --net=none`, and then attaching the +containers to your bridge with the shell commands shown in [How Docker +networks a container](#container-networking). + +But sometimes you want two particular containers to be able to +communicate directly without the added complexity of both being bound to +a host-wide Ethernet bridge. + +The solution is simple: when you create your pair of peer interfaces, +simply throw *both* of them into containers, and configure them as +classic point-to-point links. The two containers will then be able to +communicate directly (provided you manage to tell each container the +other’s IP address, of course). You might adjust the instructions of +the previous section to go something like this: + + # Start up two containers in two terminal windows + + $ sudo docker run -i -t --rm --net=none base /bin/bash + root@1f1f4c1f931a:/# + + $ sudo docker run -i -t --rm --net=none base /bin/bash + root@12e343489d2f:/# + + # Learn the container process IDs + # and create their namespace entries + + $ sudo docker inspect -f '{{.State.Pid}}' 1f1f4c1f931a + 2989 + $ sudo docker inspect -f '{{.State.Pid}}' 12e343489d2f + 3004 + $ sudo mkdir -p /var/run/netns + $ sudo ln -s /proc/2989/ns/net /var/run/netns/2989 + $ sudo ln -s /proc/3004/ns/net /var/run/netns/3004 + + # Create the "peer" interfaces and hand them out + + $ sudo ip link add A type veth peer name B + + $ sudo ip link set A netns 2989 + $ sudo ip netns exec 2989 ip addr add 10.1.1.1/32 dev A + $ sudo ip netns exec 2989 ip link set A up + $ sudo ip netns exec 2989 ip route add 10.1.1.2/32 dev A + + $ sudo ip link set B netns 3004 + $ sudo ip netns exec 3004 ip addr add 10.1.1.2/32 dev B + $ sudo ip netns exec 3004 ip link set B up + $ sudo ip netns exec 3004 ip route add 10.1.1.1/32 dev B + +The two containers should now be able to ping each other and make +connections sucessfully. Point-to-point links like this do not depend +on a subnet nor a netmask, but on the bare assertion made by `ip route` +that some other single IP address is connected to a particular network +interface. + +Note that point-to-point links can be safely combined with other kinds +of network connectivity — there is no need to start the containers with +`--net=none` if you want point-to-point links to be an addition to the +container’s normal networking instead of a replacement. + +A final permutation of this pattern is to create the point-to-point link +between the Docker host and one container, which would allow the host to +communicate with that one container on some single IP address and thus +communicate “out-of-band” of the bridge that connects the other, more +usual containers. But unless you have very specific networking needs +that drive you to such a solution, it is probably far preferable to use +`--icc=false` to lock down inter-container communication, as we explored +earlier. From 7cd1e482309e944522d3506f8e15eeb3ab7fb796 Mon Sep 17 00:00:00 2001 From: Alexandr Morozov Date: Sun, 18 May 2014 15:17:31 +0400 Subject: [PATCH 164/400] Fix hanging on tests failing Tee hanging when `go test` exit with non-zero code. Fixes #5672 Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- hack/make/test-integration | 4 ++-- hack/make/test-integration-cli | 3 ++- hack/make/test-unit | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/hack/make/test-integration b/hack/make/test-integration index 4c2bccaea..baad1349a 100644 --- a/hack/make/test-integration +++ b/hack/make/test-integration @@ -10,6 +10,6 @@ bundle_test_integration() { # this "grep" hides some really irritating warnings that "go test -coverpkg" # spews when it is given packages that aren't used +exec > >(tee -a $DEST/test.log) 2>&1 bundle_test_integration 2>&1 \ - | grep --line-buffered -v '^warning: no packages being tested depend on ' \ - | tee $DEST/test.log + | grep --line-buffered -v '^warning: no packages being tested depend on ' diff --git a/hack/make/test-integration-cli b/hack/make/test-integration-cli index f2128a26a..837bd8737 100644 --- a/hack/make/test-integration-cli +++ b/hack/make/test-integration-cli @@ -11,6 +11,7 @@ bundle_test_integration_cli() { } # subshell so that we can export PATH without breaking other things +exec > >(tee -a $DEST/test.log) 2>&1 ( export PATH="$DEST/../binary:$DEST/../dynbinary:$PATH" @@ -40,4 +41,4 @@ bundle_test_integration_cli() { DOCKERD_PID=$(set -x; cat $DEST/docker.pid) ( set -x; kill $DOCKERD_PID ) wait $DOCKERD_PID || true -) 2>&1 | tee $DEST/test.log +) diff --git a/hack/make/test-unit b/hack/make/test-unit index 066865859..552810f34 100644 --- a/hack/make/test-unit +++ b/hack/make/test-unit @@ -49,7 +49,8 @@ bundle_test_unit() { echo true fi - } 2>&1 | tee $DEST/test.log + } } +exec > >(tee -a $DEST/test.log) 2>&1 bundle_test_unit From 702442b58696b1a77d9274c92000f6e0c0c983bb Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 19 May 2014 10:07:57 -0400 Subject: [PATCH 165/400] Adding the FAQ back Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- docs/mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index ca1a4d74e..96cf3c6e0 100755 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -95,6 +95,7 @@ pages: - ['reference/commandline/index.md', '**HIDDEN**'] - ['reference/commandline/cli.md', 'Reference', 'Command line'] - ['reference/builder.md', 'Reference', 'Dockerfile'] +- ['faq.md', 'Reference', 'FAQ'] - ['reference/run.md', 'Reference', 'Run Reference'] - ['articles/index.md', '**HIDDEN**'] - ['articles/runmetrics.md', 'Reference', 'Runtime metrics'] From e1c7abe8905d4cc034f1ed49e9d102846e412424 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Sat, 17 May 2014 02:03:26 +0000 Subject: [PATCH 166/400] Add the rest of the caps so that they are retained in privilged mode Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/types.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pkg/libcontainer/types.go b/pkg/libcontainer/types.go index 07694e9f5..834201036 100644 --- a/pkg/libcontainer/types.go +++ b/pkg/libcontainer/types.go @@ -61,6 +61,23 @@ var ( {Key: "NET_RAW", Value: capability.CAP_NET_RAW}, {Key: "DAC_OVERRIDE", Value: capability.CAP_DAC_OVERRIDE}, {Key: "FOWNER", Value: capability.CAP_FOWNER}, + {Key: "DAC_READ_SEARCH", Value: capability.CAP_DAC_READ_SEARCH}, + {Key: "FSETID", Value: capability.CAP_FSETID}, + {Key: "KILL", Value: capability.CAP_KILL}, + {Key: "SETGID", Value: capability.CAP_SETGID}, + {Key: "SETUID", Value: capability.CAP_SETUID}, + {Key: "LINUX_IMMUTABLE", Value: capability.CAP_LINUX_IMMUTABLE}, + {Key: "NET_BIND_SERVICE", Value: capability.CAP_NET_BIND_SERVICE}, + {Key: "NET_BROADCAST", Value: capability.CAP_NET_BROADCAST}, + {Key: "IPC_LOCK", Value: capability.CAP_IPC_LOCK}, + {Key: "IPC_OWNER", Value: capability.CAP_IPC_OWNER}, + {Key: "SYS_CHROOT", Value: capability.CAP_SYS_CHROOT}, + {Key: "SYS_PTRACE", Value: capability.CAP_SYS_PTRACE}, + {Key: "SYS_BOOT", Value: capability.CAP_SYS_BOOT}, + {Key: "LEASE", Value: capability.CAP_LEASE}, + {Key: "SETFCAP", Value: capability.CAP_SETFCAP}, + {Key: "WAKE_ALARM", Value: capability.CAP_WAKE_ALARM}, + {Key: "BLOCK_SUSPEND", Value: capability.CAP_BLOCK_SUSPEND}, } ) From 0abad3ae2290a2b051b8fdaceab17a1ee41ecfb9 Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Mon, 19 May 2014 16:45:52 +0000 Subject: [PATCH 167/400] Don't drop CAP_FOWNER in the container. Also sorts the list of allowed capabilities. Docker-DCO-1.1-Signed-off-by: Victor Marmol (github: vmarmol) --- daemon/execdriver/native/template/default_template.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/daemon/execdriver/native/template/default_template.go b/daemon/execdriver/native/template/default_template.go index 66cfa88a3..5c4160342 100644 --- a/daemon/execdriver/native/template/default_template.go +++ b/daemon/execdriver/native/template/default_template.go @@ -10,12 +10,13 @@ import ( func New() *libcontainer.Container { container := &libcontainer.Container{ Capabilities: []string{ - "MKNOD", - "SETUID", - "SETGID", "CHOWN", - "NET_RAW", "DAC_OVERRIDE", + "FOWNER", + "MKNOD", + "NET_RAW", + "SETGID", + "SETUID", }, Namespaces: map[string]bool{ "NEWNS": true, From 68493e2f7f9cb8303302e1098e3293b521ace243 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 19 May 2014 09:51:20 +0200 Subject: [PATCH 168/400] Make /proc writable, but not /proc/sys and /proc/sysrq-trigger Some applications want to write to /proc. For instance: docker run -it centos groupadd foo Gives: groupadd: failure while writing changes to /etc/group And strace reveals why: open("/proc/self/task/13/attr/fscreate", O_RDWR) = -1 EROFS (Read-only file system) I've looked at what other systems do, and systemd-nspawn makes /proc read-write and /proc/sys readonly, while lxc allows "proc:mixed" which does the same, plus it makes /proc/sysrq-trigger also readonly. The later seems like a prudent idea, so we follows lxc proc:mixed. Additionally we make /proc/irq and /proc/bus, as these seem to let you control various hardware things. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- pkg/libcontainer/nsinit/init.go | 2 +- .../security/restrict/restrict.go | 21 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index af60d739c..25b5fc433 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -81,7 +81,7 @@ func Init(container *libcontainer.Container, uncleanRootfs, consolePath string, return fmt.Errorf("set process label %s", err) } if container.Context["restrictions"] != "" { - if err := restrict.Restrict("proc", "sys"); err != nil { + if err := restrict.Restrict("proc/sys", "proc/sysrq-trigger", "proc/irq", "proc/bus", "sys"); err != nil { return err } } diff --git a/pkg/libcontainer/security/restrict/restrict.go b/pkg/libcontainer/security/restrict/restrict.go index 361d0764a..a22a1aa73 100644 --- a/pkg/libcontainer/security/restrict/restrict.go +++ b/pkg/libcontainer/security/restrict/restrict.go @@ -10,12 +10,31 @@ import ( "github.com/dotcloud/docker/pkg/system" ) +const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV + +func mountReadonly(path string) error { + if err := system.Mount("", path, "", syscall.MS_REMOUNT|syscall.MS_RDONLY, ""); err != nil { + if err == syscall.EINVAL { + // Probably not a mountpoint, use bind-mount + if err := system.Mount(path, path, "", syscall.MS_BIND, ""); err != nil { + return err + } + if err := system.Mount(path, path, "", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC|defaultMountFlags, ""); err != nil { + return err + } + } else { + return err + } + } + return nil +} + // This has to be called while the container still has CAP_SYS_ADMIN (to be able to perform mounts). // However, afterwards, CAP_SYS_ADMIN should be dropped (otherwise the user will be able to revert those changes). func Restrict(mounts ...string) error { // remount proc and sys as readonly for _, dest := range mounts { - if err := system.Mount("", dest, "", syscall.MS_REMOUNT|syscall.MS_RDONLY, ""); err != nil { + if err := mountReadonly(dest); err != nil { return fmt.Errorf("unable to remount %s readonly: %s", dest, err) } } From e39299ca1d50df90b085e6a2e3e3f4b6897e5a59 Mon Sep 17 00:00:00 2001 From: unclejack Date: Mon, 19 May 2014 17:51:47 +0300 Subject: [PATCH 169/400] docs: add back build to remote api v1.10 & v1.11 This adds back the rm query parameter to the remote api docs for api v1.10 and v1.11. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- docs/sources/reference/api/docker_remote_api_v1.10.md | 1 + docs/sources/reference/api/docker_remote_api_v1.11.md | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/sources/reference/api/docker_remote_api_v1.10.md b/docs/sources/reference/api/docker_remote_api_v1.10.md index 721244b49..f743cb0b2 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.10.md +++ b/docs/sources/reference/api/docker_remote_api_v1.10.md @@ -1023,6 +1023,7 @@ Build an image from Dockerfile via stdin the resulting image in case of success - **q** – suppress verbose build output - **nocache** – do not use the cache when building the image + - **rm** - remove intermediate containers after a successful build Request Headers: diff --git a/docs/sources/reference/api/docker_remote_api_v1.11.md b/docs/sources/reference/api/docker_remote_api_v1.11.md index 53e07b380..8f4709ee6 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.11.md +++ b/docs/sources/reference/api/docker_remote_api_v1.11.md @@ -1063,6 +1063,7 @@ Build an image from Dockerfile via stdin the resulting image in case of success - **q** – suppress verbose build output - **nocache** – do not use the cache when building the image + - **rm** - remove intermediate containers after a successful build Request Headers: From 620c8c72535670e8358cebc65a2fb424be58a93c Mon Sep 17 00:00:00 2001 From: Alexandr Morozov Date: Mon, 19 May 2014 23:07:31 +0400 Subject: [PATCH 170/400] Make chmod on ADDed files Fixes #3979 Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- .../build_tests/TestAdd/SingleFileToRoot/Dockerfile | 1 + .../build_tests/TestAdd/WholeDirToRoot/Dockerfile | 2 ++ server/buildfile.go | 13 ++++++++----- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/integration-cli/build_tests/TestAdd/SingleFileToRoot/Dockerfile b/integration-cli/build_tests/TestAdd/SingleFileToRoot/Dockerfile index d6375debe..e96201d85 100644 --- a/integration-cli/build_tests/TestAdd/SingleFileToRoot/Dockerfile +++ b/integration-cli/build_tests/TestAdd/SingleFileToRoot/Dockerfile @@ -5,4 +5,5 @@ RUN touch /exists RUN chown dockerio.dockerio /exists ADD test_file / RUN [ $(ls -l /test_file | awk '{print $3":"$4}') = 'root:root' ] +RUN [ $(ls -l /test_file | awk '{print $1}') = '-rwxr-xr-x' ] RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] diff --git a/integration-cli/build_tests/TestAdd/WholeDirToRoot/Dockerfile b/integration-cli/build_tests/TestAdd/WholeDirToRoot/Dockerfile index 3db6d3fd9..2f1097948 100644 --- a/integration-cli/build_tests/TestAdd/WholeDirToRoot/Dockerfile +++ b/integration-cli/build_tests/TestAdd/WholeDirToRoot/Dockerfile @@ -5,5 +5,7 @@ RUN touch /exists RUN chown dockerio.dockerio exists ADD test_dir /test_dir RUN [ $(ls -l / | grep test_dir | awk '{print $3":"$4}') = 'root:root' ] +RUN [ $(ls -l / | grep test_dir | awk '{print $1}') = 'drwxr-xr-x' ] RUN [ $(ls -l /test_dir/test_file | awk '{print $3":"$4}') = 'root:root' ] +RUN [ $(ls -l /test_dir/test_file | awk '{print $1}') = '-rwxr-xr-x' ] RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] diff --git a/server/buildfile.go b/server/buildfile.go index 995390494..efe186950 100644 --- a/server/buildfile.go +++ b/server/buildfile.go @@ -431,9 +431,12 @@ func (b *buildFile) addContext(container *daemon.Container, orig, dest string, r return err } - chownR := func(destPath string, uid, gid int) error { + fixPermsR := func(destPath string, uid, gid int) error { return filepath.Walk(destPath, func(path string, info os.FileInfo, err error) error { - if err := os.Lchown(path, uid, gid); err != nil { + if err := os.Lchown(path, uid, gid); err != nil && !os.IsNotExist(err) { + return err + } + if err := os.Chmod(path, 0755); err != nil && !os.IsNotExist(err) { return err } return nil @@ -450,12 +453,12 @@ func (b *buildFile) addContext(container *daemon.Container, orig, dest string, r return err } for _, file := range files { - if err := chownR(filepath.Join(destPath, file.Name()), 0, 0); err != nil { + if err := fixPermsR(filepath.Join(destPath, file.Name()), 0, 0); err != nil { return err } } } else { - if err := chownR(destPath, 0, 0); err != nil { + if err := fixPermsR(destPath, 0, 0); err != nil { return err } } @@ -494,7 +497,7 @@ func (b *buildFile) addContext(container *daemon.Container, orig, dest string, r resPath = path.Join(destPath, path.Base(origPath)) } - if err := chownR(resPath, 0, 0); err != nil { + if err := fixPermsR(resPath, 0, 0); err != nil { return err } return nil From 667e2bd4ea5fbc8698c34565f955cb92cff92890 Mon Sep 17 00:00:00 2001 From: unclejack Date: Fri, 16 May 2014 14:47:33 +0300 Subject: [PATCH 171/400] bump remote api to 1.12 & add --force-rm to build This adds a `--force-rm` flag to docker build which makes the Docker daemon clean up all containers, even when the build has failed. This new flag requires that we bump the remote API, so we also bump the remote API version. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- api/client/commands.go | 5 + api/common.go | 2 +- api/server/server.go | 7 +- .../reference/api/docker_remote_api.md | 16 +- .../reference/api/docker_remote_api_v1.12.md | 1363 +++++++++++++++++ docs/sources/reference/commandline/cli.md | 1 + integration/buildfile_test.go | 6 +- server/buildfile.go | 7 +- server/server.go | 3 +- 9 files changed, 1400 insertions(+), 10 deletions(-) create mode 100644 docs/sources/reference/api/docker_remote_api_v1.12.md diff --git a/api/client/commands.go b/api/client/commands.go index 6562709f3..7f4288ec2 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -110,6 +110,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers") noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image") rm := cmd.Bool([]string{"#rm", "-rm"}, true, "Remove intermediate containers after a successful build") + forceRm := cmd.Bool([]string{"-force-rm"}, false, "Always remove intermediate containers, even after unsuccessful builds") if err := cmd.Parse(args); err != nil { return nil } @@ -199,6 +200,10 @@ func (cli *DockerCli) CmdBuild(args ...string) error { v.Set("rm", "1") } + if *forceRm { + v.Set("forcerm", "1") + } + cli.LoadConfigFile() headers := http.Header(make(map[string][]string)) diff --git a/api/common.go b/api/common.go index f4e31a970..a20c5d7d1 100644 --- a/api/common.go +++ b/api/common.go @@ -11,7 +11,7 @@ import ( ) const ( - APIVERSION version.Version = "1.11" + APIVERSION version.Version = "1.12" DEFAULTHTTPHOST = "127.0.0.1" DEFAULTUNIXSOCKET = "/var/run/docker.sock" ) diff --git a/api/server/server.go b/api/server/server.go index f08f39540..0ac57471a 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -903,12 +903,17 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite } else { job.Stdout.Add(utils.NewWriteFlusher(w)) } + if r.FormValue("forcerm") == "1" && version.GreaterThanOrEqualTo("1.12") { + job.Setenv("rm", "1") + } else { + job.Setenv("rm", r.FormValue("rm")) + } job.Stdin.Add(r.Body) job.Setenv("remote", r.FormValue("remote")) job.Setenv("t", r.FormValue("t")) job.Setenv("q", r.FormValue("q")) job.Setenv("nocache", r.FormValue("nocache")) - job.Setenv("rm", r.FormValue("rm")) + job.Setenv("forcerm", r.FormValue("forcerm")) job.SetenvJson("authConfig", authConfig) job.SetenvJson("configFile", configFile) diff --git a/docs/sources/reference/api/docker_remote_api.md b/docs/sources/reference/api/docker_remote_api.md index 47f4724b1..84b56866b 100644 --- a/docs/sources/reference/api/docker_remote_api.md +++ b/docs/sources/reference/api/docker_remote_api.md @@ -20,13 +20,23 @@ page_keywords: API, Docker, rcli, REST, documentation -The current version of the API is v1.11 +The current version of the API is v1.12 Calling /images//insert is the same as calling -/v1.11/images//insert +/v1.12/images//insert You can still call an old version of the api using -/v1.11/images//insert +/v1.12/images//insert + +## v1.12 + +### Full Documentation + +[*Docker Remote API v1.12*](/reference/api/docker_remote_api_v1.12/) + +### What's new + +docker build now has support for the `forcerm` parameter to always remove containers ## v1.11 diff --git a/docs/sources/reference/api/docker_remote_api_v1.12.md b/docs/sources/reference/api/docker_remote_api_v1.12.md new file mode 100644 index 000000000..d2b7f8f90 --- /dev/null +++ b/docs/sources/reference/api/docker_remote_api_v1.12.md @@ -0,0 +1,1363 @@ +page_title: Remote API v1.12 +page_description: API Documentation for Docker +page_keywords: API, Docker, rcli, REST, documentation + +# Docker Remote API v1.12 + +## 1. Brief introduction + + - The Remote API has replaced rcli + - The daemon listens on `unix:///var/run/docker.sock` but you can + [*Bind Docker to another host/port or a Unix socket*]( + /use/basics/#bind-docker). + - The API tends to be REST, but for some complex commands, like `attach` + or `pull`, the HTTP connection is hijacked to transport `stdout, stdin` + and `stderr` + +# 2. Endpoints + +## 2.1 Containers + +### List containers + +`GET /containers/json` + +List containers + + **Example request**: + + GET /containers/json?all=1&before=8dfafdbc3a40&size=1 HTTP/1.1 + + **Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Id": "8dfafdbc3a40", + "Image": "base:latest", + "Command": "echo 1", + "Created": 1367854155, + "Status": "Exit 0", + "Ports":[{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "9cd87474be90", + "Image": "base:latest", + "Command": "echo 222222", + "Created": 1367854155, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "3176a2479c92", + "Image": "base:latest", + "Command": "echo 3333333333333333", + "Created": 1367854154, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "4cb07b47f9fb", + "Image": "base:latest", + "Command": "echo 444444444444444444444444444444444", + "Created": 1367854152, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + } + ] + + Query Parameters: + +   + + - **all** – 1/True/true or 0/False/false, Show all containers. + Only running containers are shown by default + - **limit** – Show `limit` last created + containers, include non-running ones. + - **since** – Show only containers created since Id, include + non-running ones. + - **before** – Show only containers created before Id, include + non-running ones. + - **size** – 1/True/true or 0/False/false, Show the containers + sizes + + Status Codes: + + - **200** – no error + - **400** – bad parameter + - **500** – server error + +### Create a container + +`POST /containers/create` + +Create a container + + **Example request**: + + POST /containers/create HTTP/1.1 + Content-Type: application/json + + { + "Hostname":"", + "User":"", + "Memory":0, + "MemorySwap":0, + "AttachStdin":false, + "AttachStdout":true, + "AttachStderr":true, + "PortSpecs":null, + "Tty":false, + "OpenStdin":false, + "StdinOnce":false, + "Env":null, + "Cmd":[ + "date" + ], + "Dns":null, + "Image":"base", + "Volumes":{ + "/tmp": {} + }, + "VolumesFrom":"", + "WorkingDir":"", + "DisableNetwork": false, + "ExposedPorts":{ + "22/tcp": {} + } + } + + **Example response**: + + HTTP/1.1 201 OK + Content-Type: application/json + + { + "Id":"e90e34656806" + "Warnings":[] + } + + Json Parameters: + +   + + - **config** – the container's configuration + + Query Parameters: + +   + + - **name** – Assign the specified name to the container. Must + match `/?[a-zA-Z0-9_-]+`. + + Status Codes: + + - **201** – no error + - **404** – no such container + - **406** – impossible to attach (container not running) + - **500** – server error + +### Inspect a container + +`GET /containers/(id)/json` + +Return low-level information on the container `id` + + + **Example request**: + + GET /containers/4fa6e0f0c678/json HTTP/1.1 + + **Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", + "Created": "2013-05-07T14:51:42.041847+02:00", + "Path": "date", + "Args": [], + "Config": { + "Hostname": "4fa6e0f0c678", + "User": "", + "Memory": 0, + "MemorySwap": 0, + "AttachStdin": false, + "AttachStdout": true, + "AttachStderr": true, + "PortSpecs": null, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": null, + "Cmd": [ + "date" + ], + "Dns": null, + "Image": "base", + "Volumes": {}, + "VolumesFrom": "", + "WorkingDir":"" + + }, + "State": { + "Running": false, + "Pid": 0, + "ExitCode": 0, + "StartedAt": "2013-05-07T14:51:42.087658+02:01360", + "Ghost": false + }, + "Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "NetworkSettings": { + "IpAddress": "", + "IpPrefixLen": 0, + "Gateway": "", + "Bridge": "", + "PortMapping": null + }, + "SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker", + "ResolvConfPath": "/etc/resolv.conf", + "Volumes": {}, + "HostConfig": { + "Binds": null, + "ContainerIDFile": "", + "LxcConf": [], + "Privileged": false, + "PortBindings": { + "80/tcp": [ + { + "HostIp": "0.0.0.0", + "HostPort": "49153" + } + ] + }, + "Links": null, + "PublishAllPorts": false + } + } + + Status Codes: + + - **200** – no error + - **404** – no such container + - **500** – server error + +### List processes running inside a container + +`GET /containers/(id)/top` + +List processes running inside the container `id` + + **Example request**: + + GET /containers/4fa6e0f0c678/top HTTP/1.1 + + **Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Titles":[ + "USER", + "PID", + "%CPU", + "%MEM", + "VSZ", + "RSS", + "TTY", + "STAT", + "START", + "TIME", + "COMMAND" + ], + "Processes":[ + ["root","20147","0.0","0.1","18060","1864","pts/4","S","10:06","0:00","bash"], + ["root","20271","0.0","0.0","4312","352","pts/4","S+","10:07","0:00","sleep","10"] + ] + } + + Query Parameters: + +   + + - **ps_args** – ps arguments to use (eg. aux) + + Status Codes: + + - **200** – no error + - **404** – no such container + - **500** – server error + +### Get container logs + +`GET /containers/(id)/logs` + +Get stdout and stderr logs from the container ``id`` + + **Example request**: + + GET /containers/4fa6e0f0c678/logs?stderr=1&stdout=1×tamps=1&follow=1 HTTP/1.1 + + **Example response**: + + HTTP/1.1 200 OK + Content-Type: application/vnd.docker.raw-stream + + {{ STREAM }} + + Query Parameters: + +   + + - **follow** – 1/True/true or 0/False/false, return stream. + Default false + - **stdout** – 1/True/true or 0/False/false, if logs=true, return + stdout log. Default false + - **stderr** – 1/True/true or 0/False/false, if logs=true, return + stderr log. Default false + - **timestamps** – 1/True/true or 0/False/false, if logs=true, print + timestamps for every log line. Default false + + Status Codes: + + - **200** – no error + - **404** – no such container + - **500** – server error + +### Inspect changes on a container's filesystem + +`GET /containers/(id)/changes` + +Inspect changes on container `id`'s filesystem + + **Example request**: + + GET /containers/4fa6e0f0c678/changes HTTP/1.1 + + **Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Path":"/dev", + "Kind":0 + }, + { + "Path":"/dev/kmsg", + "Kind":1 + }, + { + "Path":"/test", + "Kind":1 + } + ] + + Status Codes: + + - **200** – no error + - **404** – no such container + - **500** – server error + +### Export a container + +`GET /containers/(id)/export` + +Export the contents of container `id` + + **Example request**: + + GET /containers/4fa6e0f0c678/export HTTP/1.1 + + **Example response**: + + HTTP/1.1 200 OK + Content-Type: application/octet-stream + + {{ STREAM }} + + Status Codes: + + - **200** – no error + - **404** – no such container + - **500** – server error + +### Start a container + +`POST /containers/(id)/start` + +Start the container `id` + + **Example request**: + + POST /containers/(id)/start HTTP/1.1 + Content-Type: application/json + + { + "Binds":["/tmp:/tmp"], + "LxcConf":{"lxc.utsname":"docker"}, + "PortBindings":{ "22/tcp": [{ "HostPort": "11022" }] }, + "PublishAllPorts":false, + "Privileged":false + } + + **Example response**: + + HTTP/1.1 204 No Content + Content-Type: text/plain + + Json Parameters: + +   + + - **hostConfig** – the container's host configuration (optional) + + Status Codes: + + - **204** – no error + - **404** – no such container + - **500** – server error + +### Stop a container + +`POST /containers/(id)/stop` + +Stop the container `id` + + **Example request**: + + POST /containers/e90e34656806/stop?t=5 HTTP/1.1 + + **Example response**: + + HTTP/1.1 204 OK + + Query Parameters: + +   + + - **t** – number of seconds to wait before killing the container + + Status Codes: + + - **204** – no error + - **404** – no such container + - **500** – server error + +### Restart a container + +`POST /containers/(id)/restart` + +Restart the container `id` + + **Example request**: + + POST /containers/e90e34656806/restart?t=5 HTTP/1.1 + + **Example response**: + + HTTP/1.1 204 OK + + Query Parameters: + +   + + - **t** – number of seconds to wait before killing the container + + Status Codes: + + - **204** – no error + - **404** – no such container + - **500** – server error + +### Kill a container + +`POST /containers/(id)/kill` + +Kill the container `id` + + **Example request**: + + POST /containers/e90e34656806/kill HTTP/1.1 + + **Example response**: + + HTTP/1.1 204 OK + + Query Parameters + + - **signal** - Signal to send to the container: integer or string like "SIGINT". + When not set, SIGKILL is assumed and the call will waits for the container to exit. + + Status Codes: + + - **204** – no error + - **404** – no such container + - **500** – server error + +### Attach to a container + +`POST /containers/(id)/attach` + +Attach to the container `id` + + **Example request**: + + POST /containers/16253994b7c4/attach?logs=1&stream=0&stdout=1 HTTP/1.1 + + **Example response**: + + HTTP/1.1 200 OK + Content-Type: application/vnd.docker.raw-stream + + {{ STREAM }} + + Query Parameters: + +   + + - **logs** – 1/True/true or 0/False/false, return logs. Default + false + - **stream** – 1/True/true or 0/False/false, return stream. + Default false + - **stdin** – 1/True/true or 0/False/false, if stream=true, attach + to stdin. Default false + - **stdout** – 1/True/true or 0/False/false, if logs=true, return + stdout log, if stream=true, attach to stdout. Default false + - **stderr** – 1/True/true or 0/False/false, if logs=true, return + stderr log, if stream=true, attach to stderr. Default false + + Status Codes: + + - **200** – no error + - **400** – bad parameter + - **404** – no such container + - **500** – server error + + **Stream details**: + + When using the TTY setting is enabled in + [`POST /containers/create` + ](../docker_remote_api_v1.9/#post--containers-create "POST /containers/create"), + the stream is the raw data from the process PTY and client's stdin. + When the TTY is disabled, then the stream is multiplexed to separate + stdout and stderr. + + The format is a **Header** and a **Payload** (frame). + + **HEADER** + + The header will contain the information on which stream write the + stream (stdout or stderr). It also contain the size of the + associated frame encoded on the last 4 bytes (uint32). + + It is encoded on the first 8 bytes like this: + + header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4} + + `STREAM_TYPE` can be: + + - 0: stdin (will be writen on stdout) + - 1: stdout + - 2: stderr + + `SIZE1, SIZE2, SIZE3, SIZE4` are the 4 bytes of + the uint32 size encoded as big endian. + + **PAYLOAD** + + The payload is the raw stream. + + **IMPLEMENTATION** + + The simplest way to implement the Attach protocol is the following: + + 1. Read 8 bytes + 2. chose stdout or stderr depending on the first byte + 3. Extract the frame size from the last 4 byets + 4. Read the extracted size and output it on the correct output + 5. Goto 1) + +### Wait a container + +`POST /containers/(id)/wait` + +Block until container `id` stops, then returns the exit code + + **Example request**: + + POST /containers/16253994b7c4/wait HTTP/1.1 + + **Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + {"StatusCode":0} + + Status Codes: + + - **200** – no error + - **404** – no such container + - **500** – server error + +### Remove a container + +`DELETE /containers/(id)` + +Remove the container `id` from the filesystem + + **Example request**: + + DELETE /containers/16253994b7c4?v=1 HTTP/1.1 + + **Example response**: + + HTTP/1.1 204 OK + + Query Parameters: + +   + + - **v** – 1/True/true or 0/False/false, Remove the volumes + associated to the container. Default false + - **force** – 1/True/true or 0/False/false, Removes the container + even if it was running. Default false + + Status Codes: + + - **204** – no error + - **400** – bad parameter + - **404** – no such container + - **500** – server error + +### Copy files or folders from a container + +`POST /containers/(id)/copy` + +Copy files or folders of container `id` + + **Example request**: + + POST /containers/4fa6e0f0c678/copy HTTP/1.1 + Content-Type: application/json + + { + "Resource":"test.txt" + } + + **Example response**: + + HTTP/1.1 200 OK + Content-Type: application/octet-stream + + {{ STREAM }} + + Status Codes: + + - **200** – no error + - **404** – no such container + - **500** – server error + +## 2.2 Images + +### List Images + +`GET /images/json` + +**Example request**: + + GET /images/json?all=0 HTTP/1.1 + + **Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "RepoTags": [ + "ubuntu:12.04", + "ubuntu:precise", + "ubuntu:latest" + ], + "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", + "Created": 1365714795, + "Size": 131506275, + "VirtualSize": 131506275 + }, + { + "RepoTags": [ + "ubuntu:12.10", + "ubuntu:quantal" + ], + "ParentId": "27cf784147099545", + "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "Created": 1364102658, + "Size": 24653, + "VirtualSize": 180116135 + } + ] + +### Create an image + +`POST /images/create` + +Create an image, either by pull it from the registry or by importing it + + **Example request**: + + POST /images/create?fromImage=base HTTP/1.1 + + **Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"Pulling..."} + {"status":"Pulling", "progress":"1 B/ 100 B", "progressDetail":{"current":1, "total":100}} + {"error":"Invalid..."} + ... + + When using this endpoint to pull an image from the registry, the + `X-Registry-Auth` header can be used to include + a base64-encoded AuthConfig object. + + Query Parameters: + +   + + - **fromImage** – name of the image to pull + - **fromSrc** – source to import, - means stdin + - **repo** – repository + - **tag** – tag + - **registry** – the registry to pull from + + Request Headers: + +   + + - **X-Registry-Auth** – base64-encoded AuthConfig object + + Status Codes: + + - **200** – no error + - **500** – server error + +### Insert a file in an image + +`POST /images/(name)/insert` + +Insert a file from `url` in the image `name` at `path` + + **Example request**: + + POST /images/test/insert?path=/usr&url=myurl HTTP/1.1 + + **Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"Inserting..."} + {"status":"Inserting", "progress":"1/? (n/a)", "progressDetail":{"current":1}} + {"error":"Invalid..."} + ... + + Status Codes: + + - **200** – no error + - **500** – server error + +### Inspect an image + +`GET /images/(name)/json` + +Return low-level information on the image `name` + + **Example request**: + + GET /images/base/json HTTP/1.1 + + **Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "parent":"27cf784147099545", + "created":"2013-03-23T22:24:18.818426-07:00", + "container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0", + "container_config": + { + "Hostname":"", + "User":"", + "Memory":0, + "MemorySwap":0, + "AttachStdin":false, + "AttachStdout":false, + "AttachStderr":false, + "PortSpecs":null, + "Tty":true, + "OpenStdin":true, + "StdinOnce":false, + "Env":null, + "Cmd": ["/bin/bash"] + ,"Dns":null, + "Image":"base", + "Volumes":null, + "VolumesFrom":"", + "WorkingDir":"" + }, + "Size": 6824592 + } + + Status Codes: + + - **200** – no error + - **404** – no such image + - **500** – server error + +### Get the history of an image + +`GET /images/(name)/history` + +Return the history of the image `name` + + **Example request**: + + GET /images/base/history HTTP/1.1 + + **Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Id":"b750fe79269d", + "Created":1364102658, + "CreatedBy":"/bin/bash" + }, + { + "Id":"27cf78414709", + "Created":1364068391, + "CreatedBy":"" + } + ] + + Status Codes: + + - **200** – no error + - **404** – no such image + - **500** – server error + +### Push an image on the registry + +`POST /images/(name)/push` + +Push the image `name` on the registry + + **Example request**: + + POST /images/test/push HTTP/1.1 + + **Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"Pushing..."} + {"status":"Pushing", "progress":"1/? (n/a)", "progressDetail":{"current":1}}} + {"error":"Invalid..."} + ... + + Query Parameters: + +   + + - **registry** – the registry you wan to push, optional + + Request Headers: + +   + + - **X-Registry-Auth** – include a base64-encoded AuthConfig + object. + + Status Codes: + + - **200** – no error + - **404** – no such image + - **500** – server error + +### Tag an image into a repository + +`POST /images/(name)/tag` + +Tag the image `name` into a repository + + **Example request**: + + POST /images/test/tag?repo=myrepo&force=0 HTTP/1.1 + + **Example response**: + + HTTP/1.1 201 OK + + Query Parameters: + +   + + - **repo** – The repository to tag in + - **force** – 1/True/true or 0/False/false, default false + + Status Codes: + + - **201** – no error + - **400** – bad parameter + - **404** – no such image + - **409** – conflict + - **500** – server error + +### Remove an image + +`DELETE /images/(name)` + +Remove the image `name` from the filesystem + + **Example request**: + + DELETE /images/test HTTP/1.1 + + **Example response**: + + HTTP/1.1 200 OK + Content-type: application/json + + [ + {"Untagged":"3e2f21a89f"}, + {"Deleted":"3e2f21a89f"}, + {"Deleted":"53b4f83ac9"} + ] + + Query Parameters: + +   + + - **force** – 1/True/true or 0/False/false, default false + - **noprune** – 1/True/true or 0/False/false, default false + + Status Codes: + + - **200** – no error + - **404** – no such image + - **409** – conflict + - **500** – server error + +### Search images + +`GET /images/search` + +Search for an image on [Docker.io](https://index.docker.io). + +> **Note**: +> The response keys have changed from API v1.6 to reflect the JSON +> sent by the registry server to the docker daemon's request. + + **Example request**: + + GET /images/search?term=sshd HTTP/1.1 + + **Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "description": "", + "is_official": false, + "is_trusted": false, + "name": "wma55/u1210sshd", + "star_count": 0 + }, + { + "description": "", + "is_official": false, + "is_trusted": false, + "name": "jdswinbank/sshd", + "star_count": 0 + }, + { + "description": "", + "is_official": false, + "is_trusted": false, + "name": "vgauthier/sshd", + "star_count": 0 + } + ... + ] + + Query Parameters: + +   + + - **term** – term to search + + Status Codes: + + - **200** – no error + - **500** – server error + +## 2.3 Misc + +### Build an image from Dockerfile via stdin + +`POST /build` + +Build an image from Dockerfile via stdin + + **Example request**: + + POST /build HTTP/1.1 + + {{ STREAM }} + + **Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + {"stream":"Step 1..."} + {"stream":"..."} + {"error":"Error...", "errorDetail":{"code": 123, "message": "Error..."}} + + The stream must be a tar archive compressed with one of the + following algorithms: identity (no compression), gzip, bzip2, xz. + + The archive must include a file called `Dockerfile` + at its root. It may include any number of other files, + which will be accessible in the build context (See the [*ADD build + command*](/reference/builder/#dockerbuilder)). + + Query Parameters: + +   + + - **t** – repository name (and optionally a tag) to be applied to + the resulting image in case of success + - **q** – suppress verbose build output + - **nocache** – do not use the cache when building the image + - **rm** - remove intermediate containers after a successful build + - **forcerm - always remove intermediate containers (includes rm) + + Request Headers: + +   + + - **Content-type** – should be set to + `"application/tar"`. + - **X-Registry-Config** – base64-encoded ConfigFile object + + Status Codes: + + - **200** – no error + - **500** – server error + +### Check auth configuration + +`POST /auth` + +Get the default username and email + + **Example request**: + + POST /auth HTTP/1.1 + Content-Type: application/json + + { + "username":"hannibal", + "password:"xxxx", + "email":"hannibal@a-team.com", + "serveraddress":"https://index.docker.io/v1/" + } + + **Example response**: + + HTTP/1.1 200 OK + + Status Codes: + + - **200** – no error + - **204** – no error + - **500** – server error + +### Display system-wide information + +`GET /info` + +Display system-wide information + + **Example request**: + + GET /info HTTP/1.1 + + **Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Containers":11, + "Images":16, + "Debug":false, + "NFd": 11, + "NGoroutines":21, + "MemoryLimit":true, + "SwapLimit":false, + "IPv4Forwarding":true + } + + Status Codes: + + - **200** – no error + - **500** – server error + +### Show the docker version information + +`GET /version` + +Show the docker version information + + **Example request**: + + GET /version HTTP/1.1 + + **Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Version":"0.2.2", + "GitCommit":"5a2a5cc+CHANGES", + "GoVersion":"go1.0.3" + } + + Status Codes: + + - **200** – no error + - **500** – server error + +### Ping the docker server + +`GET /_ping` + +Ping the docker server + + **Example request**: + + GET /_ping HTTP/1.1 + + **Example response**: + + HTTP/1.1 200 OK + + OK + + Status Codes: + + - **200** - no error + - **500** - server error + +### Create a new image from a container's changes + +`POST /commit` + +Create a new image from a container's changes + + **Example request**: + + POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1 + Content-Type: application/json + + { + "Hostname":"", + "User":"", + "Memory":0, + "MemorySwap":0, + "AttachStdin":false, + "AttachStdout":true, + "AttachStderr":true, + "PortSpecs":null, + "Tty":false, + "OpenStdin":false, + "StdinOnce":false, + "Env":null, + "Cmd":[ + "date" + ], + "Volumes":{ + "/tmp": {} + }, + "WorkingDir":"", + "DisableNetwork": false, + "ExposedPorts":{ + "22/tcp": {} + } + } + + **Example response**: + + HTTP/1.1 201 OK + Content-Type: application/vnd.docker.raw-stream + + {"Id":"596069db4bf5"} + + Json Parameters: + + + + - **config** - the container's configuration + + Query Parameters: + +   + + - **container** – source container + - **repo** – repository + - **tag** – tag + - **m** – commit message + - **author** – author (eg. "John Hannibal Smith + <[hannibal@a-team.com](mailto:hannibal%40a-team.com)>") + + Status Codes: + + - **201** – no error + - **404** – no such container + - **500** – server error + +### Monitor Docker's events + +`GET /events` + +Get events from docker, either in real time via streaming, or +via polling (using since) + + **Example request**: + + GET /events?since=1374067924 + + **Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"create","id":"dfdf82bd3881","from":"base:latest","time":1374067924} + {"status":"start","id":"dfdf82bd3881","from":"base:latest","time":1374067924} + {"status":"stop","id":"dfdf82bd3881","from":"base:latest","time":1374067966} + {"status":"destroy","id":"dfdf82bd3881","from":"base:latest","time":1374067970} + + Query Parameters: + +   + + - **since** – timestamp used for polling + - **until** – timestamp used for polling + + Status Codes: + + - **200** – no error + - **500** – server error + +### Get a tarball containing all images and tags in a repository + +`GET /images/(name)/get` + +Get a tarball containing all images and metadata for the repository +specified by `name`. + + **Example request** + + GET /images/ubuntu/get + + **Example response**: + + HTTP/1.1 200 OK + Content-Type: application/x-tar + + Binary data stream + + Status Codes: + + - **200** – no error + - **500** – server error + +### Load a tarball with a set of images and tags into docker + +`POST /images/load` + +Load a set of images and tags into the docker repository. + + **Example request** + + POST /images/load + + Tarball in body + + **Example response**: + + HTTP/1.1 200 OK + + Status Codes: + + - **200** – no error + - **500** – server error + +# 3. Going further + +## 3.1 Inside `docker run` + +Here are the steps of `docker run`: + +- Create the container + +- If the status code is 404, it means the image doesn't exists: + - Try to pull it + - Then retry to create the container + +- Start the container + +- If you are not in detached mode: + - Attach to the container, using logs=1 (to have stdout and + stderr from the container's start) and stream=1 + +- If in detached mode or only stdin is attached: + - Display the container's id + +## 3.2 Hijacking + +In this version of the API, /attach, uses hijacking to transport stdin, +stdout and stderr on the same socket. This might change in the future. + +## 3.3 CORS Requests + +To enable cross origin requests to the remote api add the flag +"–api-enable-cors" when running docker in daemon mode. + + $ docker -d -H="192.168.1.9:4243" --api-enable-cors diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 40abfe45a..fb4bbd303 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -192,6 +192,7 @@ To kill the container, use `docker kill`. Build a new container image from the source code at PATH + --force-rm=false Always remove intermediate containers, even after unsuccessful builds --no-cache=false Do not use cache when building the image -q, --quiet=false Suppress the verbose output generated by the containers --rm=true Remove intermediate containers after a successful build diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index b87fa116e..820a3c7c8 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -394,7 +394,7 @@ func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, u } dockerfile := constructDockerfile(context.dockerfile, ip, port) - buildfile := server.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, useCache, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil) + buildfile := server.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, useCache, false, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil) id, err := buildfile.Build(context.Archive(dockerfile, t)) if err != nil { return nil, err @@ -828,7 +828,7 @@ func TestForbiddenContextPath(t *testing.T) { } dockerfile := constructDockerfile(context.dockerfile, ip, port) - buildfile := server.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, true, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil) + buildfile := server.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, true, false, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil) _, err = buildfile.Build(context.Archive(dockerfile, t)) if err == nil { @@ -874,7 +874,7 @@ func TestBuildADDFileNotFound(t *testing.T) { } dockerfile := constructDockerfile(context.dockerfile, ip, port) - buildfile := server.NewBuildFile(mkServerFromEngine(eng, t), ioutil.Discard, ioutil.Discard, false, true, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil) + buildfile := server.NewBuildFile(mkServerFromEngine(eng, t), ioutil.Discard, ioutil.Discard, false, true, false, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil) _, err = buildfile.Build(context.Archive(dockerfile, t)) if err == nil { diff --git a/server/buildfile.go b/server/buildfile.go index 995390494..7e4e157a4 100644 --- a/server/buildfile.go +++ b/server/buildfile.go @@ -50,6 +50,7 @@ type buildFile struct { verbose bool utilizeCache bool rm bool + forceRm bool authConfig *registry.AuthConfig configFile *registry.ConfigFile @@ -807,6 +808,9 @@ func (b *buildFile) Build(context io.Reader) (string, error) { continue } if err := b.BuildStep(fmt.Sprintf("%d", stepN), line); err != nil { + if b.forceRm { + b.clearTmp(b.tmpContainers) + } return "", err } else if b.rm { b.clearTmp(b.tmpContainers) @@ -859,7 +863,7 @@ func stripComments(raw []byte) string { return strings.Join(out, "\n") } -func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeCache, rm bool, outOld io.Writer, sf *utils.StreamFormatter, auth *registry.AuthConfig, authConfigFile *registry.ConfigFile) BuildFile { +func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeCache, rm bool, forceRm bool, outOld io.Writer, sf *utils.StreamFormatter, auth *registry.AuthConfig, authConfigFile *registry.ConfigFile) BuildFile { return &buildFile{ daemon: srv.daemon, srv: srv, @@ -871,6 +875,7 @@ func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeC verbose: verbose, utilizeCache: utilizeCache, rm: rm, + forceRm: forceRm, sf: sf, authConfig: auth, configFile: authConfigFile, diff --git a/server/server.go b/server/server.go index 49fb00ce5..80d881d62 100644 --- a/server/server.go +++ b/server/server.go @@ -438,6 +438,7 @@ func (srv *Server) Build(job *engine.Job) engine.Status { suppressOutput = job.GetenvBool("q") noCache = job.GetenvBool("nocache") rm = job.GetenvBool("rm") + forceRm = job.GetenvBool("forcerm") authConfig = ®istry.AuthConfig{} configFile = ®istry.ConfigFile{} tag string @@ -496,7 +497,7 @@ func (srv *Server) Build(job *engine.Job) engine.Status { Writer: job.Stdout, StreamFormatter: sf, }, - !suppressOutput, !noCache, rm, job.Stdout, sf, authConfig, configFile) + !suppressOutput, !noCache, rm, forceRm, job.Stdout, sf, authConfig, configFile) id, err := b.Build(context) if err != nil { return job.Error(err) From 77f54252606e6a094b5f722680d43f625bb0d0a8 Mon Sep 17 00:00:00 2001 From: unclejack Date: Mon, 19 May 2014 19:47:35 +0300 Subject: [PATCH 172/400] integcli: add getContainerCount utility function Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- integration-cli/docker_utils.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 17a331f2d..660d509e7 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -3,6 +3,7 @@ package main import ( "fmt" "os/exec" + "strconv" "strings" "testing" ) @@ -71,3 +72,28 @@ func findContainerIp(t *testing.T, id string) string { return strings.Trim(out, " \r\n'") } + +func getContainerCount() (int, error) { + const containers = "Containers:" + + cmd := exec.Command(dockerBinary, "info") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + return 0, err + } + + lines := strings.Split(out, "\n") + for _, line := range lines { + if strings.Contains(line, containers) { + output := stripTrailingCharacters(line) + output = strings.TrimLeft(output, containers) + output = strings.Trim(output, " ") + containerCount, err := strconv.Atoi(output) + if err != nil { + return 0, err + } + return containerCount, nil + } + } + return 0, fmt.Errorf("couldn't find the Container count in the output") +} From 69dcf767fd6b4c54c219c20f87b2fdbb173b7f1c Mon Sep 17 00:00:00 2001 From: unclejack Date: Mon, 19 May 2014 19:46:25 +0300 Subject: [PATCH 173/400] integcli: test container removal for failed builds Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- .../build_tests/TestBuildForceRm/Dockerfile | 3 +++ integration-cli/docker_cli_build_test.go | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 integration-cli/build_tests/TestBuildForceRm/Dockerfile diff --git a/integration-cli/build_tests/TestBuildForceRm/Dockerfile b/integration-cli/build_tests/TestBuildForceRm/Dockerfile new file mode 100644 index 000000000..8468edd4c --- /dev/null +++ b/integration-cli/build_tests/TestBuildForceRm/Dockerfile @@ -0,0 +1,3 @@ +FROM busybox +RUN true +RUN thiswillfail diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index c4d6f1292..ff6db58b4 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -207,6 +207,33 @@ func TestBuildWithInaccessibleFilesInContext(t *testing.T) { logDone("build - ADD from context with accessible links must work") } +func TestBuildForceRm(t *testing.T) { + containerCountBefore, err := getContainerCount() + if err != nil { + t.Fatalf("failed to get the container count: %s", err) + } + + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestBuildForceRm") + buildCmd := exec.Command(dockerBinary, "build", "--force-rm", ".") + buildCmd.Dir = buildDirectory + _, exitCode, err := runCommandWithOutput(buildCmd) + + if err == nil || exitCode == 0 { + t.Fatal("failed to build the image") + } + + containerCountAfter, err := getContainerCount() + if err != nil { + t.Fatalf("failed to get the container count: %s", err) + } + + if containerCountBefore != containerCountAfter { + t.Fatalf("--force-rm shouldn't have left containers behind") + } + + logDone("build - ensure --force-rm doesn't leave containers behind") +} + // TODO: TestCaching // TODO: TestADDCacheInvalidation From b60d6471721bc914dca179a4372303d41913cc4c Mon Sep 17 00:00:00 2001 From: unclejack Date: Mon, 19 May 2014 21:01:55 +0300 Subject: [PATCH 174/400] default to deleting images via the remote api This makes the remote API version 1.12 and newer default to automatically deleting intermediate containers when the build has succeedeed. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- api/client/commands.go | 2 ++ api/server/server.go | 3 +++ docs/sources/reference/api/docker_remote_api_v1.12.md | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/api/client/commands.go b/api/client/commands.go index 7f4288ec2..430613fde 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -198,6 +198,8 @@ func (cli *DockerCli) CmdBuild(args ...string) error { } if *rm { v.Set("rm", "1") + } else { + v.Set("rm", "0") } if *forceRm { diff --git a/api/server/server.go b/api/server/server.go index 0ac57471a..1c620b3c9 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -903,8 +903,11 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite } else { job.Stdout.Add(utils.NewWriteFlusher(w)) } + if r.FormValue("forcerm") == "1" && version.GreaterThanOrEqualTo("1.12") { job.Setenv("rm", "1") + } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") { + job.Setenv("rm", "1") } else { job.Setenv("rm", r.FormValue("rm")) } diff --git a/docs/sources/reference/api/docker_remote_api_v1.12.md b/docs/sources/reference/api/docker_remote_api_v1.12.md index d2b7f8f90..188d4fe0a 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.12.md +++ b/docs/sources/reference/api/docker_remote_api_v1.12.md @@ -1063,7 +1063,7 @@ Build an image from Dockerfile via stdin the resulting image in case of success - **q** – suppress verbose build output - **nocache** – do not use the cache when building the image - - **rm** - remove intermediate containers after a successful build + - **rm** - remove intermediate containers after a successful build (default behavior) - **forcerm - always remove intermediate containers (includes rm) Request Headers: From a691fcb2774deb07fe602d654bd7ba0908fdc09f Mon Sep 17 00:00:00 2001 From: unclejack Date: Mon, 19 May 2014 21:21:25 +0300 Subject: [PATCH 175/400] integcli: add tests for build --rm Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- .../build_tests/TestBuildRm/Dockerfile | 4 + integration-cli/build_tests/TestBuildRm/foo | 1 + integration-cli/docker_cli_build_test.go | 85 +++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 integration-cli/build_tests/TestBuildRm/Dockerfile create mode 100644 integration-cli/build_tests/TestBuildRm/foo diff --git a/integration-cli/build_tests/TestBuildRm/Dockerfile b/integration-cli/build_tests/TestBuildRm/Dockerfile new file mode 100644 index 000000000..190eacf11 --- /dev/null +++ b/integration-cli/build_tests/TestBuildRm/Dockerfile @@ -0,0 +1,4 @@ +FROM busybox +ADD foo / +ADD foo / + diff --git a/integration-cli/build_tests/TestBuildRm/foo b/integration-cli/build_tests/TestBuildRm/foo new file mode 100644 index 000000000..5716ca598 --- /dev/null +++ b/integration-cli/build_tests/TestBuildRm/foo @@ -0,0 +1 @@ +bar diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index ff6db58b4..0bcd6dece 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -234,6 +234,91 @@ func TestBuildForceRm(t *testing.T) { logDone("build - ensure --force-rm doesn't leave containers behind") } +func TestBuildRm(t *testing.T) { + { + containerCountBefore, err := getContainerCount() + if err != nil { + t.Fatalf("failed to get the container count: %s", err) + } + + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestBuildRm") + buildCmd := exec.Command(dockerBinary, "build", "--rm", "-t", "testbuildrm", ".") + buildCmd.Dir = buildDirectory + _, exitCode, err := runCommandWithOutput(buildCmd) + + if err != nil || exitCode != 0 { + t.Fatal("failed to build the image") + } + + containerCountAfter, err := getContainerCount() + if err != nil { + t.Fatalf("failed to get the container count: %s", err) + } + + if containerCountBefore != containerCountAfter { + t.Fatalf("-rm shouldn't have left containers behind") + } + deleteImages("testbuildrm") + } + + { + containerCountBefore, err := getContainerCount() + if err != nil { + t.Fatalf("failed to get the container count: %s", err) + } + + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestBuildRm") + buildCmd := exec.Command(dockerBinary, "build", "-t", "testbuildrm", ".") + buildCmd.Dir = buildDirectory + _, exitCode, err := runCommandWithOutput(buildCmd) + + if err != nil || exitCode != 0 { + t.Fatal("failed to build the image") + } + + containerCountAfter, err := getContainerCount() + if err != nil { + t.Fatalf("failed to get the container count: %s", err) + } + + if containerCountBefore != containerCountAfter { + t.Fatalf("--rm shouldn't have left containers behind") + } + deleteImages("testbuildrm") + } + + { + containerCountBefore, err := getContainerCount() + if err != nil { + t.Fatalf("failed to get the container count: %s", err) + } + + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestBuildRm") + buildCmd := exec.Command(dockerBinary, "build", "--rm=false", "-t", "testbuildrm", ".") + buildCmd.Dir = buildDirectory + _, exitCode, err := runCommandWithOutput(buildCmd) + + if err != nil || exitCode != 0 { + t.Fatal("failed to build the image") + } + + containerCountAfter, err := getContainerCount() + if err != nil { + t.Fatalf("failed to get the container count: %s", err) + } + + if containerCountBefore == containerCountAfter { + t.Fatalf("--rm=false should have left containers behind") + } + deleteAllContainers() + deleteImages("testbuildrm") + + } + + logDone("build - ensure --rm doesn't leave containers behind and that --rm=true is the default") + logDone("build - ensure --rm=false overrides the default") +} + // TODO: TestCaching // TODO: TestADDCacheInvalidation From a87bcefb8bf0cee47bf114a46fc33708ce843208 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 19 May 2014 20:46:59 +0000 Subject: [PATCH 176/400] Make sure dev/fuse is created in container Fixes #5849 If the host system does not have fuse enabled in the kernel config we will ignore the is not exist errors when trying to copy the device node from the host system into the container. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/mount/init.go | 5 ++++- pkg/libcontainer/mount/nodes/nodes.go | 24 +++++++++++++++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/pkg/libcontainer/mount/init.go b/pkg/libcontainer/mount/init.go index 6f3080cf0..c4148131a 100644 --- a/pkg/libcontainer/mount/init.go +++ b/pkg/libcontainer/mount/init.go @@ -48,9 +48,12 @@ func InitializeMountNamespace(rootfs, console string, container *libcontainer.Co if err := setupBindmounts(rootfs, container.Mounts); err != nil { return fmt.Errorf("bind mounts %s", err) } - if err := nodes.CopyN(rootfs, nodes.DefaultNodes); err != nil { + if err := nodes.CopyN(rootfs, nodes.DefaultNodes, true); err != nil { return fmt.Errorf("copy dev nodes %s", err) } + if err := nodes.CopyN(rootfs, nodes.AdditionalNodes, false); err != nil { + return fmt.Errorf("copy additional dev nodes %s", err) + } if err := SetupPtmx(rootfs, console, container.Context["mount_label"]); err != nil { return err } diff --git a/pkg/libcontainer/mount/nodes/nodes.go b/pkg/libcontainer/mount/nodes/nodes.go index 5022f85b0..138468272 100644 --- a/pkg/libcontainer/mount/nodes/nodes.go +++ b/pkg/libcontainer/mount/nodes/nodes.go @@ -4,10 +4,11 @@ package nodes import ( "fmt" - "github.com/dotcloud/docker/pkg/system" "os" "path/filepath" "syscall" + + "github.com/dotcloud/docker/pkg/system" ) // Default list of device nodes to copy @@ -20,30 +21,43 @@ var DefaultNodes = []string{ "tty", } +// AdditionalNodes includes nodes that are not required +var AdditionalNodes = []string{ + "fuse", +} + // CopyN copies the device node from the host into the rootfs -func CopyN(rootfs string, nodesToCopy []string) error { +func CopyN(rootfs string, nodesToCopy []string, shouldExist bool) error { oldMask := system.Umask(0000) defer system.Umask(oldMask) for _, node := range nodesToCopy { - if err := Copy(rootfs, node); err != nil { + if err := Copy(rootfs, node, shouldExist); err != nil { return err } } return nil } -func Copy(rootfs, node string) error { +// Copy copies the device node into the rootfs. If the node +// on the host system does not exist and the boolean flag is passed +// an error will be returned +func Copy(rootfs, node string, shouldExist bool) error { stat, err := os.Stat(filepath.Join("/dev", node)) if err != nil { + if os.IsNotExist(err) && !shouldExist { + return nil + } return err } + var ( dest = filepath.Join(rootfs, "dev", node) st = stat.Sys().(*syscall.Stat_t) ) + if err := system.Mknod(dest, st.Mode, int(st.Rdev)); err != nil && !os.IsExist(err) { - return fmt.Errorf("copy %s %s", node, err) + return fmt.Errorf("mknod %s %s", node, err) } return nil } From 599cb12bb8aa13aa2f1be940dd8de4c9d3a06959 Mon Sep 17 00:00:00 2001 From: unclejack Date: Mon, 19 May 2014 23:55:28 +0300 Subject: [PATCH 177/400] add unprivilegeduser via the Dockerfile This changes the test TestBuildWithInaccessibleFilesInContext to not add the user 'unprivilegeduser' and add it via the Dockerfile instead. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- Dockerfile | 3 +++ integration-cli/docker_cli_build_test.go | 7 ++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8a8818d75..3aff4f3e2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -92,6 +92,9 @@ RUN /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_ # Set user.email so crosbymichael's in-container merge commits go smoothly RUN git config --global user.email 'docker-dummy@example.com' +# Add an unprivileged user to be used for tests which need it +RUN adduser unprivilegeduser + VOLUME /var/lib/docker WORKDIR /go/src/github.com/dotcloud/docker ENV DOCKER_BUILDTAGS apparmor selinux diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index c4d6f1292..e8ca7eae7 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -125,16 +125,13 @@ func TestAddWholeDirToRoot(t *testing.T) { // when we can't access files in the context. func TestBuildWithInaccessibleFilesInContext(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestBuildWithInaccessibleFilesInContext") - addUserCmd := exec.Command("adduser", "unprivilegeduser") - out, _, err := runCommandWithOutput(addUserCmd) - errorOut(err, t, fmt.Sprintf("failed to add user: %v %v", out, err)) { // This is used to ensure we detect inaccessible files early during build in the cli client pathToInaccessibleFileBuildDirectory := filepath.Join(buildDirectory, "inaccessiblefile") pathToFileWithoutReadAccess := filepath.Join(pathToInaccessibleFileBuildDirectory, "fileWithoutReadAccess") - err = os.Chown(pathToFileWithoutReadAccess, 0, 0) + err := os.Chown(pathToFileWithoutReadAccess, 0, 0) errorOut(err, t, fmt.Sprintf("failed to chown file to root: %s", err)) err = os.Chmod(pathToFileWithoutReadAccess, 0700) errorOut(err, t, fmt.Sprintf("failed to chmod file to 700: %s", err)) @@ -162,7 +159,7 @@ func TestBuildWithInaccessibleFilesInContext(t *testing.T) { pathToDirectoryWithoutReadAccess := filepath.Join(pathToInaccessibleDirectoryBuildDirectory, "directoryWeCantStat") pathToFileInDirectoryWithoutReadAccess := filepath.Join(pathToDirectoryWithoutReadAccess, "bar") - err = os.Chown(pathToDirectoryWithoutReadAccess, 0, 0) + err := os.Chown(pathToDirectoryWithoutReadAccess, 0, 0) errorOut(err, t, fmt.Sprintf("failed to chown directory to root: %s", err)) err = os.Chmod(pathToDirectoryWithoutReadAccess, 0444) errorOut(err, t, fmt.Sprintf("failed to chmod directory to 755: %s", err)) From c12dd770908acc06ab402840ad7736e0c056b567 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 19 May 2014 21:22:33 +0000 Subject: [PATCH 178/400] starts by removing the name from the graph db Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- daemon/daemon.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index fc9f07052..5df5d434f 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -291,6 +291,10 @@ func (daemon *Daemon) Destroy(container *Container) error { daemon.idIndex.Delete(container.ID) daemon.containers.Remove(element) + if _, err := daemon.containerGraph.Purge(container.ID); err != nil { + utils.Debugf("Unable to remove container from link graph: %s", err) + } + if err := daemon.driver.Remove(container.ID); err != nil { return fmt.Errorf("Driver %s failed to remove root filesystem %s: %s", daemon.driver, container.ID, err) } @@ -300,10 +304,6 @@ func (daemon *Daemon) Destroy(container *Container) error { return fmt.Errorf("Driver %s failed to remove init filesystem %s: %s", daemon.driver, initID, err) } - if _, err := daemon.containerGraph.Purge(container.ID); err != nil { - utils.Debugf("Unable to remove container from link graph: %s", err) - } - if err := os.RemoveAll(container.root); err != nil { return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err) } From e454be7567e144dc836ff283d4f4e059db307a91 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 19 May 2014 22:04:51 +0000 Subject: [PATCH 179/400] Move init volumes code to func Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/daemon.go | 3 + daemon/volumes.go | 222 ++++++++++++++++++++++++--------------------- graph/graph.go | 15 +-- runconfig/merge.go | 4 +- server/server.go | 8 +- 5 files changed, 138 insertions(+), 114 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index fc9f07052..bea11d8b6 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -607,15 +607,18 @@ func (daemon *Daemon) Commit(container *Container, repository, tag, comment, aut containerID, containerImage string containerConfig *runconfig.Config ) + if container != nil { containerID = container.ID containerImage = container.Image containerConfig = container.Config } + img, err := daemon.graph.Create(rwTar, containerID, containerImage, comment, author, containerConfig, config) if err != nil { return nil, err } + // Register the image if needed if repository != "" { if err := daemon.repositories.Set(repository, tag, img.ID, true); err != nil { diff --git a/daemon/volumes.go b/daemon/volumes.go index eac743b2d..5a0215b8f 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -162,114 +162,10 @@ func createVolumes(container *Container) error { return err } - volumesDriver := container.daemon.volumes.Driver() // Create the requested volumes if they don't exist for volPath := range container.Config.Volumes { - volPath = filepath.Clean(volPath) - volIsDir := true - // Skip existing volumes - if _, exists := container.Volumes[volPath]; exists { - continue - } - var srcPath string - var isBindMount bool - srcRW := false - // If an external bind is defined for this volume, use that as a source - if bindMap, exists := binds[volPath]; exists { - isBindMount = true - srcPath = bindMap.SrcPath - if !filepath.IsAbs(srcPath) { - return fmt.Errorf("%s must be an absolute path", srcPath) - } - if strings.ToLower(bindMap.Mode) == "rw" { - srcRW = true - } - if stat, err := os.Stat(bindMap.SrcPath); err != nil { - return err - } else { - volIsDir = stat.IsDir() - } - // Otherwise create an directory in $ROOT/volumes/ and use that - } else { - - // Do not pass a container as the parameter for the volume creation. - // The graph driver using the container's information ( Image ) to - // create the parent. - c, err := container.daemon.volumes.Create(nil, "", "", "", "", nil, nil) - if err != nil { - return err - } - srcPath, err = volumesDriver.Get(c.ID, "") - if err != nil { - return fmt.Errorf("Driver %s failed to get volume rootfs %s: %s", volumesDriver, c.ID, err) - } - srcRW = true // RW by default - } - - if p, err := filepath.EvalSymlinks(srcPath); err != nil { + if err := initializeVolume(container, volPath, binds); err != nil { return err - } else { - srcPath = p - } - - // Create the mountpoint - rootVolPath, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, volPath), container.basefs) - if err != nil { - return err - } - - newVolPath, err := filepath.Rel(container.basefs, rootVolPath) - if err != nil { - return err - } - newVolPath = "/" + newVolPath - - if volPath != newVolPath { - delete(container.Volumes, volPath) - delete(container.VolumesRW, volPath) - } - - container.Volumes[newVolPath] = srcPath - container.VolumesRW[newVolPath] = srcRW - - if err := createIfNotExists(rootVolPath, volIsDir); err != nil { - return err - } - - // Do not copy or change permissions if we are mounting from the host - if srcRW && !isBindMount { - volList, err := ioutil.ReadDir(rootVolPath) - if err != nil { - return err - } - if len(volList) > 0 { - srcList, err := ioutil.ReadDir(srcPath) - if err != nil { - return err - } - if len(srcList) == 0 { - // If the source volume is empty copy files from the root into the volume - if err := archive.CopyWithTar(rootVolPath, srcPath); err != nil { - return err - } - } - } - - var stat syscall.Stat_t - if err := syscall.Stat(rootVolPath, &stat); err != nil { - return err - } - var srcStat syscall.Stat_t - if err := syscall.Stat(srcPath, &srcStat); err != nil { - return err - } - // Change the source volume's ownership if it differs from the root - // files that were just copied - if stat.Uid != srcStat.Uid || stat.Gid != srcStat.Gid { - if err := os.Chown(srcPath, int(stat.Uid), int(stat.Gid)); err != nil { - return err - } - } } } return nil @@ -296,3 +192,119 @@ func createIfNotExists(path string, isDir bool) error { } return nil } + +func initializeVolume(container *Container, volPath string, binds map[string]BindMap) error { + volumesDriver := container.daemon.volumes.Driver() + volPath = filepath.Clean(volPath) + // Skip existing volumes + if _, exists := container.Volumes[volPath]; exists { + return nil + } + + var ( + srcPath string + isBindMount bool + volIsDir = true + + srcRW = false + ) + + // If an external bind is defined for this volume, use that as a source + if bindMap, exists := binds[volPath]; exists { + isBindMount = true + srcPath = bindMap.SrcPath + if !filepath.IsAbs(srcPath) { + return fmt.Errorf("%s must be an absolute path", srcPath) + } + if strings.ToLower(bindMap.Mode) == "rw" { + srcRW = true + } + if stat, err := os.Stat(bindMap.SrcPath); err != nil { + return err + } else { + volIsDir = stat.IsDir() + } + // Otherwise create an directory in $ROOT/volumes/ and use that + } else { + + // Do not pass a container as the parameter for the volume creation. + // The graph driver using the container's information ( Image ) to + // create the parent. + c, err := container.daemon.volumes.Create(nil, "", "", "", "", nil, nil) + if err != nil { + return err + } + srcPath, err = volumesDriver.Get(c.ID, "") + if err != nil { + return fmt.Errorf("Driver %s failed to get volume rootfs %s: %s", volumesDriver, c.ID, err) + } + srcRW = true // RW by default + } + + if p, err := filepath.EvalSymlinks(srcPath); err != nil { + return err + } else { + srcPath = p + } + + // Create the mountpoint + rootVolPath, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, volPath), container.basefs) + if err != nil { + return err + } + + newVolPath, err := filepath.Rel(container.basefs, rootVolPath) + if err != nil { + return err + } + newVolPath = "/" + newVolPath + + if volPath != newVolPath { + delete(container.Volumes, volPath) + delete(container.VolumesRW, volPath) + } + + container.Volumes[newVolPath] = srcPath + container.VolumesRW[newVolPath] = srcRW + + if err := createIfNotExists(rootVolPath, volIsDir); err != nil { + return err + } + + // Do not copy or change permissions if we are mounting from the host + if srcRW && !isBindMount { + volList, err := ioutil.ReadDir(rootVolPath) + if err != nil { + return err + } + if len(volList) > 0 { + srcList, err := ioutil.ReadDir(srcPath) + if err != nil { + return err + } + if len(srcList) == 0 { + // If the source volume is empty copy files from the root into the volume + if err := archive.CopyWithTar(rootVolPath, srcPath); err != nil { + return err + } + } + } + + var stat syscall.Stat_t + if err := syscall.Stat(rootVolPath, &stat); err != nil { + return err + } + var srcStat syscall.Stat_t + if err := syscall.Stat(srcPath, &srcStat); err != nil { + return err + } + // Change the source volume's ownership if it differs from the root + // files that were just copied + if stat.Uid != srcStat.Uid || stat.Gid != srcStat.Gid { + if err := os.Chown(srcPath, int(stat.Uid), int(stat.Gid)); err != nil { + return err + } + } + } + return nil +} diff --git a/graph/graph.go b/graph/graph.go index 5de9cbe7a..a5a433d90 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -2,12 +2,6 @@ package graph import ( "fmt" - "github.com/dotcloud/docker/archive" - "github.com/dotcloud/docker/daemon/graphdriver" - "github.com/dotcloud/docker/dockerversion" - "github.com/dotcloud/docker/image" - "github.com/dotcloud/docker/runconfig" - "github.com/dotcloud/docker/utils" "io" "io/ioutil" "os" @@ -17,6 +11,13 @@ import ( "strings" "syscall" "time" + + "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/daemon/graphdriver" + "github.com/dotcloud/docker/dockerversion" + "github.com/dotcloud/docker/image" + "github.com/dotcloud/docker/runconfig" + "github.com/dotcloud/docker/utils" ) // A Graph is a store for versioned filesystem images and the relationship between them. @@ -141,11 +142,13 @@ func (graph *Graph) Create(layerData archive.ArchiveReader, containerID, contain Architecture: runtime.GOARCH, OS: runtime.GOOS, } + if containerID != "" { img.Parent = containerImage img.Container = containerID img.ContainerConfig = *containerConfig } + if err := graph.Register(nil, layerData, img); err != nil { return nil, err } diff --git a/runconfig/merge.go b/runconfig/merge.go index 252a2fe43..e30b4cec2 100644 --- a/runconfig/merge.go +++ b/runconfig/merge.go @@ -1,9 +1,10 @@ package runconfig import ( + "strings" + "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/utils" - "strings" ) func Merge(userConf, imageConf *Config) error { @@ -82,6 +83,7 @@ func Merge(userConf, imageConf *Config) error { } } } + if userConf.Cmd == nil || len(userConf.Cmd) == 0 { userConf.Cmd = imageConf.Cmd } diff --git a/server/server.go b/server/server.go index 49fb00ce5..490f48259 100644 --- a/server/server.go +++ b/server/server.go @@ -1050,8 +1050,12 @@ func (srv *Server) ContainerCommit(job *engine.Job) engine.Status { if container == nil { return job.Errorf("No such container: %s", name) } - var config = container.Config - var newConfig runconfig.Config + + var ( + config = container.Config + newConfig runconfig.Config + ) + if err := job.GetenvJson("config", &newConfig); err != nil { return job.Error(err) } From d535d9810012b18ca83f86aa416ff4a08bb49804 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 19 May 2014 22:18:37 +0000 Subject: [PATCH 180/400] Don't save bind mounts in image Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/volumes.go | 77 ++++++++++++++++++++++++++++------------------ runconfig/parse.go | 4 +-- 2 files changed, 49 insertions(+), 32 deletions(-) diff --git a/daemon/volumes.go b/daemon/volumes.go index 5a0215b8f..793bd1850 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -168,6 +168,12 @@ func createVolumes(container *Container) error { return err } } + + for volPath := range binds { + if err := initializeVolume(container, volPath, binds); err != nil { + return err + } + } return nil } @@ -226,7 +232,6 @@ func initializeVolume(container *Container, volPath string, binds map[string]Bin } // Otherwise create an directory in $ROOT/volumes/ and use that } else { - // Do not pass a container as the parameter for the volume creation. // The graph driver using the container's information ( Image ) to // create the parent. @@ -273,38 +278,50 @@ func initializeVolume(container *Container, volPath string, binds map[string]Bin // Do not copy or change permissions if we are mounting from the host if srcRW && !isBindMount { - volList, err := ioutil.ReadDir(rootVolPath) + if err := copyExistingContents(rootVolPath, srcPath); err != nil { + return err + } + } + return nil +} + +func copyExistingContents(rootVolPath, srcPath string) error { + volList, err := ioutil.ReadDir(rootVolPath) + if err != nil { + return err + } + + if len(volList) > 0 { + srcList, err := ioutil.ReadDir(srcPath) if err != nil { + return err + } + + if len(srcList) == 0 { + // If the source volume is empty copy files from the root into the volume + if err := archive.CopyWithTar(rootVolPath, srcPath); err != nil { + return err + } + } + } + + var ( + stat syscall.Stat_t + srcStat syscall.Stat_t + ) + + if err := syscall.Stat(rootVolPath, &stat); err != nil { + return err + } + if err := syscall.Stat(srcPath, &srcStat); err != nil { + return err + } + // Change the source volume's ownership if it differs from the root + // files that were just copied + if stat.Uid != srcStat.Uid || stat.Gid != srcStat.Gid { + if err := os.Chown(srcPath, int(stat.Uid), int(stat.Gid)); err != nil { return err } - if len(volList) > 0 { - srcList, err := ioutil.ReadDir(srcPath) - if err != nil { - return err - } - if len(srcList) == 0 { - // If the source volume is empty copy files from the root into the volume - if err := archive.CopyWithTar(rootVolPath, srcPath); err != nil { - return err - } - } - } - - var stat syscall.Stat_t - if err := syscall.Stat(rootVolPath, &stat); err != nil { - return err - } - var srcStat syscall.Stat_t - if err := syscall.Stat(srcPath, &srcStat); err != nil { - return err - } - // Change the source volume's ownership if it differs from the root - // files that were just copied - if stat.Uid != srcStat.Uid || stat.Gid != srcStat.Gid { - if err := os.Chown(srcPath, int(stat.Uid), int(stat.Gid)); err != nil { - return err - } - } } return nil } diff --git a/runconfig/parse.go b/runconfig/parse.go index 54fc51ec0..0fa287adb 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -135,8 +135,8 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf if arr[0] == "/" { return nil, nil, cmd, fmt.Errorf("Invalid bind mount: source can't be '/'") } - dstDir := arr[1] - flVolumes.Set(dstDir) + // after creating the bind mount we want to delete it from the flVolumes values because + // we do not want bind mounts being committed to image configs binds = append(binds, bind) flVolumes.Delete(bind) } else if bind == "/" { From d31c37fceb6c09a48b5dd9d6c33a95d734e02704 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 19 May 2014 22:57:29 +0000 Subject: [PATCH 181/400] Add test for commiting container with bind mount Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- integration-cli/docker_cli_commit_test.go | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/integration-cli/docker_cli_commit_test.go b/integration-cli/docker_cli_commit_test.go index 7635add91..c02c89cd3 100644 --- a/integration-cli/docker_cli_commit_test.go +++ b/integration-cli/docker_cli_commit_test.go @@ -83,3 +83,28 @@ func TestCommitTTY(t *testing.T) { t.Fatal(err) } } + +func TestCommitWithHostBindMount(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--name", "bind-commit", "-v", "/dev/null:/winning", "busybox", "true") + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + + cmd = exec.Command(dockerBinary, "commit", "bind-commit", "bindtest") + imageId, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err) + } + imageId = strings.Trim(imageId, "\r\n") + + cmd = exec.Command(dockerBinary, "run", "bindtest", "true") + + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + + deleteAllContainers() + deleteImages(imageId) + + logDone("commit - commit bind mounted file") +} From 4052a81830e57c9f1d7d567ba9e53813945c9810 Mon Sep 17 00:00:00 2001 From: Jonathan McCrohan Date: Tue, 20 May 2014 01:40:44 +0100 Subject: [PATCH 182/400] docs: cli.md; document docker version output Commit 3cec63d56f9de6332aa3f33502695283d4feb054 changed the output of docker version. Updating docs accordingly. Docker-DCO-1.1-Signed-off-by: Jonathan McCrohan (github: jmccrohan) --- docs/sources/reference/commandline/cli.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 40abfe45a..da9f49583 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1143,8 +1143,8 @@ them to [*Share Images via Repositories*]( Show the docker version information. -Show the version of the Docker client, daemon, and latest released -version. +Show the Docker version, API version, Git commit, and Go version of +both Docker client and daemon. ## wait From 000a37fe9d13a173ab46fcd5b8e693950a438f98 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Fri, 16 May 2014 15:01:25 -0700 Subject: [PATCH 183/400] fix(daemon): prepend host /etc/hosts instead of bind mounting systemd systems do not require a /etc/hosts file exists since an nss module is shipped that creates localhost implicitly. So, mounting /etc/hosts can fail on these sorts of systems, as was reported on CoreOS in issue #5812. Instead of trying to bind mount just copy the hosts entries onto the containers private /etc/hosts. Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- daemon/container.go | 12 ++++++++++-- daemon/volumes.go | 5 ++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index 9ca94b2b5..54e720deb 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -879,9 +879,17 @@ func (container *Container) initializeNetworking() error { container.Config.Hostname = parts[0] container.Config.Domainname = parts[1] } - container.HostsPath = "/etc/hosts" - return container.buildHostnameFile() + content, err := ioutil.ReadFile("/etc/hosts") + if os.IsNotExist(err) { + return container.buildHostnameAndHostsFiles("") + } + if err != nil { + return err + } + + container.HostsPath = container.getRootResourcePath("hosts") + return ioutil.WriteFile(container.HostsPath, content, 0644) } else if container.hostConfig.NetworkMode.IsContainer() { // we need to get the hosts files from the container to join nc, err := container.getNetworkedContainer() diff --git a/daemon/volumes.go b/daemon/volumes.go index eac743b2d..f96ce05c5 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -40,8 +40,11 @@ func setupMountsForContainer(container *Container) error { {container.ResolvConfPath, "/etc/resolv.conf", false, true}, } - if container.HostnamePath != "" && container.HostsPath != "" { + if container.HostnamePath != "" { mounts = append(mounts, execdriver.Mount{container.HostnamePath, "/etc/hostname", false, true}) + } + + if container.HostsPath != "" { mounts = append(mounts, execdriver.Mount{container.HostsPath, "/etc/hosts", false, true}) } From 602950435056baa939f428223b6d3ff26ca5403d Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 20 May 2014 09:28:44 +0200 Subject: [PATCH 184/400] cgroups: Allow mknod for any device in systemd cgroup backend Without this any container startup fails: 2014/05/20 09:20:36 setup mount namespace copy additional dev nodes mknod fuse operation not permitted Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- .../cgroups/systemd/apply_systemd.go | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/pkg/libcontainer/cgroups/systemd/apply_systemd.go b/pkg/libcontainer/cgroups/systemd/apply_systemd.go index 52940f6fa..4d6b68b2c 100644 --- a/pkg/libcontainer/cgroups/systemd/apply_systemd.go +++ b/pkg/libcontainer/cgroups/systemd/apply_systemd.go @@ -174,13 +174,22 @@ func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) { path := filepath.Join(mountpoint, cgroup) - // /dev/pts/* - if err := ioutil.WriteFile(filepath.Join(path, "devices.allow"), []byte("c 136:* rwm"), 0700); err != nil { - return nil, err + allow := []string{ + // allow mknod for any device + "c *:* m", + "b *:* m", + + // /dev/pts/ - pts namespaces are "coming soon" + "c 136:* rwm", + + // tuntap + "c 10:200 rwm", } - // tuntap - if err := ioutil.WriteFile(filepath.Join(path, "devices.allow"), []byte("c 10:200 rwm"), 0700); err != nil { - return nil, err + + for _, val := range allow { + if err := ioutil.WriteFile(filepath.Join(path, "devices.allow"), []byte(val), 0700); err != nil { + return nil, err + } } } From fcf2e9a9107c6c9aebaf63ce044f636333e7eed8 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 20 May 2014 09:58:30 +0200 Subject: [PATCH 185/400] native driver: Add required capabilities We need SETFCAP to be able to mark files as having caps, which is heavily used by fedora. See https://github.com/dotcloud/docker/issues/5928 We also need SETPCAP, for instance systemd needs this to set caps on its childen. Both of these are safe in the sense that they can never ever result in a process with a capability not in the bounding set of the container. We also add NET_BIND_SERVICE caps, to be able to bind to ports lower than 1024. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- daemon/execdriver/native/template/default_template.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/daemon/execdriver/native/template/default_template.go b/daemon/execdriver/native/template/default_template.go index 5c4160342..ba52499c2 100644 --- a/daemon/execdriver/native/template/default_template.go +++ b/daemon/execdriver/native/template/default_template.go @@ -17,6 +17,9 @@ func New() *libcontainer.Container { "NET_RAW", "SETGID", "SETUID", + "SETFCAP", + "SETPCAP", + "NET_BIND_SERVICE", }, Namespaces: map[string]bool{ "NEWNS": true, From bc081a03d85a88cc2160ade9256d96d7b84c4caa Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Mon, 19 May 2014 17:18:53 -0400 Subject: [PATCH 186/400] docker '' causes a golang crash. This patch fixes the problem. Docker-DCO-1.1-Signed-off-by: Daniel Walsh (github: rhatdan) --- api/client/cli.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api/client/cli.go b/api/client/cli.go index 49fb3c978..44cff0dc4 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -23,6 +23,9 @@ var funcMap = template.FuncMap{ } func (cli *DockerCli) getMethod(name string) (func(...string) error, bool) { + if len(name) == 0 { + return nil, false + } methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:]) method := reflect.ValueOf(cli).MethodByName(methodName) if !method.IsValid() { From 0f278940947d74f2b7889ada18808779312f9608 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 20 May 2014 19:10:23 +0000 Subject: [PATCH 187/400] Fix network mode for lxc 1.0 Fixes #5692 This change requires lxc 1.0+ to work and breaks lxc versions less than 1.0 for host networking. We think that this is a find tradeoff by bumping docker to only support lxc 1.0 Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/execdriver/lxc/lxc_template.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index f2364cb77..a16f8990e 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -15,7 +15,9 @@ lxc.network.type = veth lxc.network.link = {{.Network.Interface.Bridge}} lxc.network.name = eth0 lxc.network.mtu = {{.Network.Mtu}} -{{else if not .Network.HostNetworking}} +{{else if .Network.HostNetworking}} +lxc.network.type = none +{{else}} # network is disabled (-n=false) lxc.network.type = empty lxc.network.flags = up From 24c73ce2d3d572313fe56bad08819e0ca8b74d26 Mon Sep 17 00:00:00 2001 From: Gleb Fotengauer-Malinovskiy Date: Tue, 20 May 2014 21:02:55 +0400 Subject: [PATCH 188/400] Fix race condition during socket creation Docker-DCO-1.1-Signed-off-by: Gleb Fotengauer-Malinovskiy (github: glebfm) --- api/server/server.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index f08f39540..58a994c64 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1205,11 +1205,20 @@ func ListenAndServe(proto, addr string, job *engine.Job) error { } } + var oldmask int + if proto == "unix" { + oldmask = syscall.Umask(0777) + } + if job.GetenvBool("BufferRequests") { l, err = listenbuffer.NewListenBuffer(proto, addr, activationLock) } else { l, err = net.Listen(proto, addr) } + + if proto == "unix" { + syscall.Umask(oldmask) + } if err != nil { return err } @@ -1247,9 +1256,6 @@ func ListenAndServe(proto, addr string, job *engine.Job) error { log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") } case "unix": - if err := os.Chmod(addr, 0660); err != nil { - return err - } socketGroup := job.Getenv("SocketGroup") if socketGroup != "" { if err := changeGroup(addr, socketGroup); err != nil { @@ -1261,6 +1267,9 @@ func ListenAndServe(proto, addr string, job *engine.Job) error { } } } + if err := os.Chmod(addr, 0660); err != nil { + return err + } default: return fmt.Errorf("Invalid protocol format.") } From 603e00a3a7644caf118d3efd0932500b4dfc4de3 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 20 May 2014 19:36:15 +0000 Subject: [PATCH 189/400] move inspect from server to daemon Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api/server/server.go | 12 ++-- daemon/daemon.go | 5 ++ daemon/inspect.go | 27 ++++++++ graph/service.go | 53 ++++++++++++-- integration/api_test.go | 3 +- integration/buildfile_test.go | 25 +++++-- integration/commands_test.go | 7 +- integration/server_test.go | 4 +- server/server.go | 126 ++++++++-------------------------- 9 files changed, 137 insertions(+), 125 deletions(-) create mode 100644 daemon/inspect.go diff --git a/api/server/server.go b/api/server/server.go index f08f39540..19986058c 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -338,7 +338,7 @@ func getContainersLogs(eng *engine.Engine, version version.Version, w http.Respo } var ( - job = eng.Job("inspect", vars["name"], "container") + job = eng.Job("container_inspect", vars["name"]) c, err = job.Stdout.AddEnv() ) if err != nil { @@ -755,7 +755,7 @@ func postContainersAttach(eng *engine.Engine, version version.Version, w http.Re } var ( - job = eng.Job("inspect", vars["name"], "container") + job = eng.Job("container_inspect", vars["name"]) c, err = job.Stdout.AddEnv() ) if err != nil { @@ -819,7 +819,7 @@ func wsContainersAttach(eng *engine.Engine, version version.Version, w http.Resp return fmt.Errorf("Missing parameter") } - if err := eng.Job("inspect", vars["name"], "container").Run(); err != nil { + if err := eng.Job("container_inspect", vars["name"]).Run(); err != nil { return err } @@ -847,9 +847,8 @@ func getContainersByName(eng *engine.Engine, version version.Version, w http.Res if vars == nil { return fmt.Errorf("Missing parameter") } - var job = eng.Job("inspect", vars["name"], "container") + var job = eng.Job("container_inspect", vars["name"]) streamJSON(job, w, false) - job.SetenvBool("conflict", true) //conflict=true to detect conflict between containers and images in the job return job.Run() } @@ -857,9 +856,8 @@ func getImagesByName(eng *engine.Engine, version version.Version, w http.Respons if vars == nil { return fmt.Errorf("Missing parameter") } - var job = eng.Job("inspect", vars["name"], "image") + var job = eng.Job("image_inspect", vars["name"]) streamJSON(job, w, false) - job.SetenvBool("conflict", true) //conflict=true to detect conflict between containers and images in the job return job.Run() } diff --git a/daemon/daemon.go b/daemon/daemon.go index 5df5d434f..24e7aa57e 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -64,6 +64,11 @@ type Daemon struct { execDriver execdriver.Driver } +// Install installs daemon capabilities to eng. +func (daemon *Daemon) Install(eng *engine.Engine) error { + return eng.Register("container_inspect", daemon.ContainerInspect) +} + // Mountpoints should be private to the container func remountPrivate(mountPoint string) error { mounted, err := mount.Mounted(mountPoint) diff --git a/daemon/inspect.go b/daemon/inspect.go new file mode 100644 index 000000000..0f771a3ca --- /dev/null +++ b/daemon/inspect.go @@ -0,0 +1,27 @@ +package daemon + +import ( + "encoding/json" + + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/runconfig" +) + +func (daemon *Daemon) ContainerInspect(job *engine.Job) engine.Status { + if len(job.Args) != 1 { + return job.Errorf("usage: %s NAME", job.Name) + } + name := job.Args[0] + if container := daemon.Get(name); container != nil { + b, err := json.Marshal(&struct { + *Container + HostConfig *runconfig.HostConfig + }{container, container.HostConfig()}) + if err != nil { + return job.Error(err) + } + job.Stdout.Write(b) + return engine.StatusOK + } + return job.Errorf("No such container: %s", name) +} diff --git a/graph/service.go b/graph/service.go index 211babd0b..881a19904 100644 --- a/graph/service.go +++ b/graph/service.go @@ -1,7 +1,10 @@ package graph import ( + "encoding/json" "fmt" + "io" + "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/utils" @@ -11,6 +14,8 @@ func (s *TagStore) Install(eng *engine.Engine) error { eng.Register("image_set", s.CmdSet) eng.Register("image_tag", s.CmdTag) eng.Register("image_get", s.CmdGet) + eng.Register("image_inspect", s.CmdLookup) + eng.Register("image_tarlayer", s.CmdTarLayer) return nil } @@ -107,11 +112,6 @@ func (s *TagStore) CmdGet(job *engine.Job) engine.Status { // but we didn't, so now we're doing it here. // // Fields that we're probably better off not including: - // - ID (the caller already knows it, and we stay more flexible on - // naming down the road) - // - Parent. That field is really an implementation detail of - // layer storage ("layer is a diff against this other layer). - // It doesn't belong at the same level as author/description/etc. // - Config/ContainerConfig. Those structs have the same sprawl problem, // so we shouldn't include them wholesale either. // - Comment: initially created to fulfill the "every image is a git commit" @@ -122,7 +122,50 @@ func (s *TagStore) CmdGet(job *engine.Job) engine.Status { res.Set("os", img.OS) res.Set("architecture", img.Architecture) res.Set("docker_version", img.DockerVersion) + res.Set("ID", img.ID) + res.Set("Parent", img.Parent) } res.WriteTo(job.Stdout) return engine.StatusOK } + +// CmdLookup return an image encoded in JSON +func (s *TagStore) CmdLookup(job *engine.Job) engine.Status { + if len(job.Args) != 1 { + return job.Errorf("usage: %s NAME", job.Name) + } + name := job.Args[0] + if image, err := s.LookupImage(name); err == nil && image != nil { + b, err := json.Marshal(image) + if err != nil { + return job.Error(err) + } + job.Stdout.Write(b) + return engine.StatusOK + } + return job.Errorf("No such image: %s", name) +} + +// CmdTarLayer return the tarLayer of the image +func (s *TagStore) CmdTarLayer(job *engine.Job) engine.Status { + if len(job.Args) != 1 { + return job.Errorf("usage: %s NAME", job.Name) + } + name := job.Args[0] + if image, err := s.LookupImage(name); err == nil && image != nil { + fs, err := image.TarLayer() + if err != nil { + return job.Error(err) + } + defer fs.Close() + + if written, err := io.Copy(job.Stdout, fs); err != nil { + return job.Error(err) + } else { + utils.Debugf("rendered layer for %s of [%d] size", image.ID, written) + } + + return engine.StatusOK + } + return job.Errorf("No such image: %s", name) +} diff --git a/integration/api_test.go b/integration/api_test.go index 04611dfe3..969e0fbaf 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -536,7 +536,6 @@ func TestGetContainersByName(t *testing.T) { func TestPostCommit(t *testing.T) { eng := NewTestEngine(t) defer mkDaemonFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) // Create a container and remove a file containerID := createTestContainer(eng, @@ -567,7 +566,7 @@ func TestPostCommit(t *testing.T) { if err := env.Decode(r.Body); err != nil { t.Fatal(err) } - if _, err := srv.ImageInspect(env.Get("Id")); err != nil { + if err := eng.Job("image_inspect", env.Get("Id")).Run(); err != nil { t.Fatalf("The image has not been committed") } } diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index b87fa116e..268e9eaf4 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -1,19 +1,22 @@ package docker import ( + "bytes" + "encoding/json" "fmt" - "github.com/dotcloud/docker/archive" - "github.com/dotcloud/docker/engine" - "github.com/dotcloud/docker/image" - "github.com/dotcloud/docker/nat" - "github.com/dotcloud/docker/server" - "github.com/dotcloud/docker/utils" "io/ioutil" "net" "net/http" "net/http/httptest" "strings" "testing" + + "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/image" + "github.com/dotcloud/docker/nat" + "github.com/dotcloud/docker/server" + "github.com/dotcloud/docker/utils" ) // A testContextTemplate describes a build context and how to test it @@ -400,7 +403,15 @@ func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, u return nil, err } - return srv.ImageInspect(id) + job := eng.Job("image_inspect", id) + buffer := bytes.NewBuffer(nil) + image := &image.Image{} + job.Stdout.Add(buffer) + if err := job.Run(); err != nil { + return nil, err + } + err = json.NewDecoder(buffer).Decode(image) + return image, err } func TestVolume(t *testing.T) { diff --git a/integration/commands_test.go b/integration/commands_test.go index b91ba603a..2ee5842e1 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -1052,11 +1052,12 @@ func TestContainerOrphaning(t *testing.T) { if err := cli.CmdBuild("-t", image, tmpDir); err != nil { t.Fatal(err) } - img, err := srv.ImageInspect(image) - if err != nil { + job := globalEngine.Job("image_get", image) + info, _ := job.Stdout.AddEnv() + if err := job.Run(); err != nil { t.Fatal(err) } - return img.ID + return info.Get("ID") } // build an image diff --git a/integration/server_test.go b/integration/server_test.go index 4da3e6e36..3752c9b7e 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -81,13 +81,13 @@ func TestMergeConfigOnCommit(t *testing.T) { container2, _, _ := mkContainer(runtime, []string{engine.Tail(outputBuffer, 1)}, t) defer runtime.Destroy(container2) - job = eng.Job("inspect", container1.Name, "container") + job = eng.Job("container_inspect", container1.Name) baseContainer, _ := job.Stdout.AddEnv() if err := job.Run(); err != nil { t.Error(err) } - job = eng.Job("inspect", container2.Name, "container") + job = eng.Job("container_inspect", container2.Name) commitContainer, _ := job.Stdout.AddEnv() if err := job.Run(); err != nil { t.Error(err) diff --git a/server/server.go b/server/server.go index 49fb00ce5..ee35d29a6 100644 --- a/server/server.go +++ b/server/server.go @@ -132,7 +132,6 @@ func InitServer(job *engine.Job) engine.Status { "pull": srv.ImagePull, "import": srv.ImageImport, "image_delete": srv.ImageDelete, - "inspect": srv.JobInspect, "events": srv.Events, "push": srv.ImagePush, "containers": srv.Containers, @@ -146,6 +145,11 @@ func InitServer(job *engine.Job) engine.Status { if err := srv.daemon.Repositories().Install(job.Eng); err != nil { return job.Error(err) } + // Install daemon-related commands from the daemon subsystem. + // See `daemon/` + if err := srv.daemon.Install(job.Eng); err != nil { + return job.Error(err) + } return engine.StatusOK } @@ -327,12 +331,7 @@ func (srv *Server) ImageExport(job *engine.Job) engine.Status { } if rootRepo != nil { for _, id := range rootRepo { - image, err := srv.ImageInspect(id) - if err != nil { - return job.Error(err) - } - - if err := srv.exportImage(image, tempdir); err != nil { + if err := srv.exportImage(job.Eng, id, tempdir); err != nil { return job.Error(err) } } @@ -346,11 +345,7 @@ func (srv *Server) ImageExport(job *engine.Job) engine.Status { return job.Error(err) } } else { - image, err := srv.ImageInspect(name) - if err != nil { - return job.Error(err) - } - if err := srv.exportImage(image, tempdir); err != nil { + if err := srv.exportImage(job.Eng, name, tempdir); err != nil { return job.Error(err) } } @@ -364,13 +359,14 @@ func (srv *Server) ImageExport(job *engine.Job) engine.Status { if _, err := io.Copy(job.Stdout, fs); err != nil { return job.Error(err) } + utils.Debugf("End Serializing %s", name) return engine.StatusOK } -func (srv *Server) exportImage(img *image.Image, tempdir string) error { - for i := img; i != nil; { +func (srv *Server) exportImage(eng *engine.Engine, name, tempdir string) error { + for n := name; n != ""; { // temporary directory - tmpImageDir := path.Join(tempdir, i.ID) + tmpImageDir := path.Join(tempdir, n) if err := os.Mkdir(tmpImageDir, os.FileMode(0755)); err != nil { if os.IsExist(err) { return nil @@ -386,44 +382,34 @@ func (srv *Server) exportImage(img *image.Image, tempdir string) error { } // serialize json - b, err := json.Marshal(i) + json, err := os.Create(path.Join(tmpImageDir, "json")) if err != nil { return err } - if err := ioutil.WriteFile(path.Join(tmpImageDir, "json"), b, os.FileMode(0644)); err != nil { + job := eng.Job("image_inspect", n) + job.Stdout.Add(json) + if err := job.Run(); err != nil { return err } // serialize filesystem - fs, err := i.TarLayer() - if err != nil { - return err - } - defer fs.Close() - fsTar, err := os.Create(path.Join(tmpImageDir, "layer.tar")) if err != nil { return err } - if written, err := io.Copy(fsTar, fs); err != nil { - return err - } else { - utils.Debugf("rendered layer for %s of [%d] size", i.ID, written) - } - - if err = fsTar.Close(); err != nil { + job = eng.Job("image_tarlayer", n) + job.Stdout.Add(fsTar) + if err := job.Run(); err != nil { return err } // find parent - if i.Parent != "" { - i, err = srv.ImageInspect(i.Parent) - if err != nil { - return err - } - } else { - i = nil + job = eng.Job("image_get", n) + info, _ := job.Stdout.AddEnv() + if err := job.Run(); err != nil { + return err } + n = info.Get("Parent") } return nil } @@ -548,7 +534,7 @@ func (srv *Server) ImageLoad(job *engine.Job) engine.Status { for _, d := range dirs { if d.IsDir() { - if err := srv.recursiveLoad(d.Name(), tmpImageDir); err != nil { + if err := srv.recursiveLoad(job.Eng, d.Name(), tmpImageDir); err != nil { return job.Error(err) } } @@ -575,8 +561,8 @@ func (srv *Server) ImageLoad(job *engine.Job) engine.Status { return engine.StatusOK } -func (srv *Server) recursiveLoad(address, tmpImageDir string) error { - if _, err := srv.ImageInspect(address); err != nil { +func (srv *Server) recursiveLoad(eng *engine.Engine, address, tmpImageDir string) error { + if err := eng.Job("image_get", address).Run(); err != nil { utils.Debugf("Loading %s", address) imageJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", address, "json")) @@ -597,7 +583,7 @@ func (srv *Server) recursiveLoad(address, tmpImageDir string) error { } if img.Parent != "" { if !srv.daemon.Graph().Exists(img.Parent) { - if err := srv.recursiveLoad(img.Parent, tmpImageDir); err != nil { + if err := srv.recursiveLoad(eng, img.Parent, tmpImageDir); err != nil { return err } } @@ -2337,64 +2323,6 @@ func (srv *Server) ContainerAttach(job *engine.Job) engine.Status { return engine.StatusOK } -func (srv *Server) ContainerInspect(name string) (*daemon.Container, error) { - if container := srv.daemon.Get(name); container != nil { - return container, nil - } - return nil, fmt.Errorf("No such container: %s", name) -} - -func (srv *Server) ImageInspect(name string) (*image.Image, error) { - if image, err := srv.daemon.Repositories().LookupImage(name); err == nil && image != nil { - return image, nil - } - return nil, fmt.Errorf("No such image: %s", name) -} - -func (srv *Server) JobInspect(job *engine.Job) engine.Status { - // TODO: deprecate KIND/conflict - if n := len(job.Args); n != 2 { - return job.Errorf("Usage: %s CONTAINER|IMAGE KIND", job.Name) - } - var ( - name = job.Args[0] - kind = job.Args[1] - object interface{} - conflict = job.GetenvBool("conflict") //should the job detect conflict between containers and images - image, errImage = srv.ImageInspect(name) - container, errContainer = srv.ContainerInspect(name) - ) - - if conflict && image != nil && container != nil { - return job.Errorf("Conflict between containers and images") - } - - switch kind { - case "image": - if errImage != nil { - return job.Error(errImage) - } - object = image - case "container": - if errContainer != nil { - return job.Error(errContainer) - } - object = &struct { - *daemon.Container - HostConfig *runconfig.HostConfig - }{container, container.HostConfig()} - default: - return job.Errorf("Unknown kind: %s", kind) - } - - b, err := json.Marshal(object) - if err != nil { - return job.Error(err) - } - job.Stdout.Write(b) - return engine.StatusOK -} - func (srv *Server) ContainerCopy(job *engine.Job) engine.Status { if len(job.Args) != 2 { return job.Errorf("Usage: %s CONTAINER RESOURCE\n", job.Name) From 94f01184beb731b8c28dd43cf259621ba14f76ff Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 20 May 2014 19:05:57 +0000 Subject: [PATCH 190/400] Update lxc requirement to 1.0 Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- Dockerfile | 5 +---- hack/PACKAGERS.md | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3aff4f3e2..183ec89db 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,6 +41,7 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq \ libapparmor-dev \ libcap-dev \ libsqlite3-dev \ + lxc=1.0* \ mercurial \ pandoc \ reprepro \ @@ -49,10 +50,6 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq \ s3cmd=1.1.0* \ --no-install-recommends -# Get and compile LXC 0.8 (since it is the most stable) -RUN git clone --no-checkout https://github.com/lxc/lxc.git /usr/local/lxc && cd /usr/local/lxc && git checkout -q lxc-0.8.0 -RUN cd /usr/local/lxc && ./autogen.sh && ./configure --disable-docs && make && make install - # Get lvm2 source for compiling statically RUN git clone --no-checkout https://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2 && cd /usr/local/lvm2 && git checkout -q v2_02_103 # see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags diff --git a/hack/PACKAGERS.md b/hack/PACKAGERS.md index 9edb4a3e1..82d959c9e 100644 --- a/hack/PACKAGERS.md +++ b/hack/PACKAGERS.md @@ -297,7 +297,7 @@ the client will even run on alternative platforms such as Mac OS X / Darwin. Some of Docker's features are activated by using optional command-line flags or by having support for them in the kernel or userspace. A few examples include: -* LXC execution driver (requires version 0.8 or later of the LXC utility scripts) +* LXC execution driver (requires version 1.0 or later of the LXC utility scripts) * AUFS graph driver (requires AUFS patches/support enabled in the kernel, and at least the "auplink" utility from aufs-tools) * experimental BTRFS graph driver (requires BTRFS support enabled in the kernel) From e2d79bec3ab76357625ca57d83299012f584c024 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 20 May 2014 21:11:33 +0000 Subject: [PATCH 191/400] Update runconfig unit test for changes Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runconfig/config_test.go | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/runconfig/config_test.go b/runconfig/config_test.go index f71528ff8..b426253b9 100644 --- a/runconfig/config_test.go +++ b/runconfig/config_test.go @@ -1,9 +1,10 @@ package runconfig import ( - "github.com/dotcloud/docker/nat" "strings" "testing" + + "github.com/dotcloud/docker/nat" ) func parse(t *testing.T, args string) (*Config, *HostConfig, error) { @@ -93,32 +94,20 @@ func TestParseRunVolumes(t *testing.T) { t.Fatalf("Error parsing volume flags, `-v /var` is missing from volumes. Received %v", config.Volumes) } - if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" { + if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" { t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp` should mount-bind /hostTmp into /containeTmp. Received %v", hostConfig.Binds) - } else if _, exists := config.Volumes["/containerTmp"]; !exists { - t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes) } - if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /hostVar:/containerVar"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" || hostConfig.Binds[1] != "/hostVar:/containerVar" { + if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /hostVar:/containerVar"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" || hostConfig.Binds[1] != "/hostVar:/containerVar" { t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /hostVar:/containerVar` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds) - } else if _, exists := config.Volumes["/containerTmp"]; !exists { - t.Fatalf("Error parsing volume flags, `-v /containerTmp` is missing from volumes. Received %v", config.Volumes) - } else if _, exists := config.Volumes["/containerVar"]; !exists { - t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes) } - if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp:ro" || hostConfig.Binds[1] != "/hostVar:/containerVar:rw" { + if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp:ro" || hostConfig.Binds[1] != "/hostVar:/containerVar:rw" { t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds) - } else if _, exists := config.Volumes["/containerTmp"]; !exists { - t.Fatalf("Error parsing volume flags, `-v /containerTmp` is missing from volumes. Received %v", config.Volumes) - } else if _, exists := config.Volumes["/containerVar"]; !exists { - t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes) } if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /containerVar"); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != "/hostTmp:/containerTmp" { t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /containerVar` should mount-bind only /hostTmp into /containeTmp. Received %v", hostConfig.Binds) - } else if _, exists := config.Volumes["/containerTmp"]; !exists { - t.Fatalf("Error parsing volume flags, `-v /containerTmp` is missing from volumes. Received %v", config.Volumes) } else if _, exists := config.Volumes["/containerVar"]; !exists { t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes) } From d98af1236c9aeef6f9eef34970e13cbe0ea06ff8 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Tue, 20 May 2014 15:18:52 -0600 Subject: [PATCH 192/400] Update gorilla/mux, gorilla/context, and kr/pty deps Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/vendor.sh | 6 +- .../github.com/gorilla/context/.travis.yml | 7 + .../src/github.com/gorilla/context/README.md | 1 + .../src/github.com/gorilla/context/context.go | 42 +++- .../gorilla/context/context_test.go | 95 ++++++++ vendor/src/github.com/gorilla/context/doc.go | 2 +- vendor/src/github.com/gorilla/mux/.travis.yml | 7 + vendor/src/github.com/gorilla/mux/README.md | 1 + vendor/src/github.com/gorilla/mux/doc.go | 2 +- vendor/src/github.com/gorilla/mux/mux.go | 32 ++- vendor/src/github.com/gorilla/mux/mux_test.go | 225 ++++++++++++++++-- vendor/src/github.com/gorilla/mux/old_test.go | 8 +- vendor/src/github.com/gorilla/mux/regexp.go | 17 +- vendor/src/github.com/gorilla/mux/route.go | 16 +- vendor/src/github.com/kr/pty/ioctl.go | 11 + vendor/src/github.com/kr/pty/ioctl_bsd.go | 39 +++ vendor/src/github.com/kr/pty/ioctl_linux.go | 42 ++++ vendor/src/github.com/kr/pty/mktypes.bash | 19 ++ vendor/src/github.com/kr/pty/pty_darwin.go | 25 +- vendor/src/github.com/kr/pty/pty_freebsd.go | 64 +++-- vendor/src/github.com/kr/pty/pty_linux.go | 30 +-- .../src/github.com/kr/pty/pty_unsupported.go | 16 -- vendor/src/github.com/kr/pty/types.go | 10 + vendor/src/github.com/kr/pty/types_freebsd.go | 15 ++ vendor/src/github.com/kr/pty/ztypes_386.go | 9 + vendor/src/github.com/kr/pty/ztypes_amd64.go | 9 + vendor/src/github.com/kr/pty/ztypes_arm.go | 9 + .../github.com/kr/pty/ztypes_freebsd_386.go | 13 + .../github.com/kr/pty/ztypes_freebsd_amd64.go | 14 ++ .../github.com/kr/pty/ztypes_freebsd_arm.go | 13 + 30 files changed, 661 insertions(+), 138 deletions(-) create mode 100644 vendor/src/github.com/gorilla/context/.travis.yml create mode 100644 vendor/src/github.com/gorilla/mux/.travis.yml create mode 100644 vendor/src/github.com/kr/pty/ioctl.go create mode 100644 vendor/src/github.com/kr/pty/ioctl_bsd.go create mode 100644 vendor/src/github.com/kr/pty/ioctl_linux.go create mode 100755 vendor/src/github.com/kr/pty/mktypes.bash create mode 100644 vendor/src/github.com/kr/pty/types.go create mode 100644 vendor/src/github.com/kr/pty/types_freebsd.go create mode 100644 vendor/src/github.com/kr/pty/ztypes_386.go create mode 100644 vendor/src/github.com/kr/pty/ztypes_amd64.go create mode 100644 vendor/src/github.com/kr/pty/ztypes_arm.go create mode 100644 vendor/src/github.com/kr/pty/ztypes_freebsd_386.go create mode 100644 vendor/src/github.com/kr/pty/ztypes_freebsd_amd64.go create mode 100644 vendor/src/github.com/kr/pty/ztypes_freebsd_arm.go diff --git a/hack/vendor.sh b/hack/vendor.sh index 1119c37b2..7f01a236c 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -39,11 +39,11 @@ clone() { echo done } -clone git github.com/kr/pty 98c7b80083 +clone git github.com/kr/pty 67e2db24c8 -clone git github.com/gorilla/context 708054d61e5 +clone git github.com/gorilla/context b06ed15e1c -clone git github.com/gorilla/mux 9b36453141c +clone git github.com/gorilla/mux 136d54f81f clone git github.com/syndtr/gocapability 3c85049eae diff --git a/vendor/src/github.com/gorilla/context/.travis.yml b/vendor/src/github.com/gorilla/context/.travis.yml new file mode 100644 index 000000000..d87d46576 --- /dev/null +++ b/vendor/src/github.com/gorilla/context/.travis.yml @@ -0,0 +1,7 @@ +language: go + +go: + - 1.0 + - 1.1 + - 1.2 + - tip diff --git a/vendor/src/github.com/gorilla/context/README.md b/vendor/src/github.com/gorilla/context/README.md index 8ee62b426..c60a31b05 100644 --- a/vendor/src/github.com/gorilla/context/README.md +++ b/vendor/src/github.com/gorilla/context/README.md @@ -1,5 +1,6 @@ context ======= +[![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context) gorilla/context is a general purpose registry for global request variables. diff --git a/vendor/src/github.com/gorilla/context/context.go b/vendor/src/github.com/gorilla/context/context.go index 35d65561f..a7f7d85bb 100644 --- a/vendor/src/github.com/gorilla/context/context.go +++ b/vendor/src/github.com/gorilla/context/context.go @@ -11,7 +11,7 @@ import ( ) var ( - mutex sync.Mutex + mutex sync.RWMutex data = make(map[*http.Request]map[interface{}]interface{}) datat = make(map[*http.Request]int64) ) @@ -19,42 +19,64 @@ var ( // Set stores a value for a given key in a given request. func Set(r *http.Request, key, val interface{}) { mutex.Lock() - defer mutex.Unlock() if data[r] == nil { data[r] = make(map[interface{}]interface{}) datat[r] = time.Now().Unix() } data[r][key] = val + mutex.Unlock() } // Get returns a value stored for a given key in a given request. func Get(r *http.Request, key interface{}) interface{} { - mutex.Lock() - defer mutex.Unlock() + mutex.RLock() if data[r] != nil { + mutex.RUnlock() return data[r][key] } + mutex.RUnlock() return nil } // GetOk returns stored value and presence state like multi-value return of map access. func GetOk(r *http.Request, key interface{}) (interface{}, bool) { - mutex.Lock() - defer mutex.Unlock() + mutex.RLock() if _, ok := data[r]; ok { value, ok := data[r][key] + mutex.RUnlock() return value, ok } + mutex.RUnlock() return nil, false } +// GetAll returns all stored values for the request as a map. Nil is returned for invalid requests. +func GetAll(r *http.Request) map[interface{}]interface{} { + mutex.RLock() + if context, ok := data[r]; ok { + mutex.RUnlock() + return context + } + mutex.RUnlock() + return nil +} + +// GetAllOk returns all stored values for the request as a map. It returns not +// ok if the request was never registered. +func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) { + mutex.RLock() + context, ok := data[r] + mutex.RUnlock() + return context, ok +} + // Delete removes a value stored for a given key in a given request. func Delete(r *http.Request, key interface{}) { mutex.Lock() - defer mutex.Unlock() if data[r] != nil { delete(data[r], key) } + mutex.Unlock() } // Clear removes all values stored for a given request. @@ -63,8 +85,8 @@ func Delete(r *http.Request, key interface{}) { // variables at the end of a request lifetime. See ClearHandler(). func Clear(r *http.Request) { mutex.Lock() - defer mutex.Unlock() clear(r) + mutex.Unlock() } // clear is Clear without the lock. @@ -84,7 +106,6 @@ func clear(r *http.Request) { // periodically until the problem is fixed. func Purge(maxAge int) int { mutex.Lock() - defer mutex.Unlock() count := 0 if maxAge <= 0 { count = len(data) @@ -92,13 +113,14 @@ func Purge(maxAge int) int { datat = make(map[*http.Request]int64) } else { min := time.Now().Unix() - int64(maxAge) - for r, _ := range data { + for r := range data { if datat[r] < min { clear(r) count++ } } } + mutex.Unlock() return count } diff --git a/vendor/src/github.com/gorilla/context/context_test.go b/vendor/src/github.com/gorilla/context/context_test.go index ff9e2ad5f..6ada8ec31 100644 --- a/vendor/src/github.com/gorilla/context/context_test.go +++ b/vendor/src/github.com/gorilla/context/context_test.go @@ -24,6 +24,7 @@ func TestContext(t *testing.T) { } r, _ := http.NewRequest("GET", "http://localhost:8080/", nil) + emptyR, _ := http.NewRequest("GET", "http://localhost:8080/", nil) // Get() assertEqual(Get(r, key1), nil) @@ -51,6 +52,26 @@ func TestContext(t *testing.T) { assertEqual(value, nil) assertEqual(ok, true) + // GetAll() + values := GetAll(r) + assertEqual(len(values), 3) + + // GetAll() for empty request + values = GetAll(emptyR) + if values != nil { + t.Error("GetAll didn't return nil value for invalid request") + } + + // GetAllOk() + values, ok = GetAllOk(r) + assertEqual(len(values), 3) + assertEqual(ok, true) + + // GetAllOk() for empty request + values, ok = GetAllOk(emptyR) + assertEqual(value, nil) + assertEqual(ok, false) + // Delete() Delete(r, key1) assertEqual(Get(r, key1), nil) @@ -64,3 +85,77 @@ func TestContext(t *testing.T) { Clear(r) assertEqual(len(data), 0) } + +func parallelReader(r *http.Request, key string, iterations int, wait, done chan struct{}) { + <-wait + for i := 0; i < iterations; i++ { + Get(r, key) + } + done <- struct{}{} + +} + +func parallelWriter(r *http.Request, key, value string, iterations int, wait, done chan struct{}) { + <-wait + for i := 0; i < iterations; i++ { + Get(r, key) + } + done <- struct{}{} + +} + +func benchmarkMutex(b *testing.B, numReaders, numWriters, iterations int) { + + b.StopTimer() + r, _ := http.NewRequest("GET", "http://localhost:8080/", nil) + done := make(chan struct{}) + b.StartTimer() + + for i := 0; i < b.N; i++ { + wait := make(chan struct{}) + + for i := 0; i < numReaders; i++ { + go parallelReader(r, "test", iterations, wait, done) + } + + for i := 0; i < numWriters; i++ { + go parallelWriter(r, "test", "123", iterations, wait, done) + } + + close(wait) + + for i := 0; i < numReaders+numWriters; i++ { + <-done + } + + } + +} + +func BenchmarkMutexSameReadWrite1(b *testing.B) { + benchmarkMutex(b, 1, 1, 32) +} +func BenchmarkMutexSameReadWrite2(b *testing.B) { + benchmarkMutex(b, 2, 2, 32) +} +func BenchmarkMutexSameReadWrite4(b *testing.B) { + benchmarkMutex(b, 4, 4, 32) +} +func BenchmarkMutex1(b *testing.B) { + benchmarkMutex(b, 2, 8, 32) +} +func BenchmarkMutex2(b *testing.B) { + benchmarkMutex(b, 16, 4, 64) +} +func BenchmarkMutex3(b *testing.B) { + benchmarkMutex(b, 1, 2, 128) +} +func BenchmarkMutex4(b *testing.B) { + benchmarkMutex(b, 128, 32, 256) +} +func BenchmarkMutex5(b *testing.B) { + benchmarkMutex(b, 1024, 2048, 64) +} +func BenchmarkMutex6(b *testing.B) { + benchmarkMutex(b, 2048, 1024, 512) +} diff --git a/vendor/src/github.com/gorilla/context/doc.go b/vendor/src/github.com/gorilla/context/doc.go index 297606455..73c740031 100644 --- a/vendor/src/github.com/gorilla/context/doc.go +++ b/vendor/src/github.com/gorilla/context/doc.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. /* -Package gorilla/context stores values shared during a request lifetime. +Package context stores values shared during a request lifetime. For example, a router can set variables extracted from the URL and later application handlers can access those values, or it can be used to store diff --git a/vendor/src/github.com/gorilla/mux/.travis.yml b/vendor/src/github.com/gorilla/mux/.travis.yml new file mode 100644 index 000000000..d87d46576 --- /dev/null +++ b/vendor/src/github.com/gorilla/mux/.travis.yml @@ -0,0 +1,7 @@ +language: go + +go: + - 1.0 + - 1.1 + - 1.2 + - tip diff --git a/vendor/src/github.com/gorilla/mux/README.md b/vendor/src/github.com/gorilla/mux/README.md index f6db41ad8..e60301b03 100644 --- a/vendor/src/github.com/gorilla/mux/README.md +++ b/vendor/src/github.com/gorilla/mux/README.md @@ -1,5 +1,6 @@ mux === +[![Build Status](https://travis-ci.org/gorilla/mux.png?branch=master)](https://travis-ci.org/gorilla/mux) gorilla/mux is a powerful URL router and dispatcher. diff --git a/vendor/src/github.com/gorilla/mux/doc.go b/vendor/src/github.com/gorilla/mux/doc.go index 8ee5540a4..b2deed34c 100644 --- a/vendor/src/github.com/gorilla/mux/doc.go +++ b/vendor/src/github.com/gorilla/mux/doc.go @@ -134,7 +134,7 @@ the inner routes use it as base for their paths: // "/products/{key}/" s.HandleFunc("/{key}/", ProductHandler) // "/products/{key}/details" - s.HandleFunc("/{key}/details"), ProductDetailsHandler) + s.HandleFunc("/{key}/details", ProductDetailsHandler) Now let's see how to build registered URLs. diff --git a/vendor/src/github.com/gorilla/mux/mux.go b/vendor/src/github.com/gorilla/mux/mux.go index 385717394..8b23c39d3 100644 --- a/vendor/src/github.com/gorilla/mux/mux.go +++ b/vendor/src/github.com/gorilla/mux/mux.go @@ -14,7 +14,7 @@ import ( // NewRouter returns a new router instance. func NewRouter() *Router { - return &Router{namedRoutes: make(map[string]*Route)} + return &Router{namedRoutes: make(map[string]*Route), KeepContext: false} } // Router registers routes to be matched and dispatches a handler. @@ -46,6 +46,8 @@ type Router struct { namedRoutes map[string]*Route // See Router.StrictSlash(). This defines the flag for new routes. strictSlash bool + // If true, do not clear the request context after handling the request + KeepContext bool } // Match matches registered routes against the request. @@ -65,6 +67,14 @@ func (r *Router) Match(req *http.Request, match *RouteMatch) bool { func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { // Clean path to canonical form and redirect. if p := cleanPath(req.URL.Path); p != req.URL.Path { + + // Added 3 lines (Philip Schlump) - It was droping the query string and #whatever from query. + // This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue: + // http://code.google.com/p/go/issues/detail?id=5252 + url := *req.URL + url.Path = p + p = url.String() + w.Header().Set("Location", p) w.WriteHeader(http.StatusMovedPermanently) return @@ -82,7 +92,9 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { } handler = r.NotFoundHandler } - defer context.Clear(req) + if !r.KeepContext { + defer context.Clear(req) + } handler.ServeHTTP(w, req) } @@ -97,14 +109,20 @@ func (r *Router) GetRoute(name string) *Route { return r.getNamedRoutes()[name] } -// StrictSlash defines the slash behavior for new routes. +// StrictSlash defines the trailing slash behavior for new routes. The initial +// value is false. // // When true, if the route path is "/path/", accessing "/path" will redirect -// to the former and vice versa. +// to the former and vice versa. In other words, your application will always +// see the path as specified in the route. // -// Special case: when a route sets a path prefix, strict slash is -// automatically set to false for that route because the redirect behavior -// can't be determined for prefixes. +// When false, if the route path is "/path", accessing "/path/" will not match +// this route and vice versa. +// +// Special case: when a route sets a path prefix using the PathPrefix() method, +// strict slash is ignored for that route because the redirect behavior can't +// be determined from a prefix alone. However, any subrouters created from that +// route inherit the original StrictSlash setting. func (r *Router) StrictSlash(value bool) *Router { r.strictSlash = value return r diff --git a/vendor/src/github.com/gorilla/mux/mux_test.go b/vendor/src/github.com/gorilla/mux/mux_test.go index 55159bd10..0e2e48067 100644 --- a/vendor/src/github.com/gorilla/mux/mux_test.go +++ b/vendor/src/github.com/gorilla/mux/mux_test.go @@ -8,16 +8,19 @@ import ( "fmt" "net/http" "testing" + + "github.com/gorilla/context" ) type routeTest struct { - title string // title of the test - route *Route // the route being tested - request *http.Request // a request to test the route - vars map[string]string // the expected vars of the match - host string // the expected host of the match - path string // the expected path of the match - shouldMatch bool // whether the request is expected to match the route at all + title string // title of the test + route *Route // the route being tested + request *http.Request // a request to test the route + vars map[string]string // the expected vars of the match + host string // the expected host of the match + path string // the expected path of the match + shouldMatch bool // whether the request is expected to match the route at all + shouldRedirect bool // whether the request should result in a redirect } func TestHost(t *testing.T) { @@ -149,6 +152,33 @@ func TestPath(t *testing.T) { path: "/111/222/333", shouldMatch: true, }, + { + title: "Path route, match with trailing slash in request and path", + route: new(Route).Path("/111/"), + request: newRequest("GET", "http://localhost/111/"), + vars: map[string]string{}, + host: "", + path: "/111/", + shouldMatch: true, + }, + { + title: "Path route, do not match with trailing slash in path", + route: new(Route).Path("/111/"), + request: newRequest("GET", "http://localhost/111"), + vars: map[string]string{}, + host: "", + path: "/111", + shouldMatch: false, + }, + { + title: "Path route, do not match with trailing slash in request", + route: new(Route).Path("/111"), + request: newRequest("GET", "http://localhost/111/"), + vars: map[string]string{}, + host: "", + path: "/111/", + shouldMatch: false, + }, { title: "Path route, wrong path in request in request URL", route: new(Route).Path("/111/222/333"), @@ -212,6 +242,15 @@ func TestPathPrefix(t *testing.T) { path: "/111", shouldMatch: true, }, + { + title: "PathPrefix route, match substring", + route: new(Route).PathPrefix("/1"), + request: newRequest("GET", "http://localhost/111/222/333"), + vars: map[string]string{}, + host: "", + path: "/1", + shouldMatch: true, + }, { title: "PathPrefix route, URL prefix in request does not match", route: new(Route).PathPrefix("/111"), @@ -414,6 +453,15 @@ func TestQueries(t *testing.T) { path: "", shouldMatch: true, }, + { + title: "Queries route, match with a query string", + route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"), + request: newRequest("GET", "http://www.example.com/api?foo=bar&baz=ding"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, { title: "Queries route, bad query", route: new(Route).Queries("foo", "bar", "baz", "ding"), @@ -568,26 +616,74 @@ func TestNamedRoutes(t *testing.T) { } func TestStrictSlash(t *testing.T) { - var r *Router - var req *http.Request - var route *Route - var match *RouteMatch - var matched bool - - // StrictSlash should be ignored for path prefix. - // So we register a route ending in slash but it doesn't attempt to add - // the slash for a path not ending in slash. - r = NewRouter() + r := NewRouter() r.StrictSlash(true) - route = r.NewRoute().PathPrefix("/static/") - req, _ = http.NewRequest("GET", "http://localhost/static/logo.png", nil) - match = new(RouteMatch) - matched = r.Match(req, match) - if !matched { - t.Errorf("Should match request %q -- %v", req.URL.Path, getRouteTemplate(route)) + + tests := []routeTest{ + { + title: "Redirect path without slash", + route: r.NewRoute().Path("/111/"), + request: newRequest("GET", "http://localhost/111"), + vars: map[string]string{}, + host: "", + path: "/111/", + shouldMatch: true, + shouldRedirect: true, + }, + { + title: "Do not redirect path with slash", + route: r.NewRoute().Path("/111/"), + request: newRequest("GET", "http://localhost/111/"), + vars: map[string]string{}, + host: "", + path: "/111/", + shouldMatch: true, + shouldRedirect: false, + }, + { + title: "Redirect path with slash", + route: r.NewRoute().Path("/111"), + request: newRequest("GET", "http://localhost/111/"), + vars: map[string]string{}, + host: "", + path: "/111", + shouldMatch: true, + shouldRedirect: true, + }, + { + title: "Do not redirect path without slash", + route: r.NewRoute().Path("/111"), + request: newRequest("GET", "http://localhost/111"), + vars: map[string]string{}, + host: "", + path: "/111", + shouldMatch: true, + shouldRedirect: false, + }, + { + title: "Propagate StrictSlash to subrouters", + route: r.NewRoute().PathPrefix("/static/").Subrouter().Path("/images/"), + request: newRequest("GET", "http://localhost/static/images"), + vars: map[string]string{}, + host: "", + path: "/static/images/", + shouldMatch: true, + shouldRedirect: true, + }, + { + title: "Ignore StrictSlash for path prefix", + route: r.NewRoute().PathPrefix("/static/"), + request: newRequest("GET", "http://localhost/static/logo.png"), + vars: map[string]string{}, + host: "", + path: "/static/", + shouldMatch: true, + shouldRedirect: false, + }, } - if match.Handler != nil { - t.Errorf("Should not redirect") + + for _, test := range tests { + testRoute(t, test) } } @@ -616,6 +712,7 @@ func testRoute(t *testing.T, test routeTest) { host := test.host path := test.path url := test.host + test.path + shouldRedirect := test.shouldRedirect var match RouteMatch ok := route.Match(request, &match) @@ -653,6 +750,84 @@ func testRoute(t *testing.T, test routeTest) { return } } + if shouldRedirect && match.Handler == nil { + t.Errorf("(%v) Did not redirect", test.title) + return + } + if !shouldRedirect && match.Handler != nil { + t.Errorf("(%v) Unexpected redirect", test.title) + return + } + } +} + +// Tests that the context is cleared or not cleared properly depending on +// the configuration of the router +func TestKeepContext(t *testing.T) { + func1 := func(w http.ResponseWriter, r *http.Request) {} + + r := NewRouter() + r.HandleFunc("/", func1).Name("func1") + + req, _ := http.NewRequest("GET", "http://localhost/", nil) + context.Set(req, "t", 1) + + res := new(http.ResponseWriter) + r.ServeHTTP(*res, req) + + if _, ok := context.GetOk(req, "t"); ok { + t.Error("Context should have been cleared at end of request") + } + + r.KeepContext = true + + req, _ = http.NewRequest("GET", "http://localhost/", nil) + context.Set(req, "t", 1) + + r.ServeHTTP(*res, req) + if _, ok := context.GetOk(req, "t"); !ok { + t.Error("Context should NOT have been cleared at end of request") + } + +} + +type TestA301ResponseWriter struct { + hh http.Header + status int +} + +func (ho TestA301ResponseWriter) Header() http.Header { + return http.Header(ho.hh) +} + +func (ho TestA301ResponseWriter) Write(b []byte) (int, error) { + return 0, nil +} + +func (ho TestA301ResponseWriter) WriteHeader(code int) { + ho.status = code +} + +func Test301Redirect(t *testing.T) { + m := make(http.Header) + + func1 := func(w http.ResponseWriter, r *http.Request) {} + func2 := func(w http.ResponseWriter, r *http.Request) {} + + r := NewRouter() + r.HandleFunc("/api/", func2).Name("func2") + r.HandleFunc("/", func1).Name("func1") + + req, _ := http.NewRequest("GET", "http://localhost//api/?abc=def", nil) + + res := TestA301ResponseWriter{ + hh: m, + status: 0, + } + r.ServeHTTP(&res, req) + + if "http://localhost/api/?abc=def" != res.hh["Location"][0] { + t.Errorf("Should have complete URL with query string") } } diff --git a/vendor/src/github.com/gorilla/mux/old_test.go b/vendor/src/github.com/gorilla/mux/old_test.go index 7e266bb69..42530590e 100644 --- a/vendor/src/github.com/gorilla/mux/old_test.go +++ b/vendor/src/github.com/gorilla/mux/old_test.go @@ -96,8 +96,8 @@ func TestRouteMatchers(t *testing.T) { method = "GET" headers = map[string]string{"X-Requested-With": "XMLHttpRequest"} resultVars = map[bool]map[string]string{ - true: map[string]string{"var1": "www", "var2": "product", "var3": "42"}, - false: map[string]string{}, + true: {"var1": "www", "var2": "product", "var3": "42"}, + false: {}, } } @@ -110,8 +110,8 @@ func TestRouteMatchers(t *testing.T) { method = "POST" headers = map[string]string{"Content-Type": "application/json"} resultVars = map[bool]map[string]string{ - true: map[string]string{"var4": "google", "var5": "product", "var6": "42"}, - false: map[string]string{}, + true: {"var4": "google", "var5": "product", "var6": "42"}, + false: {}, } } diff --git a/vendor/src/github.com/gorilla/mux/regexp.go b/vendor/src/github.com/gorilla/mux/regexp.go index 4c3482bfb..925f268ab 100644 --- a/vendor/src/github.com/gorilla/mux/regexp.go +++ b/vendor/src/github.com/gorilla/mux/regexp.go @@ -98,12 +98,13 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*rout } // Done! return &routeRegexp{ - template: template, - matchHost: matchHost, - regexp: reg, - reverse: reverse.String(), - varsN: varsN, - varsR: varsR, + template: template, + matchHost: matchHost, + strictSlash: strictSlash, + regexp: reg, + reverse: reverse.String(), + varsN: varsN, + varsR: varsR, }, nil } @@ -114,6 +115,8 @@ type routeRegexp struct { template string // True for host match, false for path match. matchHost bool + // The strictSlash value defined on the route, but disabled if PathPrefix was used. + strictSlash bool // Expanded regexp. regexp *regexp.Regexp // Reverse template. @@ -216,7 +219,7 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) m.Vars[v] = pathVars[k+1] } // Check if we should redirect. - if r.strictSlash { + if v.path.strictSlash { p1 := strings.HasSuffix(req.URL.Path, "/") p2 := strings.HasSuffix(v.path.template, "/") if p1 != p2 { diff --git a/vendor/src/github.com/gorilla/mux/route.go b/vendor/src/github.com/gorilla/mux/route.go index cb538ea4e..5cb2526d6 100644 --- a/vendor/src/github.com/gorilla/mux/route.go +++ b/vendor/src/github.com/gorilla/mux/route.go @@ -259,7 +259,8 @@ func (r *Route) Methods(methods ...string) *Route { // Path ----------------------------------------------------------------------- // Path adds a matcher for the URL path. -// It accepts a template with zero or more URL variables enclosed by {}. +// It accepts a template with zero or more URL variables enclosed by {}. The +// template must start with a "/". // Variables can define an optional regexp pattern to me matched: // // - {name} matches anything until the next slash. @@ -283,9 +284,16 @@ func (r *Route) Path(tpl string) *Route { // PathPrefix ----------------------------------------------------------------- -// PathPrefix adds a matcher for the URL path prefix. +// PathPrefix adds a matcher for the URL path prefix. This matches if the given +// template is a prefix of the full URL path. See Route.Path() for details on +// the tpl argument. +// +// Note that it does not treat slashes specially ("/foobar/" will be matched by +// the prefix "/foo") so you may want to use a trailing slash here. +// +// Also note that the setting of Router.StrictSlash() has no effect on routes +// with a PathPrefix matcher. func (r *Route) PathPrefix(tpl string) *Route { - r.strictSlash = false r.err = r.addRegexpMatcher(tpl, false, true) return r } @@ -328,7 +336,7 @@ func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool { } // Schemes adds a matcher for URL schemes. -// It accepts a sequence schemes to be matched, e.g.: "http", "https". +// It accepts a sequence of schemes to be matched, e.g.: "http", "https". func (r *Route) Schemes(schemes ...string) *Route { for k, v := range schemes { schemes[k] = strings.ToLower(v) diff --git a/vendor/src/github.com/kr/pty/ioctl.go b/vendor/src/github.com/kr/pty/ioctl.go new file mode 100644 index 000000000..5b856e871 --- /dev/null +++ b/vendor/src/github.com/kr/pty/ioctl.go @@ -0,0 +1,11 @@ +package pty + +import "syscall" + +func ioctl(fd, cmd, ptr uintptr) error { + _, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr) + if e != 0 { + return e + } + return nil +} diff --git a/vendor/src/github.com/kr/pty/ioctl_bsd.go b/vendor/src/github.com/kr/pty/ioctl_bsd.go new file mode 100644 index 000000000..73b12c53c --- /dev/null +++ b/vendor/src/github.com/kr/pty/ioctl_bsd.go @@ -0,0 +1,39 @@ +// +build darwin dragonfly freebsd netbsd openbsd + +package pty + +// from +const ( + _IOC_VOID uintptr = 0x20000000 + _IOC_OUT uintptr = 0x40000000 + _IOC_IN uintptr = 0x80000000 + _IOC_IN_OUT uintptr = _IOC_OUT | _IOC_IN + _IOC_DIRMASK = _IOC_VOID | _IOC_OUT | _IOC_IN + + _IOC_PARAM_SHIFT = 13 + _IOC_PARAM_MASK = (1 << _IOC_PARAM_SHIFT) - 1 +) + +func _IOC_PARM_LEN(ioctl uintptr) uintptr { + return (ioctl >> 16) & _IOC_PARAM_MASK +} + +func _IOC(inout uintptr, group byte, ioctl_num uintptr, param_len uintptr) uintptr { + return inout | (param_len&_IOC_PARAM_MASK)<<16 | uintptr(group)<<8 | ioctl_num +} + +func _IO(group byte, ioctl_num uintptr) uintptr { + return _IOC(_IOC_VOID, group, ioctl_num, 0) +} + +func _IOR(group byte, ioctl_num uintptr, param_len uintptr) uintptr { + return _IOC(_IOC_OUT, group, ioctl_num, param_len) +} + +func _IOW(group byte, ioctl_num uintptr, param_len uintptr) uintptr { + return _IOC(_IOC_IN, group, ioctl_num, param_len) +} + +func _IOWR(group byte, ioctl_num uintptr, param_len uintptr) uintptr { + return _IOC(_IOC_IN_OUT, group, ioctl_num, param_len) +} diff --git a/vendor/src/github.com/kr/pty/ioctl_linux.go b/vendor/src/github.com/kr/pty/ioctl_linux.go new file mode 100644 index 000000000..9fe7b0b0f --- /dev/null +++ b/vendor/src/github.com/kr/pty/ioctl_linux.go @@ -0,0 +1,42 @@ +package pty + +// from +const ( + _IOC_NRBITS = 8 + _IOC_TYPEBITS = 8 + + _IOC_SIZEBITS = 14 + _IOC_DIRBITS = 2 + + _IOC_NRSHIFT = 0 + _IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS + _IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS + _IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS + + _IOC_NONE uint = 0 + _IOC_WRITE uint = 1 + _IOC_READ uint = 2 +) + +func _IOC(dir uint, ioctl_type byte, nr byte, size uintptr) uintptr { + return (uintptr(dir)<<_IOC_DIRSHIFT | + uintptr(ioctl_type)<<_IOC_TYPESHIFT | + uintptr(nr)<<_IOC_NRSHIFT | + size<<_IOC_SIZESHIFT) +} + +func _IO(ioctl_type byte, nr byte) uintptr { + return _IOC(_IOC_NONE, ioctl_type, nr, 0) +} + +func _IOR(ioctl_type byte, nr byte, size uintptr) uintptr { + return _IOC(_IOC_READ, ioctl_type, nr, size) +} + +func _IOW(ioctl_type byte, nr byte, size uintptr) uintptr { + return _IOC(_IOC_WRITE, ioctl_type, nr, size) +} + +func _IOWR(ioctl_type byte, nr byte, size uintptr) uintptr { + return _IOC(_IOC_READ|_IOC_WRITE, ioctl_type, nr, size) +} diff --git a/vendor/src/github.com/kr/pty/mktypes.bash b/vendor/src/github.com/kr/pty/mktypes.bash new file mode 100755 index 000000000..9952c8883 --- /dev/null +++ b/vendor/src/github.com/kr/pty/mktypes.bash @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +GOOSARCH="${GOOS}_${GOARCH}" +case "$GOOSARCH" in +_* | *_ | _) + echo 'undefined $GOOS_$GOARCH:' "$GOOSARCH" 1>&2 + exit 1 + ;; +esac + +GODEFS="go tool cgo -godefs" + +$GODEFS types.go |gofmt > ztypes_$GOARCH.go + +case $GOOS in +freebsd) + $GODEFS types_$GOOS.go |gofmt > ztypes_$GOOSARCH.go + ;; +esac diff --git a/vendor/src/github.com/kr/pty/pty_darwin.go b/vendor/src/github.com/kr/pty/pty_darwin.go index 597bb03e5..4f4d5ca26 100644 --- a/vendor/src/github.com/kr/pty/pty_darwin.go +++ b/vendor/src/github.com/kr/pty/pty_darwin.go @@ -7,9 +7,6 @@ import ( "unsafe" ) -// see ioccom.h -const sys_IOCPARM_MASK = 0x1fff - func open() (pty, tty *os.File, err error) { p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0) if err != nil { @@ -39,9 +36,13 @@ func open() (pty, tty *os.File, err error) { } func ptsname(f *os.File) (string, error) { - var n [(syscall.TIOCPTYGNAME >> 16) & sys_IOCPARM_MASK]byte + n := make([]byte, _IOC_PARM_LEN(syscall.TIOCPTYGNAME)) + + err := ioctl(f.Fd(), syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n[0]))) + if err != nil { + return "", err + } - ioctl(f.Fd(), syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n))) for i, c := range n { if c == 0 { return string(n[:i]), nil @@ -51,19 +52,9 @@ func ptsname(f *os.File) (string, error) { } func grantpt(f *os.File) error { - var u int - return ioctl(f.Fd(), syscall.TIOCPTYGRANT, uintptr(unsafe.Pointer(&u))) + return ioctl(f.Fd(), syscall.TIOCPTYGRANT, 0) } func unlockpt(f *os.File) error { - var u int - return ioctl(f.Fd(), syscall.TIOCPTYUNLK, uintptr(unsafe.Pointer(&u))) -} - -func ioctl(fd, cmd, ptr uintptr) error { - _, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr) - if e != 0 { - return syscall.ENOTTY - } - return nil + return ioctl(f.Fd(), syscall.TIOCPTYUNLK, 0) } diff --git a/vendor/src/github.com/kr/pty/pty_freebsd.go b/vendor/src/github.com/kr/pty/pty_freebsd.go index 13b64d722..b341babd0 100644 --- a/vendor/src/github.com/kr/pty/pty_freebsd.go +++ b/vendor/src/github.com/kr/pty/pty_freebsd.go @@ -1,53 +1,73 @@ package pty import ( + "errors" "os" - "strconv" "syscall" "unsafe" ) -const ( - sys_TIOCGPTN = 0x4004740F - sys_TIOCSPTLCK = 0x40045431 -) +func posix_openpt(oflag int) (fd int, err error) { + r0, _, e1 := syscall.Syscall(syscall.SYS_POSIX_OPENPT, uintptr(oflag), 0, 0) + fd = int(r0) + if e1 != 0 { + err = e1 + } + return +} func open() (pty, tty *os.File, err error) { - p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0) + fd, err := posix_openpt(syscall.O_RDWR | syscall.O_CLOEXEC) if err != nil { return nil, nil, err } + p := os.NewFile(uintptr(fd), "/dev/pts") sname, err := ptsname(p) if err != nil { return nil, nil, err } - t, err := os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0) + t, err := os.OpenFile("/dev/"+sname, os.O_RDWR, 0) if err != nil { return nil, nil, err } return p, t, nil } +func isptmaster(fd uintptr) (bool, error) { + err := ioctl(fd, syscall.TIOCPTMASTER, 0) + return err == nil, err +} + +var ( + emptyFiodgnameArg fiodgnameArg + ioctl_FIODGNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg)) +) + func ptsname(f *os.File) (string, error) { - var n int - err := ioctl(f.Fd(), sys_TIOCGPTN, &n) + master, err := isptmaster(f.Fd()) if err != nil { return "", err } - return "/dev/pts/" + strconv.Itoa(n), nil -} - -func ioctl(fd uintptr, cmd uintptr, data *int) error { - _, _, e := syscall.Syscall( - syscall.SYS_IOCTL, - fd, - cmd, - uintptr(unsafe.Pointer(data)), - ) - if e != 0 { - return syscall.ENOTTY + if !master { + return "", syscall.EINVAL } - return nil + + const n = _C_SPECNAMELEN + 1 + var ( + buf = make([]byte, n) + arg = fiodgnameArg{Len: n, Buf: (*byte)(unsafe.Pointer(&buf[0]))} + ) + err = ioctl(f.Fd(), ioctl_FIODGNAME, uintptr(unsafe.Pointer(&arg))) + if err != nil { + return "", err + } + + for i, c := range buf { + if c == 0 { + return string(buf[:i]), nil + } + } + return "", errors.New("FIODGNAME string not NUL-terminated") } diff --git a/vendor/src/github.com/kr/pty/pty_linux.go b/vendor/src/github.com/kr/pty/pty_linux.go index a5edfbb39..6e5a04241 100644 --- a/vendor/src/github.com/kr/pty/pty_linux.go +++ b/vendor/src/github.com/kr/pty/pty_linux.go @@ -7,9 +7,9 @@ import ( "unsafe" ) -const ( - sys_TIOCGPTN = 0x80045430 - sys_TIOCSPTLCK = 0x40045431 +var ( + ioctl_TIOCGPTN = _IOR('T', 0x30, unsafe.Sizeof(_C_uint(0))) /* Get Pty Number (of pty-mux device) */ + ioctl_TIOCSPTLCK = _IOW('T', 0x31, unsafe.Sizeof(_C_int(0))) /* Lock/unlock Pty */ ) func open() (pty, tty *os.File, err error) { @@ -36,28 +36,16 @@ func open() (pty, tty *os.File, err error) { } func ptsname(f *os.File) (string, error) { - var n int - err := ioctl(f.Fd(), sys_TIOCGPTN, &n) + var n _C_uint + err := ioctl(f.Fd(), ioctl_TIOCGPTN, uintptr(unsafe.Pointer(&n))) if err != nil { return "", err } - return "/dev/pts/" + strconv.Itoa(n), nil + return "/dev/pts/" + strconv.Itoa(int(n)), nil } func unlockpt(f *os.File) error { - var u int - return ioctl(f.Fd(), sys_TIOCSPTLCK, &u) -} - -func ioctl(fd uintptr, cmd uintptr, data *int) error { - _, _, e := syscall.Syscall( - syscall.SYS_IOCTL, - fd, - cmd, - uintptr(unsafe.Pointer(data)), - ) - if e != 0 { - return syscall.ENOTTY - } - return nil + var u _C_int + // use TIOCSPTLCK with a zero valued arg to clear the slave pty lock + return ioctl(f.Fd(), ioctl_TIOCSPTLCK, uintptr(unsafe.Pointer(&u))) } diff --git a/vendor/src/github.com/kr/pty/pty_unsupported.go b/vendor/src/github.com/kr/pty/pty_unsupported.go index d4958b358..898c7303c 100644 --- a/vendor/src/github.com/kr/pty/pty_unsupported.go +++ b/vendor/src/github.com/kr/pty/pty_unsupported.go @@ -9,19 +9,3 @@ import ( func open() (pty, tty *os.File, err error) { return nil, nil, ErrUnsupported } - -func ptsname(f *os.File) (string, error) { - return "", ErrUnsupported -} - -func grantpt(f *os.File) error { - return ErrUnsupported -} - -func unlockpt(f *os.File) error { - return ErrUnsupported -} - -func ioctl(fd, cmd, ptr uintptr) error { - return ErrUnsupported -} diff --git a/vendor/src/github.com/kr/pty/types.go b/vendor/src/github.com/kr/pty/types.go new file mode 100644 index 000000000..5aecb6bcd --- /dev/null +++ b/vendor/src/github.com/kr/pty/types.go @@ -0,0 +1,10 @@ +// +build ignore + +package pty + +import "C" + +type ( + _C_int C.int + _C_uint C.uint +) diff --git a/vendor/src/github.com/kr/pty/types_freebsd.go b/vendor/src/github.com/kr/pty/types_freebsd.go new file mode 100644 index 000000000..ce3eb9518 --- /dev/null +++ b/vendor/src/github.com/kr/pty/types_freebsd.go @@ -0,0 +1,15 @@ +// +build ignore + +package pty + +/* +#include +#include +*/ +import "C" + +const ( + _C_SPECNAMELEN = C.SPECNAMELEN /* max length of devicename */ +) + +type fiodgnameArg C.struct_fiodgname_arg diff --git a/vendor/src/github.com/kr/pty/ztypes_386.go b/vendor/src/github.com/kr/pty/ztypes_386.go new file mode 100644 index 000000000..ff0b8fd83 --- /dev/null +++ b/vendor/src/github.com/kr/pty/ztypes_386.go @@ -0,0 +1,9 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types.go + +package pty + +type ( + _C_int int32 + _C_uint uint32 +) diff --git a/vendor/src/github.com/kr/pty/ztypes_amd64.go b/vendor/src/github.com/kr/pty/ztypes_amd64.go new file mode 100644 index 000000000..ff0b8fd83 --- /dev/null +++ b/vendor/src/github.com/kr/pty/ztypes_amd64.go @@ -0,0 +1,9 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types.go + +package pty + +type ( + _C_int int32 + _C_uint uint32 +) diff --git a/vendor/src/github.com/kr/pty/ztypes_arm.go b/vendor/src/github.com/kr/pty/ztypes_arm.go new file mode 100644 index 000000000..ff0b8fd83 --- /dev/null +++ b/vendor/src/github.com/kr/pty/ztypes_arm.go @@ -0,0 +1,9 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types.go + +package pty + +type ( + _C_int int32 + _C_uint uint32 +) diff --git a/vendor/src/github.com/kr/pty/ztypes_freebsd_386.go b/vendor/src/github.com/kr/pty/ztypes_freebsd_386.go new file mode 100644 index 000000000..d9975374e --- /dev/null +++ b/vendor/src/github.com/kr/pty/ztypes_freebsd_386.go @@ -0,0 +1,13 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types_freebsd.go + +package pty + +const ( + _C_SPECNAMELEN = 0x3f +) + +type fiodgnameArg struct { + Len int32 + Buf *byte +} diff --git a/vendor/src/github.com/kr/pty/ztypes_freebsd_amd64.go b/vendor/src/github.com/kr/pty/ztypes_freebsd_amd64.go new file mode 100644 index 000000000..5fa102fcd --- /dev/null +++ b/vendor/src/github.com/kr/pty/ztypes_freebsd_amd64.go @@ -0,0 +1,14 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types_freebsd.go + +package pty + +const ( + _C_SPECNAMELEN = 0x3f +) + +type fiodgnameArg struct { + Len int32 + Pad_cgo_0 [4]byte + Buf *byte +} diff --git a/vendor/src/github.com/kr/pty/ztypes_freebsd_arm.go b/vendor/src/github.com/kr/pty/ztypes_freebsd_arm.go new file mode 100644 index 000000000..d9975374e --- /dev/null +++ b/vendor/src/github.com/kr/pty/ztypes_freebsd_arm.go @@ -0,0 +1,13 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs types_freebsd.go + +package pty + +const ( + _C_SPECNAMELEN = 0x3f +) + +type fiodgnameArg struct { + Len int32 + Buf *byte +} From 9810da853bd890b6c963017555c3555ef9e0d842 Mon Sep 17 00:00:00 2001 From: Brice Jaglin Date: Mon, 19 May 2014 23:24:33 +0200 Subject: [PATCH 193/400] force the read of the tarSum so that sums actually get computed Docker-DCO-1.1-Signed-off-by: Brice Jaglin (github: bjaglin) --- server/buildfile.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/buildfile.go b/server/buildfile.go index efe186950..46f214eac 100644 --- a/server/buildfile.go +++ b/server/buildfile.go @@ -571,6 +571,9 @@ func (b *buildFile) CmdAdd(args string) error { return err } tarSum := utils.TarSum{Reader: r, DisableCompression: true} + if _, err := io.Copy(ioutil.Discard, &tarSum); err != nil { + return err + } remoteHash = tarSum.Sum(nil) r.Close() From bcfe2ceffb1c4c7006570d4ba21ed2068bb448a1 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Sat, 17 May 2014 14:09:05 -0700 Subject: [PATCH 194/400] Remove the mtime for temp file. Prevent false negative cache Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- server/buildfile.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/server/buildfile.go b/server/buildfile.go index 46f214eac..b37053ac1 100644 --- a/server/buildfile.go +++ b/server/buildfile.go @@ -16,11 +16,13 @@ import ( "regexp" "sort" "strings" + "syscall" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/daemon" "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/pkg/symlink" + "github.com/dotcloud/docker/pkg/system" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" @@ -563,6 +565,11 @@ func (b *buildFile) CmdAdd(args string) error { } tmpFile.Close() + // Remove the mtime of the newly created tmp file + if err := system.UtimesNano(tmpFileName, make([]syscall.Timespec, 2)); err != nil { + return err + } + origPath = path.Join(filepath.Base(tmpDirName), filepath.Base(tmpFileName)) // Process the checksum @@ -570,8 +577,8 @@ func (b *buildFile) CmdAdd(args string) error { if err != nil { return err } - tarSum := utils.TarSum{Reader: r, DisableCompression: true} - if _, err := io.Copy(ioutil.Discard, &tarSum); err != nil { + tarSum := &utils.TarSum{Reader: r, DisableCompression: true} + if _, err := io.Copy(ioutil.Discard, tarSum); err != nil { return err } remoteHash = tarSum.Sum(nil) From 03a109e446351ba40ea5d74e8eb6733b2a9a5045 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 20 May 2014 21:29:19 +0000 Subject: [PATCH 195/400] add test Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- .../TestBuildCacheADD/1/Dockerfile | 2 ++ .../TestBuildCacheADD/2/Dockerfile | 2 ++ integration-cli/docker_cli_build_test.go | 31 +++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 integration-cli/build_tests/TestBuildCacheADD/1/Dockerfile create mode 100644 integration-cli/build_tests/TestBuildCacheADD/2/Dockerfile diff --git a/integration-cli/build_tests/TestBuildCacheADD/1/Dockerfile b/integration-cli/build_tests/TestBuildCacheADD/1/Dockerfile new file mode 100644 index 000000000..728777199 --- /dev/null +++ b/integration-cli/build_tests/TestBuildCacheADD/1/Dockerfile @@ -0,0 +1,2 @@ +FROM busybox +ADD https://index.docker.io/robots.txt / diff --git a/integration-cli/build_tests/TestBuildCacheADD/2/Dockerfile b/integration-cli/build_tests/TestBuildCacheADD/2/Dockerfile new file mode 100644 index 000000000..afe79b84b --- /dev/null +++ b/integration-cli/build_tests/TestBuildCacheADD/2/Dockerfile @@ -0,0 +1,2 @@ +FROM busybox +ADD http://example.com/index.html / diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index e8ca7eae7..041b10d8b 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -9,6 +9,37 @@ import ( "testing" ) +func TestBuildCacheADD(t *testing.T) { + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestBuildCacheADD", "1") + buildCmd := exec.Command(dockerBinary, "build", "-t", "testcacheadd1", ".") + buildCmd.Dir = buildDirectory + exitCode, err := runCommand(buildCmd) + errorOut(err, t, fmt.Sprintf("build failed to complete: %v", err)) + + if err != nil || exitCode != 0 { + t.Fatal("failed to build the image") + } + + buildDirectory = filepath.Join(workingDirectory, "build_tests", "TestBuildCacheADD", "2") + buildCmd = exec.Command(dockerBinary, "build", "-t", "testcacheadd2", ".") + buildCmd.Dir = buildDirectory + out, exitCode, err := runCommandWithOutput(buildCmd) + errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) + + if err != nil || exitCode != 0 { + t.Fatal("failed to build the image") + } + + if strings.Contains(out, "Using cache") { + t.Fatal("2nd build used cache on ADD, it shouldn't") + } + + deleteImages("testcacheadd1") + deleteImages("testcacheadd2") + + logDone("build - build two images with ADD") +} + func TestBuildSixtySteps(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestBuildSixtySteps") buildCmd := exec.Command(dockerBinary, "build", "-t", "foobuildsixtysteps", ".") From 34c05c58c8d41ee2bb02cd8059e9928ee2f061ea Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 20 May 2014 00:13:00 +0000 Subject: [PATCH 196/400] Mount /dev in tmpfs for privileged containers Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/execdriver/native/create.go | 10 ++++--- .../native/template/default_template.go | 5 ++++ pkg/libcontainer/container.go | 27 ++++++++++--------- pkg/libcontainer/container.json | 12 ++++++++- pkg/libcontainer/container_test.go | 21 ++++++++++----- pkg/libcontainer/mount/init.go | 10 +++---- pkg/libcontainer/mount/nodes/nodes.go | 21 +++++++++++---- .../mount/nodes/nodes_unsupported.go | 11 ++++++++ 8 files changed, 83 insertions(+), 34 deletions(-) create mode 100644 pkg/libcontainer/mount/nodes/nodes_unsupported.go diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index 76816e0b9..9ed049194 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -10,6 +10,7 @@ import ( "github.com/dotcloud/docker/daemon/execdriver/native/template" "github.com/dotcloud/docker/pkg/apparmor" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/mount/nodes" ) // createContainer populates and configures the container type with the @@ -34,8 +35,6 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Container if err := d.setPrivileged(container); err != nil { return nil, err } - } else { - container.Mounts = append(container.Mounts, libcontainer.Mount{Type: "devtmpfs"}) } if err := d.setupCgroups(container, c); err != nil { return nil, err @@ -97,11 +96,16 @@ func (d *driver) createNetwork(container *libcontainer.Container, c *execdriver. return nil } -func (d *driver) setPrivileged(container *libcontainer.Container) error { +func (d *driver) setPrivileged(container *libcontainer.Container) (err error) { container.Capabilities = libcontainer.GetAllCapabilities() container.Cgroups.DeviceAccess = true delete(container.Context, "restrictions") + delete(container.DeviceNodes, "additional") + + if container.DeviceNodes["required"], err = nodes.GetHostDeviceNodes(); err != nil { + return err + } if apparmor.IsEnabled() { container.Context["apparmor_profile"] = "unconfined" diff --git a/daemon/execdriver/native/template/default_template.go b/daemon/execdriver/native/template/default_template.go index ba52499c2..dbe3985f9 100644 --- a/daemon/execdriver/native/template/default_template.go +++ b/daemon/execdriver/native/template/default_template.go @@ -4,6 +4,7 @@ import ( "github.com/dotcloud/docker/pkg/apparmor" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/cgroups" + "github.com/dotcloud/docker/pkg/libcontainer/mount/nodes" ) // New returns the docker default configuration for libcontainer @@ -33,6 +34,10 @@ func New() *libcontainer.Container { DeviceAccess: false, }, Context: libcontainer.Context{}, + DeviceNodes: map[string][]string{ + "required": nodes.DefaultNodes, + "additional": {"fuse"}, + }, } if apparmor.IsEnabled() { container.Context["apparmor_profile"] = "docker-default" diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index 0ea8d37c2..092cd5d93 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -11,19 +11,20 @@ type Context map[string]string // Container defines configuration options for how a // container is setup inside a directory and how a process should be executed type Container struct { - Hostname string `json:"hostname,omitempty"` // hostname - ReadonlyFs bool `json:"readonly_fs,omitempty"` // set the containers rootfs as readonly - NoPivotRoot bool `json:"no_pivot_root,omitempty"` // this can be enabled if you are running in ramdisk - User string `json:"user,omitempty"` // user to execute the process as - WorkingDir string `json:"working_dir,omitempty"` // current working directory - Env []string `json:"environment,omitempty"` // environment to set - Tty bool `json:"tty,omitempty"` // setup a proper tty or not - Namespaces map[string]bool `json:"namespaces,omitempty"` // namespaces to apply - Capabilities []string `json:"capabilities,omitempty"` // capabilities given to the container - Networks []*Network `json:"networks,omitempty"` // nil for host's network stack - Cgroups *cgroups.Cgroup `json:"cgroups,omitempty"` // cgroups - Context Context `json:"context,omitempty"` // generic context for specific options (apparmor, selinux) - Mounts Mounts `json:"mounts,omitempty"` + Hostname string `json:"hostname,omitempty"` // hostname + ReadonlyFs bool `json:"readonly_fs,omitempty"` // set the containers rootfs as readonly + NoPivotRoot bool `json:"no_pivot_root,omitempty"` // this can be enabled if you are running in ramdisk + User string `json:"user,omitempty"` // user to execute the process as + WorkingDir string `json:"working_dir,omitempty"` // current working directory + Env []string `json:"environment,omitempty"` // environment to set + Tty bool `json:"tty,omitempty"` // setup a proper tty or not + Namespaces map[string]bool `json:"namespaces,omitempty"` // namespaces to apply + Capabilities []string `json:"capabilities,omitempty"` // capabilities given to the container + Networks []*Network `json:"networks,omitempty"` // nil for host's network stack + Cgroups *cgroups.Cgroup `json:"cgroups,omitempty"` // cgroups + Context Context `json:"context,omitempty"` // generic context for specific options (apparmor, selinux) + Mounts Mounts `json:"mounts,omitempty"` + DeviceNodes map[string][]string `json:"device_nodes,omitempty"` // device nodes to add to the container's /dev } // Network defines configuration for a container's networking stack diff --git a/pkg/libcontainer/container.json b/pkg/libcontainer/container.json index 07950fe58..c3b0196b4 100644 --- a/pkg/libcontainer/container.json +++ b/pkg/libcontainer/container.json @@ -43,5 +43,15 @@ { "type": "devtmpfs" } - ] + ], + "device_nodes": { + "required": [ + "null", + "zero", + "full", + "random", + "urandom", + "tty" + ] + } } diff --git a/pkg/libcontainer/container_test.go b/pkg/libcontainer/container_test.go index b3f240740..d77ce313a 100644 --- a/pkg/libcontainer/container_test.go +++ b/pkg/libcontainer/container_test.go @@ -4,12 +4,14 @@ import ( "encoding/json" "os" "testing" + + "github.com/dotcloud/docker/pkg/libcontainer/mount/nodes" ) // Checks whether the expected capability is specified in the capabilities. -func hasCapability(expected string, capabilities []string) bool { - for _, capability := range capabilities { - if capability == expected { +func contains(expected string, values []string) bool { + for _, v := range values { + if v == expected { return true } } @@ -47,18 +49,25 @@ func TestContainerJsonFormat(t *testing.T) { t.Fail() } - if hasCapability("SYS_ADMIN", container.Capabilities) { + if contains("SYS_ADMIN", container.Capabilities) { t.Log("SYS_ADMIN should not be enabled in capabilities mask") t.Fail() } - if !hasCapability("MKNOD", container.Capabilities) { + if !contains("MKNOD", container.Capabilities) { t.Log("MKNOD should be enabled in capabilities mask") t.Fail() } - if hasCapability("SYS_CHROOT", container.Capabilities) { + if contains("SYS_CHROOT", container.Capabilities) { t.Log("capabilities mask should not contain SYS_CHROOT") t.Fail() } + + for _, n := range nodes.DefaultNodes { + if !contains(n, container.DeviceNodes["required"]) { + t.Logf("devices should contain %s", n) + t.Fail() + } + } } diff --git a/pkg/libcontainer/mount/init.go b/pkg/libcontainer/mount/init.go index c4148131a..184df1e8e 100644 --- a/pkg/libcontainer/mount/init.go +++ b/pkg/libcontainer/mount/init.go @@ -48,10 +48,10 @@ func InitializeMountNamespace(rootfs, console string, container *libcontainer.Co if err := setupBindmounts(rootfs, container.Mounts); err != nil { return fmt.Errorf("bind mounts %s", err) } - if err := nodes.CopyN(rootfs, nodes.DefaultNodes, true); err != nil { - return fmt.Errorf("copy dev nodes %s", err) + if err := nodes.CopyN(rootfs, container.DeviceNodes["required"], true); err != nil { + return fmt.Errorf("copy required dev nodes %s", err) } - if err := nodes.CopyN(rootfs, nodes.AdditionalNodes, false); err != nil { + if err := nodes.CopyN(rootfs, container.DeviceNodes["additional"], false); err != nil { return fmt.Errorf("copy additional dev nodes %s", err) } if err := SetupPtmx(rootfs, console, container.Context["mount_label"]); err != nil { @@ -195,13 +195,11 @@ func newSystemMounts(rootfs, mountLabel string, mounts libcontainer.Mounts) []mo systemMounts := []mount{ {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, {source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags}, + {source: "tmpfs", path: filepath.Join(rootfs, "dev"), device: "tmpfs", flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME, data: label.FormatMountLabel("mode=755", mountLabel)}, {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}, {source: "tmpfs", path: filepath.Join(rootfs, "run"), device: "tmpfs", flags: defaultMountFlags}, } - if len(mounts.OfType("devtmpfs")) == 1 { - systemMounts = append([]mount{{source: "tmpfs", path: filepath.Join(rootfs, "dev"), device: "tmpfs", flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME, data: label.FormatMountLabel("mode=755", mountLabel)}}, systemMounts...) - } return systemMounts } diff --git a/pkg/libcontainer/mount/nodes/nodes.go b/pkg/libcontainer/mount/nodes/nodes.go index 138468272..14b6f5ae5 100644 --- a/pkg/libcontainer/mount/nodes/nodes.go +++ b/pkg/libcontainer/mount/nodes/nodes.go @@ -4,6 +4,7 @@ package nodes import ( "fmt" + "io/ioutil" "os" "path/filepath" "syscall" @@ -21,11 +22,6 @@ var DefaultNodes = []string{ "tty", } -// AdditionalNodes includes nodes that are not required -var AdditionalNodes = []string{ - "fuse", -} - // CopyN copies the device node from the host into the rootfs func CopyN(rootfs string, nodesToCopy []string, shouldExist bool) error { oldMask := system.Umask(0000) @@ -61,3 +57,18 @@ func Copy(rootfs, node string, shouldExist bool) error { } return nil } + +func GetHostDeviceNodes() ([]string, error) { + files, err := ioutil.ReadDir("/dev") + if err != nil { + return nil, err + } + + out := []string{} + for _, f := range files { + if f.Mode()&os.ModeDevice == os.ModeDevice { + out = append(out, f.Name()) + } + } + return out, nil +} diff --git a/pkg/libcontainer/mount/nodes/nodes_unsupported.go b/pkg/libcontainer/mount/nodes/nodes_unsupported.go new file mode 100644 index 000000000..24409f411 --- /dev/null +++ b/pkg/libcontainer/mount/nodes/nodes_unsupported.go @@ -0,0 +1,11 @@ +// +build !linux + +package nodes + +import "github.com/dotcloud/docker/pkg/libcontainer" + +var DefaultNodes = []string{} + +func GetHostDeviceNodes() ([]string, error) { + return nil, libcontainer.ErrUnsupported +} From ed5892ed4efa995950e2fdeb5fd718b3bb1aa1c2 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 20 May 2014 23:34:46 +0000 Subject: [PATCH 197/400] Update documentation for container struct in libcontainer Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/container.go | 100 ++++++++++++++++++++++++++-------- 1 file changed, 77 insertions(+), 23 deletions(-) diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index 092cd5d93..f7aa24585 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -4,27 +4,70 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) -// Context is a generic key value pair that allows -// arbatrary data to be sent +// Context is a generic key value pair that allows arbatrary data to be sent type Context map[string]string -// Container defines configuration options for how a -// container is setup inside a directory and how a process should be executed +// Container defines configuration options for executing a process inside a contained environment type Container struct { - Hostname string `json:"hostname,omitempty"` // hostname - ReadonlyFs bool `json:"readonly_fs,omitempty"` // set the containers rootfs as readonly - NoPivotRoot bool `json:"no_pivot_root,omitempty"` // this can be enabled if you are running in ramdisk - User string `json:"user,omitempty"` // user to execute the process as - WorkingDir string `json:"working_dir,omitempty"` // current working directory - Env []string `json:"environment,omitempty"` // environment to set - Tty bool `json:"tty,omitempty"` // setup a proper tty or not - Namespaces map[string]bool `json:"namespaces,omitempty"` // namespaces to apply - Capabilities []string `json:"capabilities,omitempty"` // capabilities given to the container - Networks []*Network `json:"networks,omitempty"` // nil for host's network stack - Cgroups *cgroups.Cgroup `json:"cgroups,omitempty"` // cgroups - Context Context `json:"context,omitempty"` // generic context for specific options (apparmor, selinux) - Mounts Mounts `json:"mounts,omitempty"` - DeviceNodes map[string][]string `json:"device_nodes,omitempty"` // device nodes to add to the container's /dev + // Hostname optionally sets the container's hostname if provided + Hostname string `json:"hostname,omitempty"` + + // ReadonlyFs will remount the container's rootfs as readonly where only externally mounted + // bind mounts are writtable + ReadonlyFs bool `json:"readonly_fs,omitempty"` + + // NoPivotRoot will use MS_MOVE and a chroot to jail the process into the container's rootfs + // This is a common option when the container is running in ramdisk + NoPivotRoot bool `json:"no_pivot_root,omitempty"` + + // User will set the uid and gid of the executing process running inside the container + User string `json:"user,omitempty"` + + // WorkingDir will change the processes current working directory inside the container's rootfs + WorkingDir string `json:"working_dir,omitempty"` + + // Env will populate the processes environment with the provided values + // Any values from the parent processes will be cleared before the values + // provided in Env are provided to the process + Env []string `json:"environment,omitempty"` + + // Tty when true will allocate a pty slave on the host for access by the container's process + // and ensure that it is mounted inside the container's rootfs + Tty bool `json:"tty,omitempty"` + + // Namespaces specifies the container's namespaces that it should setup when cloning the init process + // If a namespace is not provided that namespace is shared from the container's parent process + Namespaces map[string]bool `json:"namespaces,omitempty"` + + // Capabilities specify the capabilities to keep when executing the process inside the container + // All capbilities not specified will be dropped from the processes capability mask + Capabilities []string `json:"capabilities,omitempty"` + + // Networks specifies the container's network stop to be created + Networks []*Network `json:"networks,omitempty"` + + // Cgroups specifies specific cgroup settings for the various subsystems that the container is + // placed into to limit the resources the container has available + Cgroups *cgroups.Cgroup `json:"cgroups,omitempty"` + + // Context is a generic key value format that allows for additional settings to be passed + // on the container's creation + // This is commonly used to specify apparmor profiles, selinux labels, and different restrictions + // placed on the container's processes + Context Context `json:"context,omitempty"` + + // Mounts specify additional source and destination paths that will be mounted inside the container's + // rootfs and mount namespace if specified + Mounts Mounts `json:"mounts,omitempty"` + + // DeviceNodes are a list of 'required' and 'additional' nodes that will be mknod into the container's + // rootfs at /dev + // + // Required device nodes will return an error if the host system does not have this device available + // + // Additional device nodes are created but no error is returned if the host system does not have the + // device avaliable for use by the container + DeviceNodes map[string][]string `json:"device_nodes,omitempty"` } // Network defines configuration for a container's networking stack @@ -32,9 +75,20 @@ type Container struct { // The network configuration can be omited from a container causing the // container to be setup with the host's networking stack type Network struct { - Type string `json:"type,omitempty"` // type of networking to setup i.e. veth, macvlan, etc - Context Context `json:"context,omitempty"` // generic context for type specific networking options - Address string `json:"address,omitempty"` - Gateway string `json:"gateway,omitempty"` - Mtu int `json:"mtu,omitempty"` + // Type sets the networks type, commonly veth and loopback + Type string `json:"type,omitempty"` + + // Context is a generic key value format for setting additional options that are specific to + // the network type + Context Context `json:"context,omitempty"` + + // Address contains the IP and mask to set on the network interface + Address string `json:"address,omitempty"` + + // Gateway sets the gateway address that is used as the default for the interface + Gateway string `json:"gateway,omitempty"` + + // Mtu sets the mtu value for the interface and will be mirrored on both the host and + // container's interfaces if a pair is created, specifically in the case of type veth + Mtu int `json:"mtu,omitempty"` } From 5eef0a28cb70881d8f1e34519e9c0df6cb1da071 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 20 May 2014 23:34:48 +0000 Subject: [PATCH 198/400] add test Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- Dockerfile | 2 ++ integration-cli/docker_cli_cp_test.go | 37 +++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/Dockerfile b/Dockerfile index 183ec89db..41f0f9294 100644 --- a/Dockerfile +++ b/Dockerfile @@ -91,6 +91,8 @@ RUN git config --global user.email 'docker-dummy@example.com' # Add an unprivileged user to be used for tests which need it RUN adduser unprivilegeduser +RUN groupadd docker +RUN gpasswd -a unprivilegeduser docker VOLUME /var/lib/docker WORKDIR /go/src/github.com/dotcloud/docker diff --git a/integration-cli/docker_cli_cp_test.go b/integration-cli/docker_cli_cp_test.go index b5a70a45e..7421ed0fa 100644 --- a/integration-cli/docker_cli_cp_test.go +++ b/integration-cli/docker_cli_cp_test.go @@ -4,6 +4,7 @@ import ( "fmt" "io/ioutil" "os" + "os/exec" "path/filepath" "testing" ) @@ -206,3 +207,39 @@ func TestCpAbsolutePath(t *testing.T) { logDone("cp - absolute paths relative to container's rootfs") } + +// Check that cp with unprivileged user doesn't return any error +func TestCpUnprivilegedUser(t *testing.T) { + out, exitCode, err := cmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch "+cpTestName) + if err != nil || exitCode != 0 { + t.Fatal("failed to create a container", out, err) + } + + cleanedContainerID := stripTrailingCharacters(out) + defer deleteContainer(cleanedContainerID) + + out, _, err = cmd(t, "wait", cleanedContainerID) + if err != nil || stripTrailingCharacters(out) != "0" { + t.Fatal("failed to set up container", out, err) + } + + tmpdir, err := ioutil.TempDir("", "docker-integration") + if err != nil { + t.Fatal(err) + } + + defer os.RemoveAll(tmpdir) + + if err = os.Chmod(tmpdir, 0777); err != nil { + t.Fatal(err) + } + + path := cpTestName + + _, _, err = runCommandWithOutput(exec.Command("su", "unprivilegeduser", "-c", dockerBinary+" cp "+cleanedContainerID+":"+path+" "+tmpdir)) + if err != nil { + t.Fatalf("couldn't copy with unprivileged user: %s:%s %s", cleanedContainerID, path, err) + } + + logDone("cp - unprivileged user") +} From 9e51b7abaea3fb30dc994a1d004cd79f2e100c1a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 21 May 2014 00:10:35 +0000 Subject: [PATCH 199/400] ignore lchown error on docker cp Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api/client/commands.go | 2 +- archive/archive.go | 8 ++++---- archive/archive_test.go | 5 +++-- archive/diff.go | 7 ++++--- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index 6562709f3..28c1a9c51 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -2058,7 +2058,7 @@ func (cli *DockerCli) CmdCp(args ...string) error { } if statusCode == 200 { - if err := archive.Untar(stream, copyData.Get("HostPath"), nil); err != nil { + if err := archive.Untar(stream, copyData.Get("HostPath"), &archive.TarOptions{NoLchown: true}); err != nil { return err } } diff --git a/archive/archive.go b/archive/archive.go index 160ea0737..76c6e3128 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -28,6 +28,7 @@ type ( TarOptions struct { Includes []string Compression Compression + NoLchown bool } ) @@ -179,7 +180,7 @@ func addTarFile(path, name string, tw *tar.Writer) error { return nil } -func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader) error { +func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, Lchown bool) error { // hdr.Mode is in linux format, which we can use for sycalls, // but for os.Foo() calls we need the mode converted to os.FileMode, // so use hdrInfo.Mode() (they differ for e.g. setuid bits) @@ -240,7 +241,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader) e return fmt.Errorf("Unhandled tar header type %d\n", hdr.Typeflag) } - if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil { + if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil && Lchown { return err } @@ -415,8 +416,7 @@ func Untar(archive io.Reader, dest string, options *TarOptions) error { } } } - - if err := createTarFile(path, dest, hdr, tr); err != nil { + if err := createTarFile(path, dest, hdr, tr, options == nil || !options.NoLchown); err != nil { return err } diff --git a/archive/archive_test.go b/archive/archive_test.go index e959a2b07..72ffd9956 100644 --- a/archive/archive_test.go +++ b/archive/archive_test.go @@ -3,7 +3,6 @@ package archive import ( "bytes" "fmt" - "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" "io" "io/ioutil" "os" @@ -11,6 +10,8 @@ import ( "path" "testing" "time" + + "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) func TestCmdStreamLargeStderr(t *testing.T) { @@ -132,7 +133,7 @@ func TestTarUntar(t *testing.T) { // Failing prevents the archives from being uncompressed during ADD func TestTypeXGlobalHeaderDoesNotFail(t *testing.T) { hdr := tar.Header{Typeflag: tar.TypeXGlobalHeader} - err := createTarFile("pax_global_header", "some_dir", &hdr, nil) + err := createTarFile("pax_global_header", "some_dir", &hdr, nil, true) if err != nil { t.Fatal(err) } diff --git a/archive/diff.go b/archive/diff.go index 49d8cb498..d16966912 100644 --- a/archive/diff.go +++ b/archive/diff.go @@ -2,13 +2,14 @@ package archive import ( "fmt" - "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" "io" "io/ioutil" "os" "path/filepath" "strings" "syscall" + + "github.com/dotcloud/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) // Linux device nodes are a bit weird due to backwards compat with 16 bit device nodes. @@ -79,7 +80,7 @@ func ApplyLayer(dest string, layer ArchiveReader) error { } defer os.RemoveAll(aufsTempdir) } - if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr); err != nil { + if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr, true); err != nil { return err } } @@ -126,7 +127,7 @@ func ApplyLayer(dest string, layer ArchiveReader) error { srcData = tmpFile } - if err := createTarFile(path, dest, srcHdr, srcData); err != nil { + if err := createTarFile(path, dest, srcHdr, srcData, true); err != nil { return err } From f042c3c15759fce5cc139f2b3362b791ac7d4829 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 21 May 2014 00:36:50 +0000 Subject: [PATCH 200/400] Update code post codereview Add specific types for Required and Optional DeviceNodes Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/execdriver/native/create.go | 4 ++-- .../native/template/default_template.go | 8 +++----- pkg/libcontainer/container.go | 17 ++++++++--------- pkg/libcontainer/container.json | 18 ++++++++---------- pkg/libcontainer/container_test.go | 2 +- pkg/libcontainer/mount/init.go | 6 +++--- 6 files changed, 25 insertions(+), 30 deletions(-) diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index 9ed049194..d35043bd0 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -101,9 +101,9 @@ func (d *driver) setPrivileged(container *libcontainer.Container) (err error) { container.Cgroups.DeviceAccess = true delete(container.Context, "restrictions") - delete(container.DeviceNodes, "additional") - if container.DeviceNodes["required"], err = nodes.GetHostDeviceNodes(); err != nil { + container.OptionalDeviceNodes = nil + if container.RequiredDeviceNodes, err = nodes.GetHostDeviceNodes(); err != nil { return err } diff --git a/daemon/execdriver/native/template/default_template.go b/daemon/execdriver/native/template/default_template.go index dbe3985f9..cbef06fbf 100644 --- a/daemon/execdriver/native/template/default_template.go +++ b/daemon/execdriver/native/template/default_template.go @@ -33,11 +33,9 @@ func New() *libcontainer.Container { Parent: "docker", DeviceAccess: false, }, - Context: libcontainer.Context{}, - DeviceNodes: map[string][]string{ - "required": nodes.DefaultNodes, - "additional": {"fuse"}, - }, + Context: libcontainer.Context{}, + RequiredDeviceNodes: nodes.DefaultNodes, + OptionalDeviceNodes: []string{"fuse"}, } if apparmor.IsEnabled() { container.Context["apparmor_profile"] = "docker-default" diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index f7aa24585..6734bfd59 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -43,7 +43,7 @@ type Container struct { // All capbilities not specified will be dropped from the processes capability mask Capabilities []string `json:"capabilities,omitempty"` - // Networks specifies the container's network stop to be created + // Networks specifies the container's network setup to be created Networks []*Network `json:"networks,omitempty"` // Cgroups specifies specific cgroup settings for the various subsystems that the container is @@ -60,14 +60,13 @@ type Container struct { // rootfs and mount namespace if specified Mounts Mounts `json:"mounts,omitempty"` - // DeviceNodes are a list of 'required' and 'additional' nodes that will be mknod into the container's - // rootfs at /dev - // - // Required device nodes will return an error if the host system does not have this device available - // - // Additional device nodes are created but no error is returned if the host system does not have the - // device avaliable for use by the container - DeviceNodes map[string][]string `json:"device_nodes,omitempty"` + // RequiredDeviceNodes are a list of device nodes that will be mknod into the container's rootfs at /dev + // If the host system does not support the device that the container requests an error is returned + RequiredDeviceNodes []string `json:"required_device_nodes,omitempty"` + + // OptionalDeviceNodes are a list of device nodes that will be mknod into the container's rootfs at /dev + // If the host system does not support the device that the container requests the error is ignored + OptionalDeviceNodes []string `json:"optional_device_nodes,omitempty"` } // Network defines configuration for a container's networking stack diff --git a/pkg/libcontainer/container.json b/pkg/libcontainer/container.json index c3b0196b4..7156260bc 100644 --- a/pkg/libcontainer/container.json +++ b/pkg/libcontainer/container.json @@ -44,14 +44,12 @@ "type": "devtmpfs" } ], - "device_nodes": { - "required": [ - "null", - "zero", - "full", - "random", - "urandom", - "tty" - ] - } + "required_device_nodes": [ + "null", + "zero", + "full", + "random", + "urandom", + "tty" + ] } diff --git a/pkg/libcontainer/container_test.go b/pkg/libcontainer/container_test.go index d77ce313a..f6e991edf 100644 --- a/pkg/libcontainer/container_test.go +++ b/pkg/libcontainer/container_test.go @@ -65,7 +65,7 @@ func TestContainerJsonFormat(t *testing.T) { } for _, n := range nodes.DefaultNodes { - if !contains(n, container.DeviceNodes["required"]) { + if !contains(n, container.RequiredDeviceNodes) { t.Logf("devices should contain %s", n) t.Fail() } diff --git a/pkg/libcontainer/mount/init.go b/pkg/libcontainer/mount/init.go index 184df1e8e..3fb9667b1 100644 --- a/pkg/libcontainer/mount/init.go +++ b/pkg/libcontainer/mount/init.go @@ -48,11 +48,11 @@ func InitializeMountNamespace(rootfs, console string, container *libcontainer.Co if err := setupBindmounts(rootfs, container.Mounts); err != nil { return fmt.Errorf("bind mounts %s", err) } - if err := nodes.CopyN(rootfs, container.DeviceNodes["required"], true); err != nil { + if err := nodes.CopyN(rootfs, container.RequiredDeviceNodes, true); err != nil { return fmt.Errorf("copy required dev nodes %s", err) } - if err := nodes.CopyN(rootfs, container.DeviceNodes["additional"], false); err != nil { - return fmt.Errorf("copy additional dev nodes %s", err) + if err := nodes.CopyN(rootfs, container.OptionalDeviceNodes, false); err != nil { + return fmt.Errorf("copy optional dev nodes %s", err) } if err := SetupPtmx(rootfs, console, container.Context["mount_label"]); err != nil { return err From 789a8f26161ca86a721fe6b7295e3eaf9051b3a2 Mon Sep 17 00:00:00 2001 From: AnandkumarPatel Date: Tue, 20 May 2014 17:20:26 -0700 Subject: [PATCH 201/400] use buffered channel so goroutine does not get blocked on done <- true when a timeout occurs. Docker-DCO-1.1-Signed-off-by: Anandkumar Patel (github: anandkumarpatel) --- daemon/container.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/container.go b/daemon/container.go index 9ca94b2b5..a2dc5977a 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -652,7 +652,7 @@ func (container *Container) Export() (archive.Archive, error) { } func (container *Container) WaitTimeout(timeout time.Duration) error { - done := make(chan bool) + done := make(chan bool, 1) go func() { container.Wait() done <- true From accea18920e856fc1099d85a3fced06dc7b53799 Mon Sep 17 00:00:00 2001 From: tpng Date: Wed, 21 May 2014 16:29:24 +0800 Subject: [PATCH 202/400] Add instructions for persisting data Docker-DCO-1.1-Signed-off-by: Benny Ng (github: tpng) --- docs/sources/installation/windows.md | 34 ++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/docs/sources/installation/windows.md b/docs/sources/installation/windows.md index ec633508c..189be0074 100644 --- a/docs/sources/installation/windows.md +++ b/docs/sources/installation/windows.md @@ -59,10 +59,34 @@ Let's try the “hello world” example. Run This will download the small busybox image and print hello world. -## Observations +## Persistent storage -### Persistent storage +1. Add a virtual hard drive to the VM created in Installation +2. Start the VM +3. Create an empty partition on the attached virtual hard drive -The virtual machine created above lacks any persistent data storage. All -images and containers will be lost when shutting down or rebooting the -VM. + ```sh + sudo fdisk /dev/sda + n (new partition) + p (primary partition) + 1 (partition 1) + w (write changes to disk) + ``` + +4. Format the partition using ext4 + + ```sh + mkfs.ext4 -L boot2docker-data /dev/sda1 + ``` + +5. Reboot + + ```sh + sudo reboot + ``` + +6. boot2docker should now auto mount the partition and persist data there. (/var/lib/docker linking to /mnt/sda1/var/lib/docker) + + ```sh + ls -l /var/lib + ``` From cfe7211a2241b5e898af11445a2470032b0ab242 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 20 May 2014 23:39:57 -0400 Subject: [PATCH 203/400] man/docker-run.1: Fix typo 'priviledged', add missing closing brackets Docker-DCO-1.1-Signed-off-by: Colin Walters (github: cgwalters) --- contrib/man/md/docker-run.1.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/man/md/docker-run.1.md b/contrib/man/md/docker-run.1.md index 3cf56c24b..ef03539ed 100644 --- a/contrib/man/md/docker-run.1.md +++ b/contrib/man/md/docker-run.1.md @@ -14,8 +14,8 @@ docker-run - Run a process in an isolated container [**-e**|**--env**=*environment*] [**--entrypoint**=*command*] [**--expose**=*port*] [**-P**|**--publish-all**[=*false*]] [**-p**|**--publish**=*port-mappping*] [**-h**|**--hostname**=*hostname*] -[**--rm**[=*false*]] [**--priviledged**[=*false*] -[**-i**|**--interactive**[=*false*] +[**--rm**[=*false*]] [**--privileged**[=*false*]] +[**-i**|**--interactive**[=*false*]] [**-t**|**--tty**[=*false*]] [**--lxc-conf**=*options*] [**-n**|**--networking**[=*true*]] [**-v**|**--volume**=*volume*] [**--volumes-from**=*container-id*] From 6d9e64b27bbee9bb699ebc0f0ff98bb7f56961b3 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Wed, 21 May 2014 09:35:22 -0400 Subject: [PATCH 204/400] Fixes some docs issues with using single-dash arguments where they should be double I found a bunch of issues where we have "-" instead of "--". Also a couple of other issues, like "-notrunc", which is now "--no-trunc" Fixes #5963 Docker-DCO-1.1-Signed-off-by: Brian Goff (github: cpuguy83) --- contrib/man/md/docker-run.1.md | 4 ++-- contrib/man/old-man/docker-run.1 | 2 +- docs/sources/articles/runmetrics.md | 2 +- docs/sources/examples/postgresql_service.md | 6 ++--- docs/sources/examples/python_web_app.md | 2 +- .../sources/use/ambassador_pattern_linking.md | 24 +++++++++---------- docs/sources/use/working_with_volumes.md | 16 ++++++------- docs/sources/use/workingwithrepository.md | 2 +- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/contrib/man/md/docker-run.1.md b/contrib/man/md/docker-run.1.md index 3cf56c24b..2ebf82b6a 100644 --- a/contrib/man/md/docker-run.1.md +++ b/contrib/man/md/docker-run.1.md @@ -164,7 +164,7 @@ and foreground Docker containers. Docker container. This is because by default a container is not allowed to access any devices. A “privileged” container is given access to all devices. -When the operator executes **docker run -privileged**, Docker will enable access +When the operator executes **docker run --privileged**, Docker will enable access to all devices on the host as well as set some configuration in AppArmor to allow the container nearly all the same access to the host as processes running outside of a container on the host. @@ -317,7 +317,7 @@ fedora-data image: # docker run --name=data -v /var/volume1 -v /tmp/volume2 -i -t fedora-data true # docker run --volumes-from=data --name=fedora-container1 -i -t fedora bash -Multiple -volumes-from parameters will bring together multiple data volumes from +Multiple --volumes-from parameters will bring together multiple data volumes from multiple containers. And it's possible to mount the volumes that came from the DATA container in yet another container via the fedora-container1 intermidiery container, allowing to abstract the actual data source from users of that data: diff --git a/contrib/man/old-man/docker-run.1 b/contrib/man/old-man/docker-run.1 index fd449374e..ae0295943 100644 --- a/contrib/man/old-man/docker-run.1 +++ b/contrib/man/old-man/docker-run.1 @@ -245,7 +245,7 @@ docker run --volumes-from=data --name=fedora-container1 -i -t fedora bash .RE .sp .TP -Multiple -volumes-from parameters will bring together multiple data volumes from multiple containers. And it's possible to mount the volumes that came from the DATA container in yet another container via the fedora-container1 intermidiery container, allowing to abstract the actual data source from users of that data: +Multiple --volumes-from parameters will bring together multiple data volumes from multiple containers. And it's possible to mount the volumes that came from the DATA container in yet another container via the fedora-container1 intermidiery container, allowing to abstract the actual data source from users of that data: .sp .RS docker run --volumes-from=fedora-container1 --name=fedora-container2 -i -t fedora bash diff --git a/docs/sources/articles/runmetrics.md b/docs/sources/articles/runmetrics.md index 15f53fb0d..bf4fe21c4 100644 --- a/docs/sources/articles/runmetrics.md +++ b/docs/sources/articles/runmetrics.md @@ -50,7 +50,7 @@ For Docker containers using cgroups, the container name will be the full ID or long ID of the container. If a container shows up as ae836c95b4c3 in `docker ps`, its long ID might be something like `ae836c95b4c3c9e9179e0e91015512da89fdec91612f63cebae57df9a5444c79`. You can -look it up with `docker inspect` or `docker ps -notrunc`. +look it up with `docker inspect` or `docker ps --no-trunc`. Putting everything together to look at the memory metrics for a Docker container, take a look at `/sys/fs/cgroup/memory/lxc//`. diff --git a/docs/sources/examples/postgresql_service.md b/docs/sources/examples/postgresql_service.md index 14d9e647a..6f0a3e6bb 100644 --- a/docs/sources/examples/postgresql_service.md +++ b/docs/sources/examples/postgresql_service.md @@ -84,7 +84,7 @@ Build an image from the Dockerfile assign it a name. And run the PostgreSQL server container (in the foreground): - $ sudo docker run -rm -P -name pg_test eg_postgresql + $ sudo docker run --rm -P --name pg_test eg_postgresql There are 2 ways to connect to the PostgreSQL server. We can use [*Link Containers*](/use/working_with_links_names/#working-with-links-names), @@ -101,7 +101,7 @@ Containers can be linked to another container's ports directly using `docker run`. This will set a number of environment variables that can then be used to connect: - $ sudo docker run -rm -t -i -link pg_test:pg eg_postgresql bash + $ sudo docker run --rm -t -i --link pg_test:pg eg_postgresql bash postgres@7ef98b1b7243:/$ psql -h $PG_PORT_5432_TCP_ADDR -p $PG_PORT_5432_TCP_PORT -d docker -U docker --password @@ -143,7 +143,7 @@ prompt, you can create a table and populate it. You can use the defined volumes to inspect the PostgreSQL log files and to backup your configuration and data: - $ docker run -rm --volumes-from pg_test -t -i busybox sh + $ docker run --rm --volumes-from pg_test -t -i busybox sh / # ls bin etc lib linuxrc mnt proc run sys usr diff --git a/docs/sources/examples/python_web_app.md b/docs/sources/examples/python_web_app.md index e761003a9..f4b76d061 100644 --- a/docs/sources/examples/python_web_app.md +++ b/docs/sources/examples/python_web_app.md @@ -51,7 +51,7 @@ the `$URL` variable. The container is given a name While this example is simple, you could run any number of interactive commands, try things out, and then exit when you're done. - $ sudo docker run -i -t -name pybuilder_run shykes/pybuilder bash + $ sudo docker run -i -t --name pybuilder_run shykes/pybuilder bash $$ URL=http://github.com/shykes/helloflask/archive/master.tar.gz $$ /usr/local/bin/buildapp $URL diff --git a/docs/sources/use/ambassador_pattern_linking.md b/docs/sources/use/ambassador_pattern_linking.md index 01962f19a..755fa4dc9 100644 --- a/docs/sources/use/ambassador_pattern_linking.md +++ b/docs/sources/use/ambassador_pattern_linking.md @@ -34,23 +34,23 @@ controlled entirely from the `docker run` parameters. Start actual Redis server on one Docker host - big-server $ docker run -d -name redis crosbymichael/redis + big-server $ docker run -d --name redis crosbymichael/redis Then add an ambassador linked to the Redis server, mapping a port to the outside world - big-server $ docker run -d -link redis:redis -name redis_ambassador -p 6379:6379 svendowideit/ambassador + big-server $ docker run -d --link redis:redis --name redis_ambassador -p 6379:6379 svendowideit/ambassador On the other host, you can set up another ambassador setting environment variables for each remote port we want to proxy to the `big-server` - client-server $ docker run -d -name redis_ambassador -expose 6379 -e REDIS_PORT_6379_TCP=tcp://192.168.1.52:6379 svendowideit/ambassador + client-server $ docker run -d --name redis_ambassador --expose 6379 -e REDIS_PORT_6379_TCP=tcp://192.168.1.52:6379 svendowideit/ambassador Then on the `client-server` host, you can use a Redis client container to talk to the remote Redis server, just by linking to the local Redis ambassador. - client-server $ docker run -i -t -rm -link redis_ambassador:redis relateiq/redis-cli + client-server $ docker run -i -t --rm --link redis_ambassador:redis relateiq/redis-cli redis 172.17.0.160:6379> ping PONG @@ -62,19 +62,19 @@ does automatically (with a tiny amount of `sed`) On the Docker host (192.168.1.52) that Redis will run on: # start actual redis server - $ docker run -d -name redis crosbymichael/redis + $ docker run -d --name redis crosbymichael/redis # get a redis-cli container for connection testing $ docker pull relateiq/redis-cli # test the redis server by talking to it directly - $ docker run -t -i -rm -link redis:redis relateiq/redis-cli + $ docker run -t -i --rm --link redis:redis relateiq/redis-cli redis 172.17.0.136:6379> ping PONG ^D # add redis ambassador - $ docker run -t -i -link redis:redis -name redis_ambassador -p 6379:6379 busybox sh + $ docker run -t -i --link redis:redis --name redis_ambassador -p 6379:6379 busybox sh In the `redis_ambassador` container, you can see the linked Redis containers `env`: @@ -98,7 +98,7 @@ to the world (via the `-p 6379:6379` port mapping): $ docker rm redis_ambassador $ sudo ./contrib/mkimage-unittest.sh - $ docker run -t -i -link redis:redis -name redis_ambassador -p 6379:6379 docker-ut sh + $ docker run -t -i --link redis:redis --name redis_ambassador -p 6379:6379 docker-ut sh $ socat TCP4-LISTEN:6379,fork,reuseaddr TCP4:172.17.0.136:6379 @@ -107,14 +107,14 @@ Now ping the Redis server via the ambassador: Now go to a different server: $ sudo ./contrib/mkimage-unittest.sh - $ docker run -t -i -expose 6379 -name redis_ambassador docker-ut sh + $ docker run -t -i --expose 6379 --name redis_ambassador docker-ut sh $ socat TCP4-LISTEN:6379,fork,reuseaddr TCP4:192.168.1.52:6379 And get the `redis-cli` image so we can talk over the ambassador bridge. $ docker pull relateiq/redis-cli - $ docker run -i -t -rm -link redis_ambassador:redis relateiq/redis-cli + $ docker run -i -t --rm --link redis_ambassador:redis relateiq/redis-cli redis 172.17.0.160:6379> ping PONG @@ -139,9 +139,9 @@ case `192.168.1.52:6379`. # docker build -t SvenDowideit/ambassador . # docker tag SvenDowideit/ambassador ambassador # then to run it (on the host that has the real backend on it) - # docker run -t -i -link redis:redis -name redis_ambassador -p 6379:6379 ambassador + # docker run -t -i --link redis:redis --name redis_ambassador -p 6379:6379 ambassador # on the remote host, you can set up another ambassador - # docker run -t -i -name redis_ambassador -expose 6379 sh + # docker run -t -i --name redis_ambassador --expose 6379 sh FROM docker-ut MAINTAINER SvenDowideit@home.org.au diff --git a/docs/sources/use/working_with_volumes.md b/docs/sources/use/working_with_volumes.md index f6d1db04f..4c0a46ff1 100644 --- a/docs/sources/use/working_with_volumes.md +++ b/docs/sources/use/working_with_volumes.md @@ -47,7 +47,7 @@ Or, you can use the `VOLUME` instruction in a `Dockerfile` to add one or more new volumes to any container created from that image: # BUILD-USING: $ docker build -t data . - # RUN-USING: $ docker run -name DATA data + # RUN-USING: $ docker run --name DATA data FROM busybox VOLUME ["/var/volume1", "/var/volume2"] CMD ["/bin/true"] @@ -62,11 +62,11 @@ it. Create a named container with volumes to share (`/var/volume1` and `/var/volume2`): - $ docker run -v /var/volume1 -v /var/volume2 -name DATA busybox true + $ docker run -v /var/volume1 -v /var/volume2 --name DATA busybox true Then mount those data volumes into your application containers: - $ docker run -t -i -rm -volumes-from DATA -name client1 ubuntu bash + $ docker run -t -i --rm --from DATA --name client1 ubuntu bash You can use multiple `-volumes-from` parameters to bring together multiple data volumes from multiple containers. @@ -75,7 +75,7 @@ Interestingly, you can mount the volumes that came from the `DATA` container in yet another container via the `client1` middleman container: - $ docker run -t -i -rm -volumes-from client1 -name client2 ubuntu bash + $ docker run -t -i --rm --volumes-from client1 --name client2 ubuntu bash This allows you to abstract the actual data source from users of that data, similar to [*Ambassador Pattern Linking*]( @@ -130,7 +130,7 @@ You cannot back up volumes using `docker export`, `docker save` and `--volumes-from` to start a new container that can access the data-container's volume. For example: - $ sudo docker run -rm --volumes-from DATA -v $(pwd):/backup busybox tar cvf /backup/backup.tar /data + $ sudo docker run --rm --volumes-from DATA -v $(pwd):/backup busybox tar cvf /backup/backup.tar /data - `-rm`: remove the container when it exits @@ -147,13 +147,13 @@ Then to restore to the same container, or another that you've made elsewhere: # create a new data container - $ sudo docker run -v /data -name DATA2 busybox true + $ sudo docker run -v /data --name DATA2 busybox true # untar the backup files into the new container᾿s data volume - $ sudo docker run -rm --volumes-from DATA2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar + $ sudo docker run --rm --volumes-from DATA2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar data/ data/sven.txt # compare to the original container - $ sudo docker run -rm --volumes-from DATA -v `pwd`:/backup busybox ls /data + $ sudo docker run --rm --volumes-from DATA -v `pwd`:/backup busybox ls /data sven.txt You can use the basic techniques above to automate backup, migration and diff --git a/docs/sources/use/workingwithrepository.md b/docs/sources/use/workingwithrepository.md index 88c3f6c44..b78d9bdc0 100644 --- a/docs/sources/use/workingwithrepository.md +++ b/docs/sources/use/workingwithrepository.md @@ -73,7 +73,7 @@ user name or description: Search the docker index for images - -notrunc=false: Don᾿t truncate output + --no-trunc=false: Don᾿t truncate output $ sudo docker search centos Found 25 results matching your query ("centos") NAME DESCRIPTION From 7e42505083cca90d445eb8a9d7d4a4bf9ffb35a4 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 1 May 2014 15:38:44 -0600 Subject: [PATCH 205/400] Add new consolidated mkimage scripts These new scripts are streamlined such that, for example, "contrib/mkimage/debootstrap" is _only_ responsible for filling a directory with the results of running debootstrap, and it can accept any arbitrary arguments. Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- contrib/mkimage.sh | 105 ++++++++++++++++++++++ contrib/mkimage/.febootstrap-minimize | 28 ++++++ contrib/mkimage/busybox-static | 34 +++++++ contrib/mkimage/debootstrap | 125 ++++++++++++++++++++++++++ contrib/mkimage/rinse | 25 ++++++ 5 files changed, 317 insertions(+) create mode 100755 contrib/mkimage.sh create mode 100755 contrib/mkimage/.febootstrap-minimize create mode 100755 contrib/mkimage/busybox-static create mode 100755 contrib/mkimage/debootstrap create mode 100755 contrib/mkimage/rinse diff --git a/contrib/mkimage.sh b/contrib/mkimage.sh new file mode 100755 index 000000000..db4815c20 --- /dev/null +++ b/contrib/mkimage.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +set -e + +mkimg="$(basename "$0")" + +usage() { + echo >&2 "usage: $mkimg [-d dir] [-t tag] script [script-args]" + echo >&2 " ie: $mkimg -t someuser/debian debootstrap --variant=minbase jessie" + echo >&2 " $mkimg -t someuser/ubuntu debootstrap --include=ubuntu-minimal trusty" + echo >&2 " $mkimg -t someuser/busybox busybox-static" + echo >&2 " $mkimg -t someuser/centos:5 rinse --distribution centos-5" + exit 1 +} + +scriptDir="$(dirname "$(readlink -f "$BASH_SOURCE")")/mkimage" + +optTemp=$(getopt --options '+d:t:h' --longoptions 'dir:,tag:,help' --name "$mkimg" -- "$@") +eval set -- "$optTemp" +unset optTemp + +dir= +tag= +while true; do + case "$1" in + -d|--dir) dir="$2" ; shift 2 ;; + -t|--tag) tag="$2" ; shift 2 ;; + -h|--help) usage ;; + --) shift ; break ;; + esac +done + +script="$1" +[ "$script" ] || usage +shift + +if [ ! -x "$scriptDir/$script" ]; then + echo >&2 "error: $script does not exist or is not executable" + echo >&2 " see $scriptDir for possible scripts" + exit 1 +fi + +# don't mistake common scripts like .febootstrap-minimize as image-creators +if [[ "$script" == .* ]]; then + echo >&2 "error: $script is a script helper, not a script" + echo >&2 " see $scriptDir for possible scripts" + exit 1 +fi + +delDir= +if [ -z "$dir" ]; then + dir="$(mktemp -d ${TMPDIR:-/tmp}/docker-mkimage.XXXXXXXXXX)" + delDir=1 +fi + +rootfsDir="$dir/rootfs" +( set -x; mkdir -p "$rootfsDir" ) + +# pass all remaining arguments to $script +"$scriptDir/$script" "$rootfsDir" "$@" + +# Docker mounts tmpfs at /dev and procfs at /proc so we can remove them +rm -rf "$rootfsDir/dev" "$rootfsDir/proc" +mkdir -p "$rootfsDir/dev" "$rootfsDir/proc" + +# make sure /etc/resolv.conf has something useful in it +mkdir -p "$rootfsDir/etc" +cat > "$rootfsDir/etc/resolv.conf" <<'EOF' +nameserver 8.8.8.8 +nameserver 8.8.4.4 +EOF + +tarFile="$dir/rootfs.tar.xz" +touch "$tarFile" + +( + set -x + tar --numeric-owner -caf "$tarFile" -C "$rootfsDir" --transform='s,^./,,' . +) + +echo >&2 "+ cat > '$dir/Dockerfile'" +cat > "$dir/Dockerfile" <<'EOF' +FROM scratch +ADD rootfs.tar.xz / +EOF + +# if our generated image has a decent shell, let's set a default command +for shell in /bin/bash /usr/bin/fish /usr/bin/zsh /bin/sh; do + if [ -x "$rootfsDir/$shell" ]; then + ( set -x; echo 'CMD ["'"$shell"'"]' >> "$dir/Dockerfile" ) + break + fi +done + +( set -x; rm -rf "$rootfsDir" ) + +if [ "$tag" ]; then + ( set -x; docker build -t "$tag" "$dir" ) +elif [ "$delDir" ]; then + # if we didn't specify a tag and we're going to delete our dir, let's just build an untagged image so that we did _something_ + ( set -x; docker build "$dir" ) +fi + +if [ "$delDir" ]; then + ( set -x; rm -rf "$dir" ) +fi diff --git a/contrib/mkimage/.febootstrap-minimize b/contrib/mkimage/.febootstrap-minimize new file mode 100755 index 000000000..7dab4eb8b --- /dev/null +++ b/contrib/mkimage/.febootstrap-minimize @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -e + +rootfsDir="$1" +shift + +( + cd "$rootfsDir" + + # effectively: febootstrap-minimize --keep-zoneinfo --keep-rpmdb --keep-services "$target" + # locales + rm -rf usr/{{lib,share}/locale,{lib,lib64}/gconv,bin/localedef,sbin/build-locale-archive} + # docs + rm -rf usr/share/{man,doc,info,gnome/help} + # cracklib + #rm -rf usr/share/cracklib + # i18n + rm -rf usr/share/i18n + # yum cache + rm -rf var/cache/yum + mkdir -p --mode=0755 var/cache/yum + # sln + rm -rf sbin/sln + # ldconfig + #rm -rf sbin/ldconfig + rm -rf etc/ld.so.cache var/cache/ldconfig + mkdir -p --mode=0755 var/cache/ldconfig +) diff --git a/contrib/mkimage/busybox-static b/contrib/mkimage/busybox-static new file mode 100755 index 000000000..e15322b49 --- /dev/null +++ b/contrib/mkimage/busybox-static @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -e + +rootfsDir="$1" +shift + +busybox="$(which busybox 2>/dev/null || true)" +if [ -z "$busybox" ]; then + echo >&2 'error: busybox: not found' + echo >&2 ' install it with your distribution "busybox-static" package' + exit 1 +fi +if ! ldd "$busybox" 2>&1 | grep -q 'not a dynamic executable'; then + echo >&2 "error: '$busybox' appears to be a dynamic executable" + echo >&2 ' you should install your distribution "busybox-static" package instead' + exit 1 +fi + +mkdir -p "$rootfsDir/bin" +rm -f "$rootfsDir/bin/busybox" # just in case +cp "$busybox" "$rootfsDir/bin/busybox" + +( + cd "$rootfsDir" + + IFS=$'\n' + modules=( $(bin/busybox --list-modules) ) + unset IFS + + for module in "${modules[@]}"; do + mkdir -p "$(dirname "$module")" + ln -sf /bin/busybox "$module" + done +) diff --git a/contrib/mkimage/debootstrap b/contrib/mkimage/debootstrap new file mode 100755 index 000000000..fe13ccde9 --- /dev/null +++ b/contrib/mkimage/debootstrap @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +set -e + +rootfsDir="$1" +shift + +# we have to do a little fancy footwork to make sure "rootfsDir" becomes the second non-option argument to debootstrap + +before=() +while [ $# -gt 0 ] && [[ "$1" == -* ]]; do + before+=( "$1" ) + shift +done + +suite="$1" +shift + +( + set -x + debootstrap "${before[@]}" "$suite" "$rootfsDir" "$@" +) + +# now for some Docker-specific tweaks + +# prevent init scripts from running during install/update +echo >&2 "+ cat > '$rootfsDir/usr/sbin/policy-rc.d'" +cat > "$rootfsDir/usr/sbin/policy-rc.d" <<'EOF' +#!/bin/sh +exit 101 +EOF +chmod +x "$rootfsDir/usr/sbin/policy-rc.d" + +# prevent upstart scripts from running during install/update +( + set -x + chroot "$rootfsDir" dpkg-divert --local --rename --add /sbin/initctl + ln -sf /bin/true "$rootfsDir/sbin/initctl" +) + +# shrink the image, since apt makes us fat (wheezy: ~157.5MB vs ~120MB) +( set -x; chroot "$rootfsDir" apt-get clean ) + +# Ubuntu 10.04 sucks... :) +if strings "$rootfsDir/usr/bin/dpkg" | grep -q unsafe-io; then + # force dpkg not to call sync() after package extraction (speeding up installs) + echo >&2 "+ echo force-unsafe-io > '$rootfsDir/etc/dpkg/dpkg.cfg.d/docker-apt-speedup'" + echo 'force-unsafe-io' > "$rootfsDir/etc/dpkg/dpkg.cfg.d/docker-apt-speedup" +fi + +if [ -d /etc/apt/apt.conf.d ]; then + # _keep_ us lean by effectively running "apt-get clean" after every install + aptGetClean='"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true";' + echo >&2 "+ cat > '$rootfsDir/etc/apt/apt.conf.d/docker-clean'" + cat > "$rootfsDir/etc/apt/apt.conf.d/docker-clean" <<-EOF + DPkg::Post-Invoke { ${aptGetClean} }; + APT::Update::Post-Invoke { ${aptGetClean} }; + + Dir::Cache::pkgcache ""; + Dir::Cache::srcpkgcache ""; + EOF + + # remove apt-cache translations for fast "apt-get update" + echo >&2 "+ cat > '$rootfsDir/etc/apt/apt.conf.d/docker-no-languages'" + echo 'Acquire::Languages "none";' > "$rootfsDir/etc/apt/apt.conf.d/docker-no-languages" +fi + +if [ -z "$DONT_TOUCH_SOURCES_LIST" ]; then + # tweak sources.list, where appropriate + lsbDist= + if [ -z "$lsbDist" -a -r "$rootfsDir/etc/os-release" ]; then + lsbDist="$(. "$rootfsDir/etc/os-release" && echo "$ID")" + fi + if [ -z "$lsbDist" -a -r "$rootfsDir/etc/lsb-release" ]; then + lsbDist="$(. "$rootfsDir/etc/lsb-release" && echo "$DISTRIB_ID")" + fi + if [ -z "$lsbDist" -a -r "$rootfsDir/etc/debian_version" ]; then + lsbDist='Debian' + fi + case "$lsbDist" in + debian|Debian) + # updates and security! + if [ "$suite" != 'sid' -a "$suite" != 'unstable' ]; then + ( + set -x + sed -i "p; s/ $suite main$/ ${suite}-updates main/" "$rootfsDir/etc/apt/sources.list" + echo "deb http://security.debian.org $suite/updates main" >> "$rootfsDir/etc/apt/sources.list" + ) + fi + ;; + ubuntu|Ubuntu) + # add the universe, updates, and security repositories + ( + set -x + sed -i " + s/ $suite main$/ $suite main universe/; p; + s/ $suite main/ ${suite}-updates main/; p; + s/ $suite-updates main/ ${suite}-security main/ + " "$rootfsDir/etc/apt/sources.list" + ) + ;; + tanglu|Tanglu) + # add the updates repository + if [ "$suite" != 'devel' ]; then + ( + set -x + sed -i "p; s/ $suite main$/ ${suite}-updates main/" "$rootfsDir/etc/apt/sources.list" + ) + fi + ;; + steamos|SteamOS) + # add contrib and non-free + ( + set -x + sed -i "s/ $suite main$/ $suite main contrib non-free/" "$rootfsDir/etc/apt/sources.list" + ) + ;; + esac +fi + +# make sure we're fully up-to-date, too +( + set -x + chroot "$rootfsDir" apt-get update + chroot "$rootfsDir" apt-get dist-upgrade -y +) diff --git a/contrib/mkimage/rinse b/contrib/mkimage/rinse new file mode 100755 index 000000000..75eb4f0d9 --- /dev/null +++ b/contrib/mkimage/rinse @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -e + +rootfsDir="$1" +shift + +# specifying --arch below is safe because "$@" can override it and the "latest" one wins :) + +( + set -x + rinse --directory "$rootfsDir" --arch amd64 "$@" +) + +"$(dirname "$BASH_SOURCE")/.febootstrap-minimize" "$rootfsDir" + +if [ -d "$rootfsDir/etc/sysconfig" ]; then + # allow networking init scripts inside the container to work without extra steps + echo 'NETWORKING=yes' > "$rootfsDir/etc/sysconfig/network" +fi + +# make sure we're fully up-to-date, too +( + set -x + chroot "$rootfsDir" yum update -y +) From 51f707cf9dfb2dbe31cfd0cd92b922ca5d98e842 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 21 May 2014 09:58:11 -0600 Subject: [PATCH 206/400] Add deprecation warnings to the mkimage scripts that are have consolidated mkimage implementations Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- contrib/mkimage-busybox.sh | 4 ++++ contrib/mkimage-debootstrap.sh | 4 ++++ contrib/mkimage-rinse.sh | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/contrib/mkimage-busybox.sh b/contrib/mkimage-busybox.sh index c1bb88c35..cbaa56783 100755 --- a/contrib/mkimage-busybox.sh +++ b/contrib/mkimage-busybox.sh @@ -2,6 +2,10 @@ # Generate a very minimal filesystem based on busybox-static, # and load it into the local docker under the name "busybox". +echo >&2 +echo >&2 'warning: this script is deprecated - see mkimage.sh and mkimage/busybox-static' +echo >&2 + BUSYBOX=$(which busybox) [ "$BUSYBOX" ] || { echo "Sorry, I could not locate busybox." diff --git a/contrib/mkimage-debootstrap.sh b/contrib/mkimage-debootstrap.sh index 613066e16..808f39354 100755 --- a/contrib/mkimage-debootstrap.sh +++ b/contrib/mkimage-debootstrap.sh @@ -1,6 +1,10 @@ #!/usr/bin/env bash set -e +echo >&2 +echo >&2 'warning: this script is deprecated - see mkimage.sh and mkimage/debootstrap' +echo >&2 + variant='minbase' include='iproute,iputils-ping' arch='amd64' # intentionally undocumented for now diff --git a/contrib/mkimage-rinse.sh b/contrib/mkimage-rinse.sh index dfe9999d9..0692ae179 100755 --- a/contrib/mkimage-rinse.sh +++ b/contrib/mkimage-rinse.sh @@ -8,6 +8,10 @@ set -e +echo >&2 +echo >&2 'warning: this script is deprecated - see mkimage.sh and mkimage/rinse' +echo >&2 + repo="$1" distro="$2" mirror="$3" From f0489ce3a9a4023265bbbd5e9cb333e95f950088 Mon Sep 17 00:00:00 2001 From: Erik Hollensbe Date: Tue, 20 May 2014 15:05:57 -0700 Subject: [PATCH 207/400] portallocator: rewrite to simplify, removes race condition Docker-DCO-1.1-Signed-off-by: Erik Hollensbe (github: erikh) --- .../portallocator/portallocator.go | 183 ++++++------------ 1 file changed, 62 insertions(+), 121 deletions(-) diff --git a/daemon/networkdriver/portallocator/portallocator.go b/daemon/networkdriver/portallocator/portallocator.go index 9ecd44711..251ab9447 100644 --- a/daemon/networkdriver/portallocator/portallocator.go +++ b/daemon/networkdriver/portallocator/portallocator.go @@ -2,21 +2,21 @@ package portallocator import ( "errors" - "github.com/dotcloud/docker/pkg/collections" "net" "sync" ) +type ( + portMap map[int]bool + protocolMap map[string]portMap + ipMapping map[string]protocolMap +) + const ( BeginPortRange = 49153 EndPortRange = 65535 ) -type ( - portMappings map[string]*collections.OrderedIntSet - ipMapping map[string]portMappings -) - var ( ErrAllPortsAllocated = errors.New("all ports are allocated") ErrPortAlreadyAllocated = errors.New("port has already been allocated") @@ -24,165 +24,106 @@ var ( ) var ( - currentDynamicPort = map[string]int{ - "tcp": BeginPortRange - 1, - "udp": BeginPortRange - 1, - } - defaultIP = net.ParseIP("0.0.0.0") - defaultAllocatedPorts = portMappings{} - otherAllocatedPorts = ipMapping{} - lock = sync.Mutex{} + mutex sync.Mutex + + defaultIP = net.ParseIP("0.0.0.0") + globalMap = ipMapping{} ) -func init() { - defaultAllocatedPorts["tcp"] = collections.NewOrderedIntSet() - defaultAllocatedPorts["udp"] = collections.NewOrderedIntSet() -} - -// RequestPort returns an available port if the port is 0 -// If the provided port is not 0 then it will be checked if -// it is available for allocation func RequestPort(ip net.IP, proto string, port int) (int, error) { - lock.Lock() - defer lock.Unlock() + mutex.Lock() + defer mutex.Unlock() - if err := validateProtocol(proto); err != nil { + if err := validateProto(proto); err != nil { return 0, err } - // If the user requested a specific port to be allocated + ip = getDefault(ip) + + mapping := getOrCreate(ip) + if port > 0 { - if err := registerSetPort(ip, proto, port); err != nil { + if !mapping[proto][port] { + mapping[proto][port] = true + return port, nil + } else { + return 0, ErrPortAlreadyAllocated + } + } else { + port, err := findPort(ip, proto) + + if err != nil { return 0, err } + return port, nil } - return registerDynamicPort(ip, proto) } -// ReleasePort will return the provided port back into the -// pool for reuse func ReleasePort(ip net.IP, proto string, port int) error { - lock.Lock() - defer lock.Unlock() + mutex.Lock() + defer mutex.Unlock() - if err := validateProtocol(proto); err != nil { - return err - } + ip = getDefault(ip) - allocated := defaultAllocatedPorts[proto] - allocated.Remove(port) + mapping := getOrCreate(ip) + delete(mapping[proto], port) - if !equalsDefault(ip) { - registerIP(ip) - - // Remove the port for the specific ip address - allocated = otherAllocatedPorts[ip.String()][proto] - allocated.Remove(port) - } return nil } func ReleaseAll() error { - lock.Lock() - defer lock.Unlock() + mutex.Lock() + defer mutex.Unlock() - currentDynamicPort["tcp"] = BeginPortRange - 1 - currentDynamicPort["udp"] = BeginPortRange - 1 - - defaultAllocatedPorts = portMappings{} - defaultAllocatedPorts["tcp"] = collections.NewOrderedIntSet() - defaultAllocatedPorts["udp"] = collections.NewOrderedIntSet() - - otherAllocatedPorts = ipMapping{} + globalMap = ipMapping{} return nil } -func registerDynamicPort(ip net.IP, proto string) (int, error) { +func getOrCreate(ip net.IP) protocolMap { + ipstr := ip.String() - if !equalsDefault(ip) { - registerIP(ip) - - ipAllocated := otherAllocatedPorts[ip.String()][proto] - - port, err := findNextPort(proto, ipAllocated) - if err != nil { - return 0, err + if _, ok := globalMap[ipstr]; !ok { + globalMap[ipstr] = protocolMap{ + "tcp": portMap{}, + "udp": portMap{}, } - ipAllocated.Push(port) - return port, nil - - } else { - - allocated := defaultAllocatedPorts[proto] - - port, err := findNextPort(proto, allocated) - if err != nil { - return 0, err - } - allocated.Push(port) - return port, nil - } -} - -func registerSetPort(ip net.IP, proto string, port int) error { - allocated := defaultAllocatedPorts[proto] - if allocated.Exists(port) { - return ErrPortAlreadyAllocated } - if !equalsDefault(ip) { - registerIP(ip) - - ipAllocated := otherAllocatedPorts[ip.String()][proto] - if ipAllocated.Exists(port) { - return ErrPortAlreadyAllocated - } - ipAllocated.Push(port) - } else { - allocated.Push(port) - } - return nil + return globalMap[ipstr] } -func equalsDefault(ip net.IP) bool { - return ip == nil || ip.Equal(defaultIP) -} +func findPort(ip net.IP, proto string) (int, error) { + port := BeginPortRange -func findNextPort(proto string, allocated *collections.OrderedIntSet) (int, error) { - port := nextPort(proto) - startSearchPort := port - for allocated.Exists(port) { - port = nextPort(proto) - if startSearchPort == port { + mapping := getOrCreate(ip) + + for mapping[proto][port] { + port++ + + if port > EndPortRange { return 0, ErrAllPortsAllocated } } + + mapping[proto][port] = true + return port, nil } -func nextPort(proto string) int { - c := currentDynamicPort[proto] + 1 - if c > EndPortRange { - c = BeginPortRange +func getDefault(ip net.IP) net.IP { + if ip == nil { + return defaultIP } - currentDynamicPort[proto] = c - return c + + return ip } -func registerIP(ip net.IP) { - if _, exists := otherAllocatedPorts[ip.String()]; !exists { - otherAllocatedPorts[ip.String()] = portMappings{ - "tcp": collections.NewOrderedIntSet(), - "udp": collections.NewOrderedIntSet(), - } - } -} - -func validateProtocol(proto string) error { - if _, exists := defaultAllocatedPorts[proto]; !exists { +func validateProto(proto string) error { + if proto != "tcp" && proto != "udp" { return ErrUnknownProtocol } + return nil } From 91ba3379312a74132e2578d167c9e36eeb889525 Mon Sep 17 00:00:00 2001 From: Erik Hollensbe Date: Tue, 20 May 2014 21:19:55 -0700 Subject: [PATCH 208/400] bridge: retry any proxy set up failures until we get a successful port, defeats a race condition Docker-DCO-1.1-Signed-off-by: Erik Hollensbe (github: erikh) --- daemon/networkdriver/bridge/driver.go | 46 ++++++++++++++++++--------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index c64aa423d..a14941a8f 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -380,7 +380,7 @@ func AllocatePort(job *engine.Job) engine.Status { ip = defaultBindingIP id = job.Args[0] hostIP = job.Getenv("HostIP") - hostPort = job.GetenvInt("HostPort") + origHostPort = job.GetenvInt("HostPort") containerPort = job.GetenvInt("ContainerPort") proto = job.Getenv("Proto") network = currentInterfaces[id] @@ -390,29 +390,45 @@ func AllocatePort(job *engine.Job) engine.Status { ip = net.ParseIP(hostIP) } - // host ip, proto, and host port - hostPort, err = portallocator.RequestPort(ip, proto, hostPort) - if err != nil { - return job.Error(err) - } - var ( + hostPort int container net.Addr host net.Addr ) - if proto == "tcp" { - host = &net.TCPAddr{IP: ip, Port: hostPort} - container = &net.TCPAddr{IP: network.IP, Port: containerPort} - } else { - host = &net.UDPAddr{IP: ip, Port: hostPort} - container = &net.UDPAddr{IP: network.IP, Port: containerPort} + /* + Try up to 10 times to get a port that's not already allocated. + + In the event of failure to bind, return the error that portmapper.Map + yields. + */ + for i := 0; i < 10; i++ { + // host ip, proto, and host port + hostPort, err = portallocator.RequestPort(ip, proto, origHostPort) + + if err != nil { + return job.Error(err) + } + + if proto == "tcp" { + host = &net.TCPAddr{IP: ip, Port: hostPort} + container = &net.TCPAddr{IP: network.IP, Port: containerPort} + } else { + host = &net.UDPAddr{IP: ip, Port: hostPort} + container = &net.UDPAddr{IP: network.IP, Port: containerPort} + } + + if err = portmapper.Map(container, ip, hostPort); err == nil { + break + } + + job.Logf("Failed to bind %s:%d for container address %s:%d. Trying another port.", ip.String(), hostPort, network.IP.String(), containerPort) } - if err := portmapper.Map(container, ip, hostPort); err != nil { - portallocator.ReleasePort(ip, proto, hostPort) + if err != nil { return job.Error(err) } + network.PortMappings = append(network.PortMappings, host) out := engine.Env{} From 56a53c72d285b3f9fa6b34dcb81c9720a1974bde Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Tue, 20 May 2014 13:39:46 -0700 Subject: [PATCH 209/400] remove chmod 755: fixes #5941 Docker-DCO-1.1-Signed-off-by: Tibor Vass (github: tiborvass) --- docs/sources/reference/builder.md | 4 +++- .../build_tests/TestAdd/EtcToRoot/Dockerfile | 2 ++ .../TestAdd/SingleFileToRoot/Dockerfile | 2 +- .../build_tests/TestAdd/WholeDirToRoot/Dockerfile | 2 +- integration-cli/docker_cli_build_test.go | 15 +++++++++++++++ server/buildfile.go | 3 --- 6 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 integration-cli/build_tests/TestAdd/EtcToRoot/Dockerfile diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index c3ba93955..cd7f4eff8 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -235,7 +235,9 @@ being built (also called the *context* of the build) or a remote file URL. `` is the absolute path to which the source will be copied inside the destination container. -All new files and directories are created with mode 0755, uid and gid 0. +All new files and directories are created with a uid and gid of 0. + +In the case where `` is a remote file URL, the destination will have permissions 600. > **Note**: > If you build using STDIN (`docker build - < somefile`), there is no diff --git a/integration-cli/build_tests/TestAdd/EtcToRoot/Dockerfile b/integration-cli/build_tests/TestAdd/EtcToRoot/Dockerfile new file mode 100644 index 000000000..58c75b00f --- /dev/null +++ b/integration-cli/build_tests/TestAdd/EtcToRoot/Dockerfile @@ -0,0 +1,2 @@ +FROM scratch +ADD . / diff --git a/integration-cli/build_tests/TestAdd/SingleFileToRoot/Dockerfile b/integration-cli/build_tests/TestAdd/SingleFileToRoot/Dockerfile index e96201d85..561dbe9c5 100644 --- a/integration-cli/build_tests/TestAdd/SingleFileToRoot/Dockerfile +++ b/integration-cli/build_tests/TestAdd/SingleFileToRoot/Dockerfile @@ -5,5 +5,5 @@ RUN touch /exists RUN chown dockerio.dockerio /exists ADD test_file / RUN [ $(ls -l /test_file | awk '{print $3":"$4}') = 'root:root' ] -RUN [ $(ls -l /test_file | awk '{print $1}') = '-rwxr-xr-x' ] +RUN [ $(ls -l /test_file | awk '{print $1}') = '-rw-r--r--' ] RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] diff --git a/integration-cli/build_tests/TestAdd/WholeDirToRoot/Dockerfile b/integration-cli/build_tests/TestAdd/WholeDirToRoot/Dockerfile index 2f1097948..03e9ac0b1 100644 --- a/integration-cli/build_tests/TestAdd/WholeDirToRoot/Dockerfile +++ b/integration-cli/build_tests/TestAdd/WholeDirToRoot/Dockerfile @@ -7,5 +7,5 @@ ADD test_dir /test_dir RUN [ $(ls -l / | grep test_dir | awk '{print $3":"$4}') = 'root:root' ] RUN [ $(ls -l / | grep test_dir | awk '{print $1}') = 'drwxr-xr-x' ] RUN [ $(ls -l /test_dir/test_file | awk '{print $3":"$4}') = 'root:root' ] -RUN [ $(ls -l /test_dir/test_file | awk '{print $1}') = '-rwxr-xr-x' ] +RUN [ $(ls -l /test_dir/test_file | awk '{print $1}') = '-rw-r--r--' ] RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 041b10d8b..455264d9f 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -152,6 +152,21 @@ func TestAddWholeDirToRoot(t *testing.T) { logDone("build - add whole directory to root") } +func TestAddEtcToRoot(t *testing.T) { + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestAdd") + buildCmd := exec.Command(dockerBinary, "build", "-t", "testaddimg", "EtcToRoot") + buildCmd.Dir = buildDirectory + out, exitCode, err := runCommandWithOutput(buildCmd) + errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) + + if err != nil || exitCode != 0 { + t.Fatal("failed to build the image") + } + + deleteImages("testaddimg") + logDone("build - add etc directory to root") +} + // Issue #5270 - ensure we throw a better error than "unexpected EOF" // when we can't access files in the context. func TestBuildWithInaccessibleFilesInContext(t *testing.T) { diff --git a/server/buildfile.go b/server/buildfile.go index b37053ac1..f71f94574 100644 --- a/server/buildfile.go +++ b/server/buildfile.go @@ -438,9 +438,6 @@ func (b *buildFile) addContext(container *daemon.Container, orig, dest string, r if err := os.Lchown(path, uid, gid); err != nil && !os.IsNotExist(err) { return err } - if err := os.Chmod(path, 0755); err != nil && !os.IsNotExist(err) { - return err - } return nil }) } From bb431a719008ae183179d9d71090bd1224d61480 Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Wed, 21 May 2014 11:29:11 -0700 Subject: [PATCH 210/400] for perms checking tests, create files from Go and remove them from git Docker-DCO-1.1-Signed-off-by: Tibor Vass (github: tiborvass) --- .../TestAdd/SingleFileToRoot/test_file | 0 .../TestAdd/WholeDirToRoot/test_dir/test_file | 0 integration-cli/docker_cli_build_test.go | 24 +++++++++++++++---- 3 files changed, 19 insertions(+), 5 deletions(-) delete mode 100644 integration-cli/build_tests/TestAdd/SingleFileToRoot/test_file delete mode 100644 integration-cli/build_tests/TestAdd/WholeDirToRoot/test_dir/test_file diff --git a/integration-cli/build_tests/TestAdd/SingleFileToRoot/test_file b/integration-cli/build_tests/TestAdd/SingleFileToRoot/test_file deleted file mode 100644 index e69de29bb..000000000 diff --git a/integration-cli/build_tests/TestAdd/WholeDirToRoot/test_dir/test_file b/integration-cli/build_tests/TestAdd/WholeDirToRoot/test_dir/test_file deleted file mode 100644 index e69de29bb..000000000 diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 455264d9f..ed61afe7c 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -57,8 +57,13 @@ func TestBuildSixtySteps(t *testing.T) { } func TestAddSingleFileToRoot(t *testing.T) { - buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestAdd") - buildCmd := exec.Command(dockerBinary, "build", "-t", "testaddimg", "SingleFileToRoot") + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestAdd", "SingleFileToRoot") + f, err := os.OpenFile(filepath.Join(buildDirectory, "test_file"), os.O_CREATE, 0644) + if err != nil { + t.Fatal(err) + } + f.Close() + buildCmd := exec.Command(dockerBinary, "build", "-t", "testaddimg", ".") buildCmd.Dir = buildDirectory out, exitCode, err := runCommandWithOutput(buildCmd) errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) @@ -137,8 +142,17 @@ func TestAddDirContentToExistDir(t *testing.T) { } func TestAddWholeDirToRoot(t *testing.T) { - buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestAdd") - buildCmd := exec.Command(dockerBinary, "build", "-t", "testaddimg", "WholeDirToRoot") + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestAdd", "WholeDirToRoot") + test_dir := filepath.Join(buildDirectory, "test_dir") + if err := os.MkdirAll(test_dir, 0755); err != nil { + t.Fatal(err) + } + f, err := os.OpenFile(filepath.Join(test_dir, "test_file"), os.O_CREATE, 0644) + if err != nil { + t.Fatal(err) + } + f.Close() + buildCmd := exec.Command(dockerBinary, "build", "-t", "testaddimg", ".") buildCmd.Dir = buildDirectory out, exitCode, err := runCommandWithOutput(buildCmd) errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) @@ -192,7 +206,7 @@ func TestBuildWithInaccessibleFilesInContext(t *testing.T) { // check if we've detected the failure before we started building if !strings.Contains(out, "no permission to read from ") { - t.Fatalf("output should've contained the string: no permission to read from ") + t.Fatalf("output should've contained the string: no permission to read from but contained: %s", out) } if !strings.Contains(out, "Error checking context is accessible") { From 8e967fe8028d8362fe3dfb293a8e07a959a4dd7f Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 21 May 2014 14:28:19 -0600 Subject: [PATCH 211/400] Revert "Always mount a /run tmpfs in the container" This reverts commit 905795ece624675abe2ec2622b0bbafdb9d7f44c. Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- daemon/execdriver/lxc/lxc_template.go | 1 - graph/graph.go | 1 - pkg/libcontainer/mount/init.go | 1 - 3 files changed, 3 deletions(-) diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index a16f8990e..d660df902 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -92,7 +92,6 @@ lxc.pivotdir = lxc_putold # We cannot mount them directly read-only, because that would prevent loading AppArmor profiles. lxc.mount.entry = proc {{escapeFstabSpaces $ROOTFS}}/proc proc nosuid,nodev,noexec 0 0 lxc.mount.entry = sysfs {{escapeFstabSpaces $ROOTFS}}/sys sysfs nosuid,nodev,noexec 0 0 -lxc.mount.entry = tmpfs {{escapeFstabSpaces $ROOTFS}}/run tmpfs nosuid,nodev,noexec 0 0 {{if .Tty}} lxc.mount.entry = {{.Console}} {{escapeFstabSpaces $ROOTFS}}/dev/console none bind,rw 0 0 diff --git a/graph/graph.go b/graph/graph.go index a5a433d90..649b39b12 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -257,7 +257,6 @@ func SetupInitLayer(initLayer string) error { "/dev/pts": "dir", "/dev/shm": "dir", "/proc": "dir", - "/run": "dir", "/sys": "dir", "/.dockerinit": "file", "/.dockerenv": "file", diff --git a/pkg/libcontainer/mount/init.go b/pkg/libcontainer/mount/init.go index c4148131a..338623d84 100644 --- a/pkg/libcontainer/mount/init.go +++ b/pkg/libcontainer/mount/init.go @@ -197,7 +197,6 @@ func newSystemMounts(rootfs, mountLabel string, mounts libcontainer.Mounts) []mo {source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags}, {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}, - {source: "tmpfs", path: filepath.Join(rootfs, "run"), device: "tmpfs", flags: defaultMountFlags}, } if len(mounts.OfType("devtmpfs")) == 1 { From 0056884090726f736af2594b09aaa44e373a84d1 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sun, 18 May 2014 18:52:41 +0200 Subject: [PATCH 212/400] Rewrite of the Introduction documentation 1. Re-aligns the introduction with the new product positioning. 2. Cleanup of some issues with language and formatting. 3. Makes the introduction leaner and meaner. 4. Responds to feedback from product. Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- docs/mkdocs.yml | 2 - docs/sources/index.md | 133 +++-- docs/sources/introduction/get-docker.md | 77 --- docs/sources/introduction/technology.md | 268 --------- .../introduction/understanding-docker.md | 543 ++++++++++-------- .../introduction/working-with-docker.md | 321 ++++------- 6 files changed, 479 insertions(+), 865 deletions(-) delete mode 100644 docs/sources/introduction/get-docker.md delete mode 100644 docs/sources/introduction/technology.md diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 32d085279..e89262cc6 100755 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -28,9 +28,7 @@ pages: - ['index.md', 'About', 'Docker'] - ['introduction/index.md', '**HIDDEN**'] - ['introduction/understanding-docker.md', 'About', 'Understanding Docker'] -- ['introduction/technology.md', 'About', 'The Technology'] - ['introduction/working-with-docker.md', 'About', 'Working with Docker'] -- ['introduction/get-docker.md', 'About', 'Get Docker'] # Installation: - ['installation/index.md', '**HIDDEN**'] diff --git a/docs/sources/index.md b/docs/sources/index.md index d58232156..7860a82f2 100644 --- a/docs/sources/index.md +++ b/docs/sources/index.md @@ -1,82 +1,99 @@ page_title: About Docker -page_description: Docker introduction home page +page_description: Introduction to Docker. page_keywords: docker, introduction, documentation, about, technology, understanding, Dockerfile # About Docker -*Secure And Portable Containers Made Easy* +**Develop, Ship and Run Any Application, Anywhere** ## Introduction -[**Docker**](https://www.docker.io) is a container based virtualization -framework. Unlike traditional virtualization Docker is fast, lightweight -and easy to use. Docker allows you to create containers holding -all the dependencies for an application. Each container is kept isolated -from any other, and nothing gets shared. +[**Docker**](https://www.docker.io) is a platform for developers and +sysadmins to develop, ship, and run applications. Docker consists of: -## Docker highlights +* The Docker Engine - our lightweight and powerful open source container + virtualization technology combined with a work flow to help you build + and containerize your applications. +* [Docker.io](https://index.docker.io) - our SAAS service that helps you + share and manage your applications stacks. - - **Containers provide sand-boxing:** - Applications run securely without outside access. - - **Docker allows simple portability:** - Containers are directories, they can be zipped and transported. - - **It all works fast:** - Starting a container is a very fast single process. - - **Docker is easy on the system resources (unlike VMs):** - No more than what each application needs. - - **Agnostic in its _essence_:** - Free of framework, language or platform dependencies. +Docker enables applications to be quickly assembled from components and +eliminates the friction when shipping code. We want to help you get code +from your desktop, tested and deployed into production as fast as +possible. -And most importantly: +## Why Docker? - - **Docker reduces complexity:** - Docker accepts commands *in plain English*, e.g. `docker run [..]`. +- **Faster delivery of your applications** + * We want to help your environment work better. Docker containers, + and the work flow that comes with them, helps your developers, + sysadmins, QA folks, and release engineers all work together to get code + into production and doing something useful. We've created a standard + container format that allows developers to care about their applications + inside containers and sysadmins and operators to care about running the + container. This creates a separation of duties that makes managing and + deploying code much easier and much more streamlined. + * We make it easy to build new containers and expose how each rapid + iterations and updates as well as visibility of changes. + container is built. This helps everyone in your organization understand + how an application works and how it is built. + * Docker containers are lightweight and fast! Containers have + sub-second launch times! With containers you can reduce the cycle + time in development, testing and deployment. + +- **Deploy and scale more easily** + * Docker containers run (almost!) everywhere. You can deploy your + containers on desktops, physical servers, virtual machines, into + data centers and to public and private cloud. + * As Docker runs on so many platforms it makes it easy to move your + appications around. You can easily move an application from a + testing environment into the cloud and back whenever you need. + * The lightweight containers Docker creates also making scaling and + down really fast and easy. If you need more containers you can + quickly launch them and then shut them down when you don't need them + anymore. + +- **Get higher density and run more workloads** + * Docker containers don't need a hypervisor so you can pack more of + them onto your hosts. This means you get more value out of every + server and can potentially reduce the money you spend on equipment and + licenses! + +- **Faster deployment makes for easier management** + * As Docker speeds up your work flow it makes it easier to make lots + of little changes instead of huge, big bang updates. Smaller + changes mean smaller risks and mean more uptime! ## About this guide -In this introduction we will take you on a tour and show you what -makes Docker tick. +First we'll show you [what makes Docker tick in our Understanding Docker +section](introduction/understanding-docker.md): -On the [**first page**](introduction/understanding-docker.md), which is -**_informative_**: - - - You will find information on Docker; - - And discover Docker's features. - - We will also compare Docker to virtual machines; + - You will find see how Docker works at a high level; + - The architecture of Docker; + - Discover Docker's features; + - See how Docker compares to virtual machines; - And see some common use cases. -> [Click here to go to Understanding Docker](introduction/understanding-docker.md). +> [Click here to go to the Understanding +> Docker section](introduction/understanding-docker.md). -The [**second page**](introduction/technology.md) has **_technical_** information on: +Next we get [**practical** with the Working with Docker +section](introduction/working-with-docker.md) and you can learn about: - - The architecture of Docker; - - The underlying technology, and; - - *How* Docker works. + - Docker on the command line; + - Get introduced to your first Docker commands; + - Get to know your way around the basics of Docker operation. -> [Click here to go to Understanding the Technology](introduction/technology.md). +> [Click here to go to the Working with +> Docker section](introduction/working-with-docker.md). -On the [**third page**](introduction/working-with-docker.md) we get **_practical_**. -There you can: - - - Learn about Docker's components (i.e. Containers, Images and the - Dockerfile); - - And get started working with them straight away. - -> [Click here to go to Working with Docker](introduction/working-with-docker.md). - -Finally, on the [**fourth**](introduction/get-docker.md) page, we go **_hands on_** -and see: - - - The installation instructions, and; - - How Docker makes some hard problems much, much easier. - -> [Click here to go to Get Docker](introduction/get-docker.md). +If you want to see how to install Docker you can jump to the +[installation](/installation/#installation) section. > **Note**: -> We know how valuable your time is. Therefore, the documentation is prepared -> in a way to allow anyone to start from any section need. Although we strongly -> recommend that you visit [Understanding Docker]( -> introduction/understanding-docker.md) to see how Docker is different, if you -> already have some knowledge and want to quickly get started with Docker, -> don't hesitate to jump to [Working with Docker]( -> introduction/working-with-docker.md). +> We know how valuable your time is so you if you want to get started +> with Docker straight away don't hesitate to jump to [Working with +> Docker](introduction/working-with-docker.md). For a fuller +> understanding of Docker though we do recommend you read [Understanding +> Docker]( introduction/understanding-docker.md). diff --git a/docs/sources/introduction/get-docker.md b/docs/sources/introduction/get-docker.md deleted file mode 100644 index e0d6f1665..000000000 --- a/docs/sources/introduction/get-docker.md +++ /dev/null @@ -1,77 +0,0 @@ -page_title: Getting Docker -page_description: Getting Docker and installation tutorials -page_keywords: docker, introduction, documentation, about, technology, understanding, Dockerfile - -# Getting Docker - -*How to install Docker?* - -## Introductions - -Once you are comfortable with your level of knowledge of Docker, and -feel like actually trying the product, you can download and start using -it by following the links listed below. There, you will find -installation instructions, specifically tailored for your platform of choice. - -## Installation Instructions - -### Linux (Native) - - - **Arch Linux:** - [Installation on Arch Linux](../installation/archlinux.md) - - **Fedora:** - [Installation on Fedora](../installation/fedora.md) - - **FrugalWare:** - [Installation on FrugalWare](../installation/frugalware.md) - - **Gentoo:** - [Installation on Gentoo](../installation/gentoolinux.md) - - **Red Hat Enterprise Linux:** - [Installation on Red Hat Enterprise Linux](../installation/rhel.md) - - **Ubuntu:** - [Installation on Ubuntu](../installation/ubuntulinux.md) - - **openSUSE:** - [Installation on openSUSE](../installation/openSUSE.md) - -### Mac OS X (Using Boot2Docker) - -In order to work, Docker makes use of some Linux Kernel features which -are not supported by Mac OS X. To run Docker on OS X we install and run -a lightweight virtual machine and run Docker on that. - - - **Mac OS X :** - [Installation on Mac OS X](../installation/mac.md) - -### Windows (Using Boot2Docker) - -Docker can also run on Windows using a virtual machine. You then run -Linux and Docker inside that virtual machine. - - - **Windows:** - [Installation on Windows](../installation/windows.md) - -### Infrastructure-as-a-Service - - - **Amazon EC2:** - [Installation on Amazon EC2](../installation/amazon.md) - - **Google Cloud Platform:** - [Installation on Google Cloud Platform](../installation/google.md) - - **Rackspace Cloud:** - [Installation on Rackspace Cloud](../installation/rackspace.md) - -## Where to go from here - -### Understanding Docker - -Visit [Understanding Docker](understanding-docker.md) in our Getting Started manual. - -### Learn about parts of Docker and the underlying technology - -Visit [Understanding the Technology](technology.md) in our Getting Started manual. - -### Get practical and learn how to use Docker straight away - -Visit [Working with Docker](working-with-docker.md) in our Getting Started manual. - -### Get the whole story - -[https://www.docker.io/the_whole_story/](https://www.docker.io/the_whole_story/) diff --git a/docs/sources/introduction/technology.md b/docs/sources/introduction/technology.md deleted file mode 100644 index a724e4aae..000000000 --- a/docs/sources/introduction/technology.md +++ /dev/null @@ -1,268 +0,0 @@ -page_title: Understanding the Technology -page_description: Technology of Docker explained in depth -page_keywords: docker, introduction, documentation, about, technology, understanding, Dockerfile - -# Understanding the Technology - -*What is the architecture of Docker? What is its underlying technology?* - -## Introduction - -When it comes to understanding Docker and its underlying technology -there is no *magic* involved. Everything is based on tried and tested -features of the *Linux kernel*. Docker either makes use of those -features directly or builds upon them to provide new functionality. - -Aside from the technology, one of the major factors that make Docker -great is the way it is built. The project's core is very lightweight and -as much of Docker as possible is designed to be pluggable. Docker is -also built with integration in mind and has a fully featured API that -allows you to access all of the power of Docker from inside your own -applications. - -## The Architecture of Docker - -Docker is designed for developers and sysadmins. It's built to help you -build applications and services and then deploy them quickly and -efficiently: from development to production. - -Let's take a look. - --- Docker is a client-server application. --- Both the Docker client and the daemon *can* run on the same system, or; --- You can connect a Docker client with a remote Docker daemon. --- They communicate via sockets or through a RESTful API. --- Users interact with the client to command the daemon, e.g. to create, run, and stop containers. --- The daemon, receiving those commands, does the job, e.g. run a container, stop a container. - -![Docker Architecture Diagram](/article-img/architecture.svg) - -## The components of Docker - -Docker's main components are: - - - Docker *daemon*; - - Docker *client*, and; - - [Docker.io](https://index.docker.io) registry. - -### The Docker daemon - -As shown on the diagram above, the Docker daemon runs on a host machine. -The user does not directly interact with the daemon, but instead through -an intermediary: the Docker client. - -### Docker client - -The Docker client is the primary user interface to Docker. It is tasked -with accepting commands from the user and communicating back and forth -with a Docker daemon to manage the container lifecycle on any host. - -### Docker.io registry - -[Docker.io](https://index.docker.io) is the global archive (and -directory) of user supplied Docker container images. It currently hosts -a large – in fact, rapidly growing – number of projects where you -can find almost any popular application or deployment stack readily -available to download and run with a single command. - -As a social community project, Docker tries to provide all necessary -tools for everyone to grow with other *Dockers*. By issuing a single -command through the Docker client you can start sharing your own -creations with the rest of the world. - -However, knowing that not everything can be shared the [Docker.io]( -https://index.docker.io) also offers private repositories. In order to see -the available plans, you can click [here](https://index.docker.io/plans). - -Using [*docker-registry*](https://github.com/dotcloud/docker-registry), it is -also possible to run your own private Docker image registry service on your own -servers. - -> **Note:** To learn more about the [*Docker.io*](http://index.docker.io) -> registry (for public *and* private repositories), check out the [Registry & -> Index Spec](http://docs.docker.io/api/registry_index_spec/). - -### Summary - - - **When you install Docker, you get all the components:** - The daemon, the client and access to the [Docker.io](http://index.docker.io) registry. - - **You can run these components together or distributed:** - Servers with the Docker daemon running, controlled by the Docker client. - - **You can benefit form the public registry:** - Download and build upon images created by the community. - - **You can start a private repository for proprietary use.** - Sign up for a [plan](https://index.docker.io/plans) or host your own [docker-registry]( -https://github.com/dotcloud/docker-registry). - -## Elements of Docker - -The basic elements of Docker are: - - - **Containers, which allow:** - The run portion of Docker. Your applications run inside of containers. - - **Images, which provide:** - The build portion of Docker. Your containers are built from images. - - **The Dockerfile, which automates:** - A file that contains simple instructions that build Docker images. - -To get practical and learn what they are, and **_how to work_** with -them, continue to [Working with Docker](working-with-docker.md). If you would like to -understand **_how they work_**, stay here and continue reading. - -## The underlying technology - -The power of Docker comes from the underlying technology it is built -from. A series of operating system features are carefully glued together -to provide Docker's features and provide an easy to use interface to -those features. In this section, we will see the main operating system -features that Docker uses to make easy containerization happen. - -### Namespaces - -Docker takes advantage of a technology called `namespaces` to provide -an isolated workspace we call a *container*. When you run a container, -Docker creates a set of *namespaces* for that container. - -This provides a layer of isolation: each process runs in its own -namespace and does not have access outside it. - -Some of the namespaces Docker uses are: - - - **The `pid` namespace:** - Used for process numbering (PID: Process ID) - - **The `net` namespace:** - Used for managing network interfaces (NET: Networking) - - **The `ipc` namespace:** - Used for managing access to IPC resources (IPC: InterProcess Communication) - - **The `mnt` namespace:** - Used for managing mount-points (MNT: Mount) - - **The `uts` namespace:** - Used for isolating kernel / version identifiers. (UTS: Unix Timesharing System) - -### Control groups - -Docker also makes use of another technology called `cgroups` or control -groups. A key need to run applications in isolation is to have them -contained, not just in terms of related filesystem and/or dependencies, -but also, resources. Control groups allow Docker to fairly -share available hardware resources to containers and if asked, set up to -limits and constraints, for example limiting the memory to a maximum of 128 -MBs. - -### UnionFS - -UnionFS or union filesystems are filesystems that operate by creating -layers, making them very lightweight and fast. Docker uses union -filesystems to provide the building blocks for containers. We'll see -more about this below. - -### Containers - -Docker combines these components to build a container format we call -`libcontainer`. Docker also supports traditional Linux containers like -[LXC](https://linuxcontainers.org/) which also make use of these -components. - -## How does everything work - -A lot happens when Docker creates a container. - -Let's see how it works! - -### How does a container work? - -A container consists of an operating system, user added files and -meta-data. Each container is built from an image. That image tells -Docker what the container holds, what process to run when the container -is launched and a variety of other configuration data. The Docker image -is read-only. When Docker runs a container from an image it adds a -read-write layer on top of the image (using the UnionFS technology we -saw earlier) to run inside the container. - -### What happens when you run a container? - -The Docker client (or the API!) tells the Docker daemon to run a -container. Let's take a look at a simple `Hello world` example. - - $ docker run -i -t ubuntu /bin/bash - -Let's break down this command. The Docker client is launched using the -`docker` binary. The bare minimum the Docker client needs to tell the -Docker daemon is: - -* What Docker image to build the container from; -* The command you want to run inside the container when it is launched. - -So what happens under the covers when we run this command? - -Docker begins with: - - - **Pulling the `ubuntu` image:** - Docker checks for the presence of the `ubuntu` image and if it doesn't - exist locally on the host, then Docker downloads it from [Docker.io](https://index.docker.io) - - **Creates a new container:** - Once Docker has the image it creates a container from it. - - **Allocates a filesystem and mounts a read-write _layer_:** - The container is created in the filesystem and a read-write layer is added to the image. - - **Allocates a network / bridge interface:** - Creates a network interface that allows the Docker container to talk to the local host. - - **Sets up an IP address:** - Intelligently finds and attaches an available IP address from a pool. - - **Executes _a_ process that you specify:** - Runs your application, and; - - **Captures and provides application output:** - Connects and logs standard input, outputs and errors for you to see how your application is running. - -### How does a Docker Image work? - -We've already seen that Docker images are read-only templates that -Docker containers are launched from. When you launch that container it -creates a read-write layer on top of that image that your application is -run in. - -Docker images are built using a simple descriptive set of steps we -call *instructions*. Instructions are stored in a file called a -`Dockerfile`. Each instruction writes a new layer to an image using the -UnionFS technology we saw earlier. - -Every image starts from a base image, for example `ubuntu` a base Ubuntu -image or `fedora` a base Fedora image. Docker builds and provides these -base images via [Docker.io](http://index.docker.io). - -### How does a Docker registry work? - -The Docker registry is a store for your Docker images. Once you build a -Docker image you can *push* it to a public or private repository on [Docker.io]( -http://index.docker.io) or to your own registry running behind your firewall. - -Using the Docker client, you can search for already published images and -then pull them down to your Docker host to build containers from them -(or even build on these images). - -[Docker.io](http://index.docker.io) provides both public and -private storage for images. Public storage is searchable and can be -downloaded by anyone. Private repositories are excluded from search -results and only you and your users can pull them down and use them to -build containers. You can [sign up for a plan here](https://index.docker.io/plans). - -To learn more, check out the [Working with Repositories]( -http://docs.docker.io/use/workingwithrepository) section from the -[Docker documentation](http://docs.docker.io). - -## Where to go from here - -### Understanding Docker - -Visit [Understanding Docker](understanding-docker.md) in our Getting Started manual. - -### Get practical and learn how to use Docker straight away - -Visit [Working with Docker](working-with-docker.md) in our Getting Started manual. - -### Get the product and go hands-on - -Visit [Get Docker](get-docker.md) in our Getting Started manual. - -### Get the whole story - -[https://www.docker.io/the_whole_story/](https://www.docker.io/the_whole_story/) diff --git a/docs/sources/introduction/understanding-docker.md b/docs/sources/introduction/understanding-docker.md index 53f5e4317..1d99be704 100644 --- a/docs/sources/introduction/understanding-docker.md +++ b/docs/sources/introduction/understanding-docker.md @@ -1,38 +1,129 @@ page_title: Understanding Docker page_description: Docker explained in depth -page_keywords: docker, introduction, documentation, about, technology, understanding, Dockerfile +page_keywords: docker, introduction, documentation, about, technology, understanding # Understanding Docker -*What is Docker? What makes it great?* +**What is Docker?** -Building development lifecycles, pipelines and deployment tooling is -hard. It's not easy to create portable applications and services. -There's often high friction getting code from your development -environment to production. It's also hard to ensure those applications -and services are consistent, up-to-date and managed. +Docker is a platform for developing, shipping, and running applications. +Docker is designed to deliver your applications faster. With Docker you +can separate your applications from your infrastructure AND treat your +infrastructure like a managed application. We want to help you ship code +faster, test faster, deploy faster and shorten the cycle between writing +code and running code. -Docker is designed to solve these problem for both developers and -sysadmins. It is a lightweight framework (with a powerful API) that -provides a lifecycle for building and deploying applications into -containers. +Docker does this by combining a lightweight container virtualization +platform with workflow and tooling that helps you manage and deploy your +applications. -Docker provides a way to run almost any application securely isolated -into a container. The isolation and security allows you to run many -containers simultaneously on your host. The lightweight nature of +At its core Docker provides a way to run almost any application securely +isolated into a container. The isolation and security allows you to run +many containers simultaneously on your host. The lightweight nature of containers, which run without the extra overload of a hypervisor, means you can get more out of your hardware. -**Note:** Docker itself is *shipped* with the Apache 2.0 license and it -is completely open-source — *the pun? very much intended*. +Surrounding the container virtualization, we provide tooling and a +platform to help you get your applications (and its supporting +components) into Docker containers, to distribute and ship those +containers to your teams to develop and test on them and then to deploy +those applications to your production environment whether it be in a +local data center or the Cloud. -### What are the Docker basics I need to know? +## What can I use Docker for? -Docker has three major components: +* Faster delivery of your applications + +Docker is perfect for helping you with the development lifecycle. Docker +can allow your developers to develop on local containers that contain +your applications and services. It can integrate into a continuous +integration and deployment workflow. + +Your developers write code locally and share their development stack via +Docker with their colleagues. When they are ready they can push their +code and the stack they are developing on to a test environment and +execute any required tests. From the testing environment you can then +push your Docker images into production and deploy your code. + +* Deploy and scale more easily + +Docker's container platform allows you to have highly portable +workloads. Docker containers can run on a developer's local host, on +physical or virtual machines in a data center or in the Cloud. + +Docker's portability and lightweight nature also makes managing +workloads dynamically easy. You can use Docker to build and scale out +applications and services. Docker's speed means that scaling can be near +real time. + +* Get higher density and run more workloads + +Docker is lightweight and fast. It provides a viable (and +cost-effective!) alternative to hypervisor-based virtual machines. This +is especially useful in high density environments, for example building +your own Cloud or Platform-as-a-Service. But it is also useful +for small and medium deployments where you want to get more out of the +resources you have. + +## What are the major Docker components? + +Docker has two major components: + +* Docker: the open source container virtualization platform. +* [Docker.io](https://index.docker.io): our Software-as-a-Service + platform for sharing and managing Docker containers. + +**Note:** Docker is licensed with the open source Apache 2.0 license. + +## What is the architecture of Docker? + +Docker has a client-server architecture. The Docker *client* talks to +the Docker *daemon* which does the heavy lifting of building, running +and distributing your Docker containers. Both the Docker client and the +daemon *can* run on the same system, or you can connect a Docker client +with a remote Docker daemon. The Docker client and service can +communicate via sockets or through a RESTful API. + +![Docker Architecture Diagram](/article-img/architecture.svg) + +### The Docker daemon + +As shown on the diagram above, the Docker daemon runs on a host machine. +The user does not directly interact with the daemon, but instead through +the Docker client. + +### The Docker client + +The Docker client, in the form of the `docker` binary, is the primary user +interface to Docker. It is tasked with accepting commands from the user +and communicating back and forth with a Docker daemon. + +### Inside Docker + +Inside Docker there are three concepts we’ll need to understand: -* Docker containers. * Docker images. * Docker registries. +* Docker containers. + +#### Docker images + +The Docker image is a read-only template, for example an Ubuntu operating system +with Apache and your web application installed. Docker containers are +created from images. You can download Docker images that other people +have created or Docker provides a simple way to build new images or +update existing images. You can consider Docker images to be the **build** +portion of Docker. + +#### Docker Registries + +Docker registries hold images. These are public (or private!) stores +that you can upload or download images to and from. The public Docker +registry is called [Docker.io](http://index.docker.io). It provides a +huge collection of existing images that you can use. These images can be +images you create yourself or you can make use of images that others +have previously created. You can consider Docker registries the +**distribution** portion of Docker. #### Docker containers @@ -40,233 +131,201 @@ Docker containers are like a directory. A Docker container holds everything that is needed for an application to run. Each container is created from a Docker image. Docker containers can be run, started, stopped, moved and deleted. Each container is an isolated and secure -application platform. You can consider Docker containers the *run* -portion of the Docker framework. +application platform. You can consider Docker containers the **run** +portion of Docker. -#### Docker images +## So how does Docker work? -The Docker image is a template, for example an Ubuntu -operating system with Apache and your web application installed. Docker -containers are launched from images. Docker provides a simple way to -build new images or update existing images. You can consider Docker -images to be the *build* portion of the Docker framework. +We've learned so far that: -#### Docker Registries - -Docker registries hold images. These are public (or private!) stores -that you can upload or download images to and from. These images can be -images you create yourself or you can make use of images that others -have previously created. Docker registries allow you to build simple and -powerful development and deployment work flows. You can consider Docker -registries the *share* portion of the Docker framework. - -### How does Docker work? - -Docker is a client-server framework. The Docker *client* commands the Docker -*daemon*, which in turn creates, builds and manages containers. - -The Docker daemon takes advantage of some neat Linux kernel and -operating system features, like `namespaces` and `cgroups`, to build -isolated container. Docker provides a simple abstraction layer to these -technologies. - -> **Note:** If you would like to learn more about the underlying technology, -> why not jump to [Understanding the Technology](technology.md) where we talk about them? You can -> always come back here to continue learning about features of Docker and what -> makes it different. - -## Features of Docker - -In order to get a good grasp of the capabilities of Docker you should -read the [User's Manual](http://docs.docker.io). Let's look at a summary -of Docker's features to give you an idea of how Docker might be useful -to you. - -### User centric and simple to use - -*Docker is made for humans.* - -It's easy to get started and easy to build and deploy applications with -Docker: or as we say "*dockerize*" them! As much of Docker as possible -uses plain English for commands and tries to be as lightweight and -transparent as possible. We want to get out of the way so you can build -and deploy your applications. - -### Docker is Portable - -*Dockerize And Go!* - -Docker containers are highly portable. Docker provides a standard -container format to hold your applications: - -* You take care of your applications inside the container, and; -* Docker takes care of managing the container. - -Any machine, be it bare-metal or virtualized, can run any Docker -container. The sole requirement is to have Docker installed. - -**This translates to:** - - - Reliability; - - Freeing your applications out of the dependency-hell; - - A natural guarantee that things will work, anywhere. - -### Lightweight - -*No more resources waste.* - -Containers are lightweight, in fact, they are extremely lightweight. -Unlike traditional virtual machines, which have the overhead of a -hypervisor, Docker relies on operating system level features to provide -isolation and security. A Docker container does not need anything more -than what your application needs to run. - -This translates to: - - - Ability to deploy a large number of applications on a single system; - - Lightning fast start up times and reduced overhead. - -### Docker can run anything - -*An amazing host! (again, pun intended.)* - -Docker isn't prescriptive about what applications or services you can run -inside containers. We provide use cases and examples for running web -services, databases, applications - just about anything you can imagine -can run in a Docker container. - -**This translates to:** - - - Ability to run a wide range of applications; - - Ability to deploy reliably without repeating yourself. - -### Plays well with others - -*A wonderful guest.* - -Today, it is possible to install and use Docker almost anywhere. Even on -non-Linux systems such as Windows or Mac OS X thanks to a project called -[Boot2Docker](http://boot2docker.io). - -**This translates to running Docker (and Docker containers!) _anywhere_:** - - - **Linux:** - Ubuntu, CentOS / RHEL, Fedora, Gentoo, openSUSE and more. - - **Infrastructure-as-a-Service:** - Amazon AWS, Google GCE, Rackspace Cloud and probably, your favorite IaaS. - - **Microsoft Windows** - - **OS X** - -### Docker is Responsible - -*A tool that you can trust.* - -Docker does not just bring you a set of tools to isolate and run -applications. It also allows you to specify constraints and controls on -those resources. - -**This translates to:** - - - Fine tuning available resources for each application; - - Allocating memory or CPU intelligently to make most of your environment; - -Without dealing with complicated commands or third party applications. - -### Docker is Social - -*Docker knows that No One Is an Island.* - -Docker allows you to share the images you've built with the world. And -lots of people have already shared their own images. - -To facilitate this sharing Docker comes with a public registry called -[Docker.io](http://index.docker.io). If you don't want your images to be -public you can also use private images on [Docker.io](https://index.docker.io) -or even run your own registry behind your firewall. - -**This translates to:** - - - No more wasting time building everything from scratch; - - Easily and quickly save your application stack; - - Share and benefit from the depth of the Docker community. - -## Docker versus Virtual Machines - -> I suppose it is tempting, if the *only* tool you have is a hammer, to -> treat *everything* as if it were a nail. -> — **_Abraham Maslow_** - -**Docker containers are:** - - - Easy on the resources; - - Extremely light to deal with; - - Do not come with substantial overhead; - - Very easy to work with; - - Agnostic; - - Can work *on* virtual machines; - - Secure and isolated; - - *Artful*, *social*, *fun*, and; - - Powerful sand-boxes. - -**Docker containers are not:** - - - Hardware or OS emulators; - - Resource heavy; - - Platform, software or language dependent. - -## Docker Use Cases - -Docker is a framework. As a result it's flexible and powerful enough to -be used in a lot of different use cases. - -### For developers - - - **Developed with developers in mind:** - Build, test and ship applications with nothing but Docker and lean - containers. - - **Re-usable building blocks to create more:** - Docker images are easily updated building blocks. - - **Automatically build-able:** - It has never been this easy to build - *anything*. - - **Easy to integrate:** - A powerful, fully featured API allows you to integrate Docker into your tooling. - -### For sysadmins - - - **Efficient (and DevOps friendly!) lifecycle:** - Operations and developments are consistent, repeatable and reliable. - - **Balanced environments:** - Processes between development, testing and production are leveled. - - **Improvements on speed and integration:** - Containers are almost nothing more than isolated, secure processes. - - **Lowered costs of infrastructure:** - Containers are lightweight and heavy on resources compared to virtual machines. - - **Portable configurations:** - Issues and overheads with dealing with configurations and systems are eliminated. - -### For everyone - - - **Increased security without performance loss:** - Replacing VMs with containers provide security without additional - hardware (or software). - - **Portable:** - You can easily move applications and workloads from different operating - systems and platforms. - -## Where to go from here - -### Learn about Parts of Docker and the underlying technology - -Visit [Understanding the Technology](technology.md) in our Getting Started manual. - -### Get practical and learn how to use Docker straight away - -Visit [Working with Docker](working-with-docker.md) in our Getting Started manual. - -### Get the product and go hands-on - -Visit [Get Docker](get-docker.md) in our Getting Started manual. +1. You can build Docker images that hold your applications. +2. You can create Docker containers from those Docker images to run your + applications. +3. You can share those Docker images via + [Docker.io](https://index.docker.io) or your own registry. + +Let's look at how these elements combine together to make Docker work. + +### How does a Docker Image work? + +We've already seen that Docker images are read-only templates that +Docker containers are launched from. Each image consists of a series of +layers. Docker makes use of [union file +systems](http://en.wikipedia.org/wiki/UnionFS) to combine these layers +into a single image. Union file systems allow files and directories of +separate file systems, known as branches, to be transparently overlaid, +forming a single coherent file system. + +One of the reasons Docker is so lightweight is because of these layers. +When you change a Docker image, for example update an application to a +new version, this builds a new layer. Hence, rather than replacing the whole +image or entirely rebuilding, as you may do with a virtual machine, only +that layer is added or updated. Now you don't need to distribute a whole new image, +just the update, making distributing Docker images fast and simple. + +Every image starts from a base image, for example `ubuntu`, a base Ubuntu +image, or `fedora`, a base Fedora image. You can also use images of your +own as the basis for a new image, for example if you have a base Apache +image you could use this as the base of all your web application images. + +> **Note:** +> Docker usually gets these base images from [Docker.io](https://index.docker.io). + +Docker images are then built from these base images using a simple +descriptive set of steps we call *instructions*. Each instruction +creates a new layer in our image. Instructions include steps like: + +* Run a command. +* Add a file or directory. +* Create an environment variable. +* What process to run when launching a container from this image. + +These instructions are stored in a file called a `Dockerfile`. Docker +reads this `Dockerfile` when you request an image be built, executes the +instructions and returns a final image. + +### How does a Docker registry work? + +The Docker registry is the store for your Docker images. Once you build +a Docker image you can *push* it to a public registry [Docker.io]( +https://index.docker.io) or to your own registry running behind your +firewall. + +Using the Docker client, you can search for already published images and +then pull them down to your Docker host to build containers from them. + +[Docker.io](https://index.docker.io) provides both public and +private storage for images. Public storage is searchable and can be +downloaded by anyone. Private storage is excluded from search +results and only you and your users can pull them down and use them to +build containers. You can [sign up for a plan +here](https://index.docker.io/plans). + +### How does a container work? + +A container consists of an operating system, user added files and +meta-data. As we've discovered each container is built from an image. That image tells +Docker what the container holds, what process to run when the container +is launched and a variety of other configuration data. The Docker image +is read-only. When Docker runs a container from an image it adds a +read-write layer on top of the image (using a union file system as we +saw earlier) in which your application is then run. + +### What happens when you run a container? + +The Docker client using the `docker` binary, or via the API, tells the +Docker daemon to run a container. Let's take a look at what happens +next. + + $ docker run -i -t ubuntu /bin/bash + +Let's break down this command. The Docker client is launched using the +`docker` binary with the `run` option telling it to launch a new +container. The bare minimum the Docker client needs to tell the +Docker daemon to run the container is: + +* What Docker image to build the container from, here `ubuntu`, a base + Ubuntu image; +* The command you want to run inside the container when it is launched, + here `bin/bash` to shell the Bash shell inside the new container. + +So what happens under the covers when we run this command? + +Docker begins with: + +- **Pulling the `ubuntu` image:** + Docker checks for the presence of the `ubuntu` image and if it doesn't + exist locally on the host, then Docker downloads it from + [Docker.io](https://index.docker.io). If the image already exists then + Docker uses it for the new container. +- **Creates a new container:** + Once Docker has the image it creates a container from it: + * **Allocates a filesystem and mounts a read-write _layer_:** + The container is created in the file system and a read-write layer is + added to the image. + * **Allocates a network / bridge interface:** + Creates a network interface that allows the Docker container to talk to + the local host. + * **Sets up an IP address:** + Finds and attaches an available IP address from a pool. +- **Executes a process that you specify:** + Runs your application, and; +- **Captures and provides application output:** + Connects and logs standard input, outputs and errors for you to see how + your application is running. + +Now you have a running container! From here you can manage your running +container, interact with your application and then when finished stop +and remove your container. + +## The underlying technology + +Docker is written in Go and makes use of several Linux kernel features to +deliver the features we've seen. + +### Namespaces + +Docker takes advantage of a technology called `namespaces` to provide an +isolated workspace we call a *container*. When you run a container, +Docker creates a set of *namespaces* for that container. + +This provides a layer of isolation: each aspect of a container runs in +its own namespace and does not have access outside it. + +Some of the namespaces that Docker uses are: + + - **The `pid` namespace:** + Used for process isolation (PID: Process ID). + - **The `net` namespace:** + Used for managing network interfaces (NET: Networking). + - **The `ipc` namespace:** + Used for managing access to IPC resources (IPC: InterProcess +Communication). + - **The `mnt` namespace:** + Used for managing mount-points (MNT: Mount). + - **The `uts` namespace:** + Used for isolating kernel and version identifiers. (UTS: Unix Timesharing +System). + +### Control groups + +Docker also makes use of another technology called `cgroups` or control +groups. A key need to run applications in isolation is to have them only +use the resources you want. This ensures containers are good +multi-tenant citizens on a host. Control groups allow Docker to +share available hardware resources to containers and if required, set up to +limits and constraints, for example limiting the memory available to a +specific container. + +### Union file systems + +Union file systems or UnionFS are file systems that operate by creating +layers, making them very lightweight and fast. Docker uses union file +systems to provide the building blocks for containers. We learned about +union file systems earlier in this document. Docker can make use of +several union file system variants including: AUFS, btrfs, vfs, and +DeviceMapper. + +### Container format + +Docker combines these components into a wrapper we call a container +format. The default container format is called `libcontainer`. Docker +also supports traditional Linux containers using +[LXC](https://linuxcontainers.org/). In future Docker may support other +container formats, for example integration with BSD Jails or Solaris +Zones. + +## Next steps + +### Learning how to use Docker + +Visit [Working with Docker](working-with-docker.md). + +### Installing Docker + +Visit the [installation](/installation/#installation) section. ### Get the whole story [https://www.docker.io/the_whole_story/](https://www.docker.io/the_whole_story/) + diff --git a/docs/sources/introduction/working-with-docker.md b/docs/sources/introduction/working-with-docker.md index 1b5b43fec..1abee1ce3 100644 --- a/docs/sources/introduction/working-with-docker.md +++ b/docs/sources/introduction/working-with-docker.md @@ -1,80 +1,63 @@ -page_title: Working with Docker and the Dockerfile -page_description: Working with Docker and The Dockerfile explained in depth +page_title: Introduction to working with Docker +page_description: Introduction to working with Docker and Docker commands. page_keywords: docker, introduction, documentation, about, technology, understanding, Dockerfile -# Working with Docker and the Dockerfile +# An Introduction to working with Docker -*How to use and work with Docker?* +**Getting started with Docker** -> **Warning! Don't let this long page bore you.** -> If you prefer a summary and would like to see how a specific command +> **Note:** +> If you would like to see how a specific command > works, check out the glossary of all available client -> commands on our [User's Manual: Commands Reference]( -> http://docs.docker.io/reference/commandline/cli). +> commands on our [Commands Reference](/reference/commandline/cli). ## Introduction -On the last page, [Understanding the Technology](technology.md), we covered the -components that make up Docker and learnt about the -underlying technology and *how* everything works. +In the [Understanding Docker](understanding-docker.md) section we +covered the components that make up Docker, learned about the underlying +technology and saw *how* everything works. -Now, it is time to get practical and see *how to work with* the Docker client, -Docker containers and images and the `Dockerfile`. +Now, let's get an introduction to the basics of interacting with Docker. -> **Note:** You are encouraged to take a good look at the container, -> image and `Dockerfile` explanations here to have a better understanding -> on what exactly they are and to get an overall idea on how to work with -> them. On the next page (i.e., [Get Docker](get-docker.md)), you will be -> able to find links for platform-centric installation instructions. +> **Note:** +> This page assumes you have a host with a running Docker +> daemon and access to a Docker client. To see how to install Docker on +> a variety of platforms see the [installation +> section](/installation/#installation). -## Elements of Docker - -As we mentioned on the, [Understanding the Technology](technology.md) page, the main -elements of Docker are: - - - Containers; - - Images, and; - - The `Dockerfile`. - -> **Note:** This page is more *practical* than *technical*. If you are -> interested in understanding how these tools work behind the scenes -> and do their job, you can always read more on -> [Understanding the Technology](technology.md). - -## Working with the Docker client - -In order to work with the Docker client, you need to have a host with -the Docker daemon installed and running. - -### How to use the client +## How to use the client The client provides you a command-line interface to Docker. It is accessed by running the `docker` binary. -> **Tip:** The below instructions can be considered a summary of our -> *interactive tutorial*. If you prefer a more hands-on approach without -> installing anything, why not give that a shot and check out the -> [Docker Interactive Tutorial](https://www.docker.io/gettingstarted). +> **Tip:** +> The below instructions can be considered a summary of our +> [interactive tutorial](https://www.docker.io/gettingstarted). If you +> prefer a more hands-on approach without installing anything, why not +> give that a shot and check out the +> [tutorial](https://www.docker.io/gettingstarted). -The `docker` client usage consists of passing a chain of arguments: +The `docker` client usage is pretty simple. Each action you can take +with Docker is a command and each command can take a series of +flags and arguments. - # Usage: [sudo] docker [option] [command] [arguments] .. + # Usage: [sudo] docker [flags] [command] [arguments] .. # Example: $ docker run -i -t ubuntu /bin/bash -### Our first Docker command +## Using the Docker client -Let's get started with our first Docker command by checking the -version of the currently installed Docker client using the `docker -version` command. +Let's get started with the Docker client by running our first Docker +command. We're going to use the `docker version` command to return +version information on the currently installed Docker client and daemon. # Usage: [sudo] docker version # Example: $ docker version -This command will not only provide you the version of Docker client you -are using, but also the version of Go (the programming language powering -Docker). +This command will not only provide you the version of Docker client and +daemon you are using, but also the version of Go (the programming +language powering Docker). Client version: 0.8.0 Go version (client): go1.2 @@ -87,19 +70,16 @@ Docker). Last stable version: 0.8.0 -### Finding out all available commands +### Seeing what the Docker client can do -The user-centric nature of Docker means providing you a constant stream -of helpful instructions. This begins with the client itself. - -In order to get a full list of available commands run the `docker` -binary: +We can see all of the commands available to us with the Docker client by +running the `docker` binary without any options. # Usage: [sudo] docker # Example: $ docker -You will get an output with all currently available commands. +You will see a list of all currently available commands. Commands: attach Attach to a running container @@ -107,23 +87,23 @@ You will get an output with all currently available commands. commit Create a new image from a container's changes . . . -### Command usage instructions +### Seeing Docker command usage -The same way used to learn all available commands can be repeated to find -out usage instructions for a specific command. +You can also zoom in and review the usage for specific Docker commands. -Try typing Docker followed with a `[command]` to see the instructions: +Try typing Docker followed with a `[command]` to see the usage for that +command: # Usage: [sudo] docker [command] [--help] # Example: $ docker attach - Help outputs . . . + Help output . . . -Or you can pass the `--help` flag to the `docker` binary. +Or you can also pass the `--help` flag to the `docker` binary. $ docker images --help -You will get an output with all available options: +This will display the help text and all available flags: Usage: docker attach [OPTIONS] CONTAINER @@ -134,6 +114,9 @@ You will get an output with all available options: ## Working with images +Let's get started with using Docker by working with Docker images, the +building blocks of Docker containers. + ### Docker Images As we've discovered a Docker image is a read-only template that we build @@ -146,30 +129,32 @@ runs Apache and our own web application as a starting point to launch containers To search for Docker image we use the `docker search` command. The `docker search` command returns a list of all images that match your -search criteria together with additional, useful information about that -image. This includes information such as social metrics like how many -other people like the image - we call these "likes" *stars*. We also -tell you if an image is *trusted*. A *trusted* image is built from a -known source and allows you to introspect in greater detail how the -image is constructed. +search criteria, together with some useful information about that image. + +This information includes social metrics like how many other people like +the image: we call these "likes" *stars*. We also tell you if an image +is *trusted*. A *trusted* image is built from a known source and allows +you to introspect in greater detail how the image is constructed. # Usage: [sudo] docker search [image name] # Example: $ docker search nginx - NAME DESCRIPTION STARS OFFICIAL TRUSTED - $ dockerfile/nginx Trusted Nginx (http://nginx.org/) Build 6 [OK] - paintedfox/nginx-php5 A docker image for running Nginx with PHP5. 3 [OK] - $ dockerfiles/django-uwsgi-nginx dockerfile and configuration files to buil... 2 [OK] + NAME DESCRIPTION STARS OFFICIAL TRUSTED + dockerfile/nginx Trusted Nginx (http://nginx.org/) Build 6 [OK] + paintedfox/nginx-php5 A docker image for running Nginx with PHP5. 3 [OK] + dockerfiles/django-uwsgi-nginx Dockerfile and configuration files to buil... 2 [OK] . . . -> **Note:** To learn more about trusted builds, check out [this]( -http://blog.docker.io/2013/11/introducing-trusted-builds) blog post. +> **Note:** +> To learn more about trusted builds, check out +> [this](http://blog.docker.io/2013/11/introducing-trusted-builds) blog +> post. ### Downloading an image -Downloading a Docker image is called *pulling*. To do this we hence use the -`docker pull` command. +Once we find an image we'd like to download we can pull it down from +[Docker.io](https://index.docker.io) using the `docker pull` command. # Usage: [sudo] docker pull [image name] # Example: @@ -182,13 +167,13 @@ Downloading a Docker image is called *pulling*. To do this we hence use the . . . As you can see, Docker will download, one by one, all the layers forming -the final image. This demonstrates the *building block* philosophy of -Docker. +the image. ### Listing available images -In order to get a full list of available images, you can use the -`docker images` command. +You may already have some images you've pulled down or built yourself +and you can use the `docker images` command to see the images +available to you locally. # Usage: [sudo] docker images # Example: @@ -197,28 +182,41 @@ In order to get a full list of available images, you can use the REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE myUserName/nginx latest a0d6c70867d2 41 seconds ago 578.8 MB nginx latest 173c2dd28ab2 3 minutes ago 578.8 MB - $ dockerfile/nginx latest 0ade68db1d05 3 weeks ago 578.8 MB + dockerfile/nginx latest 0ade68db1d05 3 weeks ago 578.8 MB + +### Building our own images + +You can build your own images using a `Dockerfile` and the `docker +build` command. The `Dockerfile` is very flexible and provides a +powerful set of instructions for building applications into Docker +images. To learn more about the `Dockerfile` see the [`Dockerfile` +Reference](/reference/builder/) and [tutorial](https://www.docker.io/learn/dockerfile/). ## Working with containers ### Docker Containers -Docker containers are directories on your Docker host that are built -from Docker images. In order to create or start a container, you need an -image. This could be the base `ubuntu` image or an image built and -shared with you or an image you've built yourself. +Docker containers run your applications and are built from Docker +images. In order to create or start a container, you need an image. This +could be the base `ubuntu` image or an image built and shared with you +or an image you've built yourself. ### Running a new container from an image -The easiest way to create a new container is to *run* one from an image. +The easiest way to create a new container is to *run* one from an image +using the `docker run` command. # Usage: [sudo] docker run [arguments] .. # Example: $ docker run -d --name nginx_web nginx /usr/sbin/nginx + 25137497b2749e226dd08f84a17e4b2be114ddf4ada04125f130ebfe0f1a03d3 This will create a new container from an image called `nginx` which will launch the command `/usr/sbin/nginx` when the container is run. We've -also given our container a name, `nginx_web`. +also given our container a name, `nginx_web`. When the container is run +Docker will return a container ID, a long string that uniquely +identifies our container. We use can the container's name or its string +to work with it. Containers can be run in two modes: @@ -226,7 +224,8 @@ Containers can be run in two modes: * Daemonized; An interactive container runs in the foreground and you can connect to -it and interact with it. A daemonized container runs in the background. +it and interact with it, for example sign into a shell on that +container. A daemonized container runs in the background. A container will run as long as the process you have launched inside it is running, for example if the `/usr/bin/nginx` process stops running @@ -236,7 +235,7 @@ the container will also stop. We can see a list of all the containers on our host using the `docker ps` command. By default the `docker ps` command only shows running -containers. But we can also add the `-a` flag to show *all* containers - +containers. But we can also add the `-a` flag to show *all* containers: both running and stopped. # Usage: [sudo] docker ps [-a] @@ -248,8 +247,8 @@ both running and stopped. ### Stopping a container -You can use the `docker stop` command to stop an active container. This will gracefully -end the active process. +You can use the `docker stop` command to stop an active container. This +will gracefully end the active process. # Usage: [sudo] docker stop [container ID] # Example: @@ -259,6 +258,10 @@ end the active process. If the `docker stop` command succeeds it will return the name of the container it has stopped. +> **Note:** +> If you want you to more aggressively stop a container you can use the +> `docker kill` command. + ### Starting a Container Stopped containers can be started again. @@ -271,136 +274,18 @@ Stopped containers can be started again. If the `docker start` command succeeds it will return the name of the freshly started container. -## Working with the Dockerfile +## Next steps -The `Dockerfile` holds the set of instructions Docker uses to build a Docker image. - -> **Tip:** Below is a short summary of our full Dockerfile tutorial. In -> order to get a better-grasp of how to work with these automation -> scripts, check out the [Dockerfile step-by-step -> tutorial](https://www.docker.io/learn/dockerfile). - -A `Dockerfile` contains instructions written in the following format: - - # Usage: Instruction [arguments / command] .. - # Example: - FROM ubuntu - -A `#` sign is used to provide a comment: - - # Comments .. - -> **Tip:** The `Dockerfile` is very flexible and provides a powerful set -> of instructions for building applications. To learn more about the -> `Dockerfile` and its instructions see the [Dockerfile -> Reference](http://docs.docker.io/reference/builder/). - -### First steps with the Dockerfile - -It's a good idea to add some comments to the start of your `Dockerfile` -to provide explanation and exposition to any future consumers, for -example: - - # - # Dockerfile to install Nginx - # VERSION 2 - EDITION 1 - -The first instruction in any `Dockerfile` must be the `FROM` instruction. The `FROM` instruction specifies the image name that this new image is built from, it is often a base image like `ubuntu`. - - # Base image used is Ubuntu: - FROM ubuntu - -Next, we recommend you use the `MAINTAINER` instruction to tell people who manages this image. - - # Maintainer: O.S. Tezer (@ostezer) - MAINTAINER O.S. Tezer, ostezer@gmail.com - -After this we can add additional instructions that represent the steps -to build our actual image. - -### Our Dockerfile so far - -So far our `Dockerfile` will look like. - - # Dockerfile to install Nginx - # VERSION 2 - EDITION 1 - FROM ubuntu - MAINTAINER O.S. Tezer, ostezer@gmail.com - -Let's install a package and configure an application inside our image. To do this we use a new -instruction: `RUN`. The `RUN` instruction executes commands inside our -image, for example. The instruction is just like running a command on -the command line inside a container. - - RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.list - RUN apt-get update - RUN apt-get install -y nginx - RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf - -We can see here that we've *run* four instructions. Each time we run an -instruction a new layer is added to our image. Here's we've added an -Ubuntu package repository, updated the packages, installed the `nginx` -package and then echo'ed some configuration to the default -`/etc/nginx/nginx.conf` configuration file. - -Let's specify another instruction, `CMD`, that tells Docker what command -to run when a container is created from this image. - - CMD /usr/sbin/nginx - -We can now save this file and use it build an image. - -### Using a Dockerfile - -Docker uses the `Dockerfile` to build images. The build process is initiated by the `docker build` command. - - # Use the Dockerfile at the current location - # Usage: [sudo] docker build . - # Example: - $ docker build -t="my_nginx_image" . - - Uploading context 25.09 kB - Uploading context - Step 0 : FROM ubuntu - ---> 9cd978db300e - Step 1 : MAINTAINER O.S. Tezer, ostezer@gmail.com - ---> Using cache - ---> 467542d0cdd3 - Step 2 : RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.list - ---> Using cache - ---> 0a688bd2a48c - Step 3 : RUN apt-get update - ---> Running in de2937e8915a - . . . - Step 10 : CMD /usr/sbin/nginx - ---> Running in b4908b9b9868 - ---> 626e92c5fab1 - Successfully built 626e92c5fab1 - -Here we can see that Docker has executed each instruction in turn and -each instruction has created a new layer in turn and each layer identified -by a new ID. The `-t` flag allows us to specify a name for our new -image, here `my_nginx_image`. - -We can see our new image using the `docker images` command. - - $ docker images - REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE - my_nginx_img latest 626e92c5fab1 57 seconds ago 337.6 MB - -## Where to go from here +Here we've learned the basics of how to interact with Docker images and +how to run and work with our first container. ### Understanding Docker -Visit [Understanding Docker](understanding-docker.md) in our Getting Started manual. +Visit [Understanding Docker](understanding-docker.md). -### Learn about parts of Docker and the underlying technology +### Installing Docker -Visit [Understanding the Technology](technology.md) in our Getting Started manual. - -### Get the product and go hands-on - -Visit [Get Docker](get-docker.md) in our Getting Started manual. +Visit the [installation](/installation/#installation) section. ### Get the whole story From 811d93326bc2d9451eb444e2343bb3063611de7a Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 21 May 2014 20:48:06 +0000 Subject: [PATCH 213/400] Move get pid into cgroup implementation Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/execdriver/native/create.go | 13 +++- daemon/execdriver/native/driver.go | 59 ++++++++----------- pkg/libcontainer/cgroups/fs/apply_raw.go | 30 ++++++++++ pkg/libcontainer/cgroups/fs/devices.go | 9 --- .../cgroups/systemd/apply_nosystemd.go | 6 +- .../cgroups/systemd/apply_systemd.go | 24 +++++++- pkg/libcontainer/cgroups/utils.go | 26 ++++++++ 7 files changed, 118 insertions(+), 49 deletions(-) diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index 76816e0b9..d177ca784 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -3,6 +3,7 @@ package native import ( "fmt" "os" + "os/exec" "path/filepath" "github.com/dotcloud/docker/daemon/execdriver" @@ -46,7 +47,11 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Container if err := d.setupLabels(container, c); err != nil { return nil, err } - if err := configuration.ParseConfiguration(container, d.activeContainers, c.Config["native"]); err != nil { + cmds := make(map[string]*exec.Cmd) + for k, v := range d.activeContainers { + cmds[k] = v.cmd + } + if err := configuration.ParseConfiguration(container, cmds, c.Config["native"]); err != nil { return nil, err } return container, nil @@ -82,10 +87,12 @@ func (d *driver) createNetwork(container *libcontainer.Container, c *execdriver. } if c.Network.ContainerID != "" { - cmd := d.activeContainers[c.Network.ContainerID] - if cmd == nil || cmd.Process == nil { + active := d.activeContainers[c.Network.ContainerID] + if active == nil || active.cmd.Process == nil { return fmt.Errorf("%s is not a valid running container to join", c.Network.ContainerID) } + cmd := active.cmd + nspath := filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "net") container.Networks = append(container.Networks, &libcontainer.Network{ Type: "netns", diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index 59d193ed0..425403fa4 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -7,14 +7,14 @@ import ( "os" "os/exec" "path/filepath" - "strconv" "strings" "syscall" "github.com/dotcloud/docker/daemon/execdriver" "github.com/dotcloud/docker/pkg/apparmor" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/cgroups" + "github.com/dotcloud/docker/pkg/libcontainer/cgroups/fs" + "github.com/dotcloud/docker/pkg/libcontainer/cgroups/systemd" "github.com/dotcloud/docker/pkg/libcontainer/nsinit" "github.com/dotcloud/docker/pkg/system" ) @@ -53,24 +53,31 @@ func init() { }) } +type activeContainer struct { + container *libcontainer.Container + cmd *exec.Cmd +} + type driver struct { root string initPath string - activeContainers map[string]*exec.Cmd + activeContainers map[string]*activeContainer } func NewDriver(root, initPath string) (*driver, error) { if err := os.MkdirAll(root, 0700); err != nil { return nil, err } + // native driver root is at docker_root/execdriver/native. Put apparmor at docker_root if err := apparmor.InstallDefaultProfile(filepath.Join(root, "../..", BackupApparmorProfilePath)); err != nil { return nil, err } + return &driver{ root: root, initPath: initPath, - activeContainers: make(map[string]*exec.Cmd), + activeContainers: make(map[string]*activeContainer), }, nil } @@ -80,7 +87,10 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba if err != nil { return -1, err } - d.activeContainers[c.ID] = &c.Cmd + d.activeContainers[c.ID] = &activeContainer{ + container: container, + cmd: &c.Cmd, + } var ( dataPath = filepath.Join(d.root, c.ID) @@ -175,41 +185,18 @@ func (d *driver) Name() string { return fmt.Sprintf("%s-%s", DriverName, Version) } -// TODO: this can be improved with our driver -// there has to be a better way to do this func (d *driver) GetPidsForContainer(id string) ([]int, error) { - pids := []int{} + active := d.activeContainers[id] - subsystem := "devices" - cgroupRoot, err := cgroups.FindCgroupMountpoint(subsystem) - if err != nil { - return pids, err - } - cgroupDir, err := cgroups.GetThisCgroupDir(subsystem) - if err != nil { - return pids, err + if active == nil { + return nil, fmt.Errorf("active container for %s does not exist", id) } + c := active.container.Cgroups - filename := filepath.Join(cgroupRoot, cgroupDir, id, "tasks") - if _, err := os.Stat(filename); os.IsNotExist(err) { - filename = filepath.Join(cgroupRoot, cgroupDir, "docker", id, "tasks") + if systemd.UseSystemd() { + return systemd.GetPids(c) } - - output, err := ioutil.ReadFile(filename) - if err != nil { - return pids, err - } - for _, p := range strings.Split(string(output), "\n") { - if len(p) == 0 { - continue - } - pid, err := strconv.Atoi(p) - if err != nil { - return pids, fmt.Errorf("Invalid pid '%s': %s", p, err) - } - pids = append(pids, pid) - } - return pids, nil + return fs.GetPids(c) } func (d *driver) writeContainerFile(container *libcontainer.Container, id string) error { @@ -225,6 +212,8 @@ func (d *driver) createContainerRoot(id string) error { } func (d *driver) removeContainerRoot(id string) error { + delete(d.activeContainers, id) + return os.RemoveAll(filepath.Join(d.root, id)) } diff --git a/pkg/libcontainer/cgroups/fs/apply_raw.go b/pkg/libcontainer/cgroups/fs/apply_raw.go index 65aabcc52..fd52c6074 100644 --- a/pkg/libcontainer/cgroups/fs/apply_raw.go +++ b/pkg/libcontainer/cgroups/fs/apply_raw.go @@ -103,6 +103,36 @@ func GetStats(c *cgroups.Cgroup, subsystem string, pid int) (map[string]float64, return sys.Stats(d) } +func GetPids(c *cgroups.Cgroup) ([]int, error) { + cgroupRoot, err := cgroups.FindCgroupMountpoint("cpu") + if err != nil { + return nil, err + } + cgroupRoot = filepath.Dir(cgroupRoot) + + if _, err := os.Stat(cgroupRoot); err != nil { + return nil, fmt.Errorf("cgroup root %s not found", cgroupRoot) + } + + cgroup := c.Name + if c.Parent != "" { + cgroup = filepath.Join(c.Parent, cgroup) + } + + d := &data{ + root: cgroupRoot, + cgroup: cgroup, + c: c, + } + + dir, err := d.path("devices") + if err != nil { + return nil, err + } + + return cgroups.ReadProcsFile(dir) +} + func (raw *data) parent(subsystem string) (string, error) { initPath, err := cgroups.GetInitCgroupDir(subsystem) if err != nil { diff --git a/pkg/libcontainer/cgroups/fs/devices.go b/pkg/libcontainer/cgroups/fs/devices.go index a2f91eda1..fd9a39d9b 100644 --- a/pkg/libcontainer/cgroups/fs/devices.go +++ b/pkg/libcontainer/cgroups/fs/devices.go @@ -1,9 +1,5 @@ package fs -import ( - "os" -) - type devicesGroup struct { } @@ -12,11 +8,6 @@ func (s *devicesGroup) Set(d *data) error { if err != nil { return err } - defer func() { - if err != nil { - os.RemoveAll(dir) - } - }() if !d.c.DeviceAccess { if err := writeFile(dir, "devices.deny", "a"); err != nil { diff --git a/pkg/libcontainer/cgroups/systemd/apply_nosystemd.go b/pkg/libcontainer/cgroups/systemd/apply_nosystemd.go index 302fab773..0fff3e4c6 100644 --- a/pkg/libcontainer/cgroups/systemd/apply_nosystemd.go +++ b/pkg/libcontainer/cgroups/systemd/apply_nosystemd.go @@ -12,6 +12,10 @@ func UseSystemd() bool { return false } -func Apply(c *Cgroup, pid int) (cgroups.ActiveCgroup, error) { +func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) { + return nil, fmt.Errorf("Systemd not supported") +} + +func GetPids(c *cgroups.Cgroup) ([]int, error) { return nil, fmt.Errorf("Systemd not supported") } diff --git a/pkg/libcontainer/cgroups/systemd/apply_systemd.go b/pkg/libcontainer/cgroups/systemd/apply_systemd.go index 4d6b68b2c..0f6beb658 100644 --- a/pkg/libcontainer/cgroups/systemd/apply_systemd.go +++ b/pkg/libcontainer/cgroups/systemd/apply_systemd.go @@ -3,6 +3,7 @@ package systemd import ( + "fmt" "io/ioutil" "os" "path/filepath" @@ -78,7 +79,7 @@ type cgroupArg struct { func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) { var ( - unitName = c.Parent + "-" + c.Name + ".scope" + unitName = getUnitName(c) slice = "system.slice" properties []systemd1.Property cpuArgs []cgroupArg @@ -303,3 +304,24 @@ func (c *systemdCgroup) Cleanup() error { return nil } + +func GetPids(c *cgroups.Cgroup) ([]int, error) { + unitName := getUnitName(c) + + mountpoint, err := cgroups.FindCgroupMountpoint("cpu") + if err != nil { + return nil, err + } + + props, err := theConn.GetUnitTypeProperties(unitName, getIfaceForUnit(unitName)) + if err != nil { + return nil, err + } + cgroup := props["ControlGroup"].(string) + + return cgroups.ReadProcsFile(filepath.Join(mountpoint, cgroup)) +} + +func getUnitName(c *cgroups.Cgroup) string { + return fmt.Sprintf("%s-%s.scope", c.Parent, c.Name) +} diff --git a/pkg/libcontainer/cgroups/utils.go b/pkg/libcontainer/cgroups/utils.go index 02a7f357f..111c87147 100644 --- a/pkg/libcontainer/cgroups/utils.go +++ b/pkg/libcontainer/cgroups/utils.go @@ -4,6 +4,8 @@ import ( "bufio" "io" "os" + "path/filepath" + "strconv" "strings" "github.com/dotcloud/docker/pkg/mount" @@ -49,6 +51,30 @@ func GetInitCgroupDir(subsystem string) (string, error) { return parseCgroupFile(subsystem, f) } +func ReadProcsFile(dir string) ([]int, error) { + f, err := os.Open(filepath.Join(dir, "cgroup.procs")) + if err != nil { + return nil, err + } + defer f.Close() + + var ( + s = bufio.NewScanner(f) + out = []int{} + ) + + for s.Scan() { + if t := s.Text(); t != "" { + pid, err := strconv.Atoi(t) + if err != nil { + return nil, err + } + out = append(out, pid) + } + } + return out, nil +} + func parseCgroupFile(subsystem string, r io.Reader) (string, error) { s := bufio.NewScanner(r) for s.Scan() { From 26b4a4920adca614f6be17a96f254f331271faf0 Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Wed, 21 May 2014 17:11:57 -0400 Subject: [PATCH 214/400] This check was backwards. If I do a docker events > /tmp/out I do not want the control characters getting written to the file. The check should check the output file not the input file. Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) --- api/client/cli.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/client/cli.go b/api/client/cli.go index 44cff0dc4..bb5d191e1 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -76,7 +76,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string, tlsC } if in != nil { - if file, ok := in.(*os.File); ok { + if file, ok := out.(*os.File); ok { terminalFd = file.Fd() isTerminal = term.IsTerminal(terminalFd) } From 5579bec47be6279569e69c72cccf87864af481de Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Wed, 21 May 2014 15:07:40 -0700 Subject: [PATCH 215/400] integration-cli: tests for /etc/hosts and net=host Some basic tests to make sure this is acting correctly on machines. Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- integration-cli/docker_cli_links_test.go | 36 ++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/integration-cli/docker_cli_links_test.go b/integration-cli/docker_cli_links_test.go index ed30288b7..0480183bc 100644 --- a/integration-cli/docker_cli_links_test.go +++ b/integration-cli/docker_cli_links_test.go @@ -3,10 +3,46 @@ package main import ( "fmt" "github.com/dotcloud/docker/pkg/iptables" + "io/ioutil" + "os" "os/exec" + "strings" "testing" ) +func TestEtcHostsRegularFile(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "--net=host", "busybox", "ls", "-la", "/etc/hosts") + out, _, _, err := runCommandWithStdoutStderr(runCmd) + errorOut(err, t, out) + + if !strings.HasPrefix(out, "-") { + t.Errorf("/etc/hosts should be a regular file") + } + + deleteAllContainers() + + logDone("link - /etc/hosts is a regular file") +} + +func TestEtcHostsContentMatch(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "--net=host", "busybox", "cat", "/etc/hosts") + out, _, _, err := runCommandWithStdoutStderr(runCmd) + errorOut(err, t, out) + + hosts, err := ioutil.ReadFile("/etc/hosts") + if os.IsNotExist(err) { + t.Skip("/etc/hosts does not exist, skip this test") + } + + if out != string(hosts) { + t.Errorf("container") + } + + deleteAllContainers() + + logDone("link - /etc/hosts matches hosts copy") +} + func TestPingUnlinkedContainers(t *testing.T) { runCmd := exec.Command(dockerBinary, "run", "--rm", "busybox", "sh", "-c", "ping -c 1 alias1 -W 1 && ping -c 1 alias2 -W 1") exitCode, err := runCommand(runCmd) From 61ac745d7a7dd192948e0c1cfbdff87af7715c92 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Wed, 21 May 2014 15:08:40 -0700 Subject: [PATCH 216/400] integration-cli: fix spelling error in test Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- integration-cli/docker_cli_run_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 154b72e27..10b9f6a7c 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -438,7 +438,7 @@ func TestCreateVolume(t *testing.T) { deleteAllContainers() - logDone("run - create docker mangaed volume") + logDone("run - create docker managed volume") } // Test that creating a volume with a symlink in its path works correctly. Test for #5152. From df5585b273686324e9fd8269a6492409698623ee Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Wed, 21 May 2014 15:30:10 -0700 Subject: [PATCH 217/400] vendor: bump github.com/coreos/go-systemd to v2 There are a couple of bugfixes since this was last bumped. Pull them in. Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- hack/vendor.sh | 2 +- .../coreos/go-systemd/dbus/methods.go | 46 ++++++++++- .../coreos/go-systemd/dbus/methods_test.go | 67 +++++++++------ .../coreos/go-systemd/dbus/subscription.go | 8 +- .../go-systemd/dbus/subscription_set_test.go | 5 +- .../go-systemd/dbus/subscription_test.go | 1 + .../coreos/go-systemd/journal/send.go | 2 +- .../coreos/go-systemd/login1/dbus.go | 81 +++++++++++++++++++ .../coreos/go-systemd/login1/dbus_test.go | 30 +++++++ 9 files changed, 207 insertions(+), 35 deletions(-) create mode 100644 vendor/src/github.com/coreos/go-systemd/login1/dbus.go create mode 100644 vendor/src/github.com/coreos/go-systemd/login1/dbus_test.go diff --git a/hack/vendor.sh b/hack/vendor.sh index 7f01a236c..8084f2eb9 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -60,4 +60,4 @@ mkdir -p src/code.google.com/p/go/src/pkg/archive mv tmp-tar src/code.google.com/p/go/src/pkg/archive/tar clone git github.com/godbus/dbus v1 -clone git github.com/coreos/go-systemd v1 +clone git github.com/coreos/go-systemd v2 diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/methods.go b/vendor/src/github.com/coreos/go-systemd/dbus/methods.go index 11d5cda94..a60de059e 100644 --- a/vendor/src/github.com/coreos/go-systemd/dbus/methods.go +++ b/vendor/src/github.com/coreos/go-systemd/dbus/methods.go @@ -204,7 +204,7 @@ func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]i // to modify. properties are the settings to set, encoded as an array of property // name and value pairs. func (c *Conn) SetUnitProperties(name string, runtime bool, properties ...Property) error { - return c.sysobj.Call("SetUnitProperties", 0, name, runtime, properties).Store() + return c.sysobj.Call("org.freedesktop.systemd1.Manager.SetUnitProperties", 0, name, runtime, properties).Store() } func (c *Conn) GetUnitTypeProperty(unit string, unitType string, propertyName string) (*Property, error) { @@ -253,6 +253,48 @@ type UnitStatus struct { JobPath dbus.ObjectPath // The job object path } +type LinkUnitFileChange EnableUnitFileChange + +// LinkUnitFiles() links unit files (that are located outside of the +// usual unit search paths) into the unit search path. +// +// It takes a list of absolute paths to unit files to link and two +// booleans. The first boolean controls whether the unit shall be +// enabled for runtime only (true, /run), or persistently (false, +// /etc). +// The second controls whether symlinks pointing to other units shall +// be replaced if necessary. +// +// This call returns a list of the changes made. The list consists of +// structures with three strings: the type of the change (one of symlink +// or unlink), the file name of the symlink and the destination of the +// symlink. +func (c *Conn) LinkUnitFiles(files []string, runtime bool, force bool) ([]LinkUnitFileChange, error) { + result := make([][]interface{}, 0) + err := c.sysobj.Call("org.freedesktop.systemd1.Manager.LinkUnitFiles", 0, files, runtime, force).Store(&result) + if err != nil { + return nil, err + } + + resultInterface := make([]interface{}, len(result)) + for i := range result { + resultInterface[i] = result[i] + } + + changes := make([]LinkUnitFileChange, len(result)) + changesInterface := make([]interface{}, len(changes)) + for i := range changes { + changesInterface[i] = &changes[i] + } + + err = dbus.Store(resultInterface, changesInterface...) + if err != nil { + return nil, err + } + + return changes, nil +} + // EnableUnitFiles() may be used to enable one or more units in the system (by // creating symlinks to them in /etc or /run). // @@ -317,7 +359,7 @@ type EnableUnitFileChange struct { // symlink. func (c *Conn) DisableUnitFiles(files []string, runtime bool) ([]DisableUnitFileChange, error) { result := make([][]interface{}, 0) - err := c.sysobj.Call("DisableUnitFiles", 0, files, runtime).Store(&result) + err := c.sysobj.Call("org.freedesktop.systemd1.Manager.DisableUnitFiles", 0, files, runtime).Store(&result) if err != nil { return nil, err } diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/methods_test.go b/vendor/src/github.com/coreos/go-systemd/dbus/methods_test.go index d943e7ebf..8c7ab93eb 100644 --- a/vendor/src/github.com/coreos/go-systemd/dbus/methods_test.go +++ b/vendor/src/github.com/coreos/go-systemd/dbus/methods_test.go @@ -36,36 +36,38 @@ func setupConn(t *testing.T) *Conn { return conn } +func findFixture(target string, t *testing.T) string { + abs, err := filepath.Abs("../fixtures/" + target) + if err != nil { + t.Fatal(err) + } + return abs +} + func setupUnit(target string, conn *Conn, t *testing.T) { // Blindly stop the unit in case it is running conn.StopUnit(target, "replace") // Blindly remove the symlink in case it exists targetRun := filepath.Join("/run/systemd/system/", target) - err := os.Remove(targetRun) - - // 1. Enable the unit - abs, err := filepath.Abs("../fixtures/" + target) - if err != nil { - t.Fatal(err) - } + os.Remove(targetRun) +} +func linkUnit(target string, conn *Conn, t *testing.T) { + abs := findFixture(target, t) fixture := []string{abs} - install, changes, err := conn.EnableUnitFiles(fixture, true, true) + changes, err := conn.LinkUnitFiles(fixture, true, true) if err != nil { t.Fatal(err) } - if install != false { - t.Fatal("Install was true") - } - if len(changes) < 1 { t.Fatalf("Expected one change, got %v", changes) } - if changes[0].Filename != targetRun { + runPath := filepath.Join("/run/systemd/system/", target) + if changes[0].Filename != runPath { t.Fatal("Unexpected target filename") } } @@ -76,6 +78,7 @@ func TestStartStopUnit(t *testing.T) { conn := setupConn(t) setupUnit(target, conn, t) + linkUnit(target, conn, t) // 2. Start the unit job, err := conn.StartUnit(target, "replace") @@ -84,7 +87,7 @@ func TestStartStopUnit(t *testing.T) { } if job != "done" { - t.Fatal("Job is not done, %v", job) + t.Fatal("Job is not done:", job) } units, err := conn.ListUnits() @@ -130,28 +133,41 @@ func TestEnableDisableUnit(t *testing.T) { conn := setupConn(t) setupUnit(target, conn, t) + abs := findFixture(target, t) + runPath := filepath.Join("/run/systemd/system/", target) - abs, err := filepath.Abs("../fixtures/" + target) + // 1. Enable the unit + install, changes, err := conn.EnableUnitFiles([]string{abs}, true, true) if err != nil { t.Fatal(err) } - path := filepath.Join("/run/systemd/system/", target) + if install != false { + t.Fatal("Install was true") + } + + if len(changes) < 1 { + t.Fatalf("Expected one change, got %v", changes) + } + + if changes[0].Filename != runPath { + t.Fatal("Unexpected target filename") + } // 2. Disable the unit - changes, err := conn.DisableUnitFiles([]string{abs}, true) + dChanges, err := conn.DisableUnitFiles([]string{abs}, true) if err != nil { t.Fatal(err) } - if len(changes) != 1 { - t.Fatalf("Changes should include the path, %v", changes) + if len(dChanges) != 1 { + t.Fatalf("Changes should include the path, %v", dChanges) } - if changes[0].Filename != path { - t.Fatalf("Change should include correct filename, %+v", changes[0]) + if dChanges[0].Filename != runPath { + t.Fatalf("Change should include correct filename, %+v", dChanges[0]) } - if changes[0].Destination != "" { - t.Fatalf("Change destination should be empty, %+v", changes[0]) + if dChanges[0].Destination != "" { + t.Fatalf("Change destination should be empty, %+v", dChanges[0]) } } @@ -230,7 +246,7 @@ func TestSetUnitProperties(t *testing.T) { value := info["CPUShares"].(uint64) if value != 1023 { - t.Fatal("CPUShares of unit is not 1023, %s", value) + t.Fatal("CPUShares of unit is not 1023:", value) } } @@ -250,7 +266,7 @@ func TestStartStopTransientUnit(t *testing.T) { } if job != "done" { - t.Fatal("Job is not done, %v", job) + t.Fatal("Job is not done:", job) } units, err := conn.ListUnits() @@ -295,6 +311,7 @@ func TestConnJobListener(t *testing.T) { conn := setupConn(t) setupUnit(target, conn, t) + linkUnit(target, conn, t) jobSize := len(conn.jobListener.jobs) diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/subscription.go b/vendor/src/github.com/coreos/go-systemd/dbus/subscription.go index 3d896d896..fcd29b6e8 100644 --- a/vendor/src/github.com/coreos/go-systemd/dbus/subscription.go +++ b/vendor/src/github.com/coreos/go-systemd/dbus/subscription.go @@ -40,7 +40,6 @@ func (c *Conn) Subscribe() error { err := c.sysobj.Call("org.freedesktop.systemd1.Manager.Subscribe", 0).Store() if err != nil { - c.sysconn.Close() return err } @@ -51,7 +50,6 @@ func (c *Conn) Subscribe() error { func (c *Conn) Unsubscribe() error { err := c.sysobj.Call("org.freedesktop.systemd1.Manager.Unsubscribe", 0).Store() if err != nil { - c.sysconn.Close() return err } @@ -69,7 +67,11 @@ func (c *Conn) initDispatch() { go func() { for { - signal := <-ch + signal, ok := <-ch + if !ok { + return + } + switch signal.Name { case "org.freedesktop.systemd1.Manager.JobRemoved": c.jobComplete(signal) diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/subscription_set_test.go b/vendor/src/github.com/coreos/go-systemd/dbus/subscription_set_test.go index db600850c..4ecd15376 100644 --- a/vendor/src/github.com/coreos/go-systemd/dbus/subscription_set_test.go +++ b/vendor/src/github.com/coreos/go-systemd/dbus/subscription_set_test.go @@ -25,6 +25,7 @@ func TestSubscriptionSetUnit(t *testing.T) { subSet.Add(target) setupUnit(target, conn, t) + linkUnit(target, conn, t) job, err := conn.StartUnit(target, "replace") if err != nil { @@ -47,7 +48,7 @@ func TestSubscriptionSetUnit(t *testing.T) { tCh, ok := changes[target] if !ok { - t.Fatal("Unexpected event %v", changes) + t.Fatal("Unexpected event:", changes) } if tCh.ActiveState == "active" && tCh.Name == target { @@ -63,5 +64,3 @@ func TestSubscriptionSetUnit(t *testing.T) { success: return } - - diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/subscription_test.go b/vendor/src/github.com/coreos/go-systemd/dbus/subscription_test.go index 6f4d0b32a..f2b5dfc28 100644 --- a/vendor/src/github.com/coreos/go-systemd/dbus/subscription_test.go +++ b/vendor/src/github.com/coreos/go-systemd/dbus/subscription_test.go @@ -47,6 +47,7 @@ func TestSubscribeUnit(t *testing.T) { evChan, errChan := conn.SubscribeUnits(time.Second) setupUnit(target, conn, t) + linkUnit(target, conn, t) job, err := conn.StartUnit(target, "replace") if err != nil { diff --git a/vendor/src/github.com/coreos/go-systemd/journal/send.go b/vendor/src/github.com/coreos/go-systemd/journal/send.go index a29bcbf0f..b52e12098 100644 --- a/vendor/src/github.com/coreos/go-systemd/journal/send.go +++ b/vendor/src/github.com/coreos/go-systemd/journal/send.go @@ -119,7 +119,7 @@ func appendVariable(w io.Writer, name, value string) { fmt.Fprintln(w, value) } else { /* just write the variable and value all on one line */ - fmt.Fprintln(w, "%s=%s", name, value) + fmt.Fprintf(w, "%s=%s\n", name, value) } } diff --git a/vendor/src/github.com/coreos/go-systemd/login1/dbus.go b/vendor/src/github.com/coreos/go-systemd/login1/dbus.go new file mode 100644 index 000000000..d00dd110b --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/login1/dbus.go @@ -0,0 +1,81 @@ +/* +Copyright 2014 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Integration with the systemd logind API. See http://www.freedesktop.org/wiki/Software/systemd/logind/ +package login1 + +import ( + "os" + "strconv" + + "github.com/godbus/dbus" +) + +const ( + dbusInterface = "org.freedesktop.login1.Manager" + dbusPath = "/org/freedesktop/login1" +) + +// Conn is a connection to systemds dbus endpoint. +type Conn struct { + conn *dbus.Conn + object *dbus.Object +} + +// New() establishes a connection to the system bus and authenticates. +func New() (*Conn, error) { + c := new(Conn) + + if err := c.initConnection(); err != nil { + return nil, err + } + + return c, nil +} + +func (c *Conn) initConnection() error { + var err error + c.conn, err = dbus.SystemBusPrivate() + if err != nil { + return err + } + + // Only use EXTERNAL method, and hardcode the uid (not username) + // to avoid a username lookup (which requires a dynamically linked + // libc) + methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))} + + err = c.conn.Auth(methods) + if err != nil { + c.conn.Close() + return err + } + + err = c.conn.Hello() + if err != nil { + c.conn.Close() + return err + } + + c.object = c.conn.Object("org.freedesktop.login1", dbus.ObjectPath(dbusPath)) + + return nil +} + +// Reboot asks logind for a reboot optionally asking for auth. +func (c *Conn) Reboot(askForAuth bool) { + c.object.Call(dbusInterface+".Reboot", 0, askForAuth) +} diff --git a/vendor/src/github.com/coreos/go-systemd/login1/dbus_test.go b/vendor/src/github.com/coreos/go-systemd/login1/dbus_test.go new file mode 100644 index 000000000..4439d3738 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/login1/dbus_test.go @@ -0,0 +1,30 @@ +/* +Copyright 2014 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package login1 + +import ( + "testing" +) + +// TestNew ensures that New() works without errors. +func TestNew(t *testing.T) { + _, err := New() + + if err != nil { + t.Fatal(err) + } +} From 62468a3eba4d115c16388fb4f67d6adfe4bbb0fc Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 22 May 2014 08:35:10 +1000 Subject: [PATCH 218/400] Added BitBucket support to documentation Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- docs/sources/docker-io/builds.md | 125 ++++++++++++++++++---- docs/sources/use/workingwithrepository.md | 20 ++-- 2 files changed, 114 insertions(+), 31 deletions(-) diff --git a/docs/sources/docker-io/builds.md b/docs/sources/docker-io/builds.md index 0ca058663..1f6e00220 100644 --- a/docs/sources/docker-io/builds.md +++ b/docs/sources/docker-io/builds.md @@ -7,20 +7,25 @@ page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker.io, ## Trusted Builds *Trusted Builds* is a special feature allowing you to specify a source -repository with a *Dockerfile* to be built by the Docker build clusters. The -system will clone your repository and build the Dockerfile using the repository -as the context. The resulting image will then be uploaded to the registry and -marked as a `Trusted Build`. +repository with a `Dockerfile` to be built by the +[Docker.io](https://index.docker.io) build clusters. The system will +clone your repository and build the `Dockerfile` using the repository as +the context. The resulting image will then be uploaded to the registry +and marked as a *Trusted Build*. Trusted Builds have a number of advantages. For example, users of *your* Trusted Build can be certain that the resulting image was built exactly how it claims to be. -Furthermore, the Dockerfile will be available to anyone browsing your repository +Furthermore, the `Dockerfile` will be available to anyone browsing your repository on the registry. Another advantage of the Trusted Builds feature is the automated builds. This makes sure that your repository is always up to date. -### Linking with a GitHub account +Trusted builds are supported for both public and private repositories on +both [GitHub](http://github.com) and +[BitBucket](https://bitbucket.org/). + +### Setting up Trusted Builds with GitHub In order to setup a Trusted Build, you need to first link your [Docker.io]( https://index.docker.io) account with a GitHub one. This will allow the registry @@ -30,23 +35,28 @@ to see your repositories. > https://index.docker.io) needs to setup a GitHub service hook. Although nothing > else is done with your account, this is how GitHub manages permissions, sorry! -### Creating a Trusted Build +Click on the [Trusted Builds tab](https://index.docker.io/builds/) to +get started and then select [+ Add +New](https://index.docker.io/builds/add/). + +Select the [GitHub +service](https://index.docker.io/associate/github/). + +Then follow the instructions to authorize and link your GitHub account +to Docker.io. + +#### Creating a Trusted Build You can [create a Trusted Build](https://index.docker.io/builds/github/select/) -from any of your public GitHub repositories with a Dockerfile. +from any of your public or private GitHub repositories with a `Dockerfile`. -> **Note:** We currently only support public repositories. To have more than -> one Docker image from the same GitHub repository, you will need to set up one -> Trusted Build per Dockerfile, each using a different image name. This rule -> applies to building multiple branches on the same GitHub repository as well. - -### GitHub organizations +#### GitHub organizations GitHub organizations appear once your membership to that organization is made public on GitHub. To verify, you can look at the members tab for your organization on GitHub. -### GitHub service hooks +#### GitHub service hooks You can follow the below steps to configure the GitHub service hooks for your Trusted Build: @@ -74,9 +84,32 @@ Trusted Build: +### Setting up Trusted Builds with BitBucket + +In order to setup a Trusted Build, you need to first link your +[Docker.io]( https://index.docker.io) account with a BitBucket one. This +will allow the registry to see your repositories. + +Click on the [Trusted Builds tab](https://index.docker.io/builds/) to +get started and then select [+ Add +New](https://index.docker.io/builds/add/). + +Select the [BitBucket +service](https://index.docker.io/associate/bitbucket/). + +Then follow the instructions to authorize and link your BitBucket account +to Docker.io. + +#### Creating a Trusted Build + +You can [create a Trusted +Build](https://index.docker.io/builds/bitbucket/select/) +from any of your public or private BitBucket repositories with a +`Dockerfile`. + ### The Dockerfile and Trusted Builds -During the build process, we copy the contents of your Dockerfile. We also +During the build process, we copy the contents of your `Dockerfile`. We also add it to the [Docker.io](https://index.docker.io) for the Docker community to see on the repository page. @@ -89,14 +122,18 @@ repository's full description. > If you change the full description after a build, it will be > rewritten the next time the Trusted Build has been built. To make changes, > modify the README.md from the Git repository. We will look for a README.md -> in the same directory as your Dockerfile. +> in the same directory as your `Dockerfile`. ### Build triggers -If you need another way to trigger your Trusted Builds outside of GitHub, you -can setup a build trigger. When you turn on the build trigger for a Trusted -Build, it will give you a URL to which you can send POST requests. This will -trigger the Trusted Build process, which is similar to GitHub webhooks. +If you need another way to trigger your Trusted Builds outside of GitHub +or BitBucket, you can setup a build trigger. When you turn on the build +trigger for a Trusted Build, it will give you a URL to which you can +send POST requests. This will trigger the Trusted Build process, which +is similar to GitHub web hooks. + +Build Triggers are available under the Settings tab of each Trusted +Build. > **Note:** > You can only trigger one build at a time and no more than one @@ -105,6 +142,52 @@ trigger the Trusted Build process, which is similar to GitHub webhooks. > You can find the logs of last 10 triggers on the settings page to verify > if everything is working correctly. +### Webhooks + +Also available for Trusted Builds are Webhooks. Webhooks can be called +after a successful repository push is made. + +The web hook call will generate a HTTP POST with the following JSON +payload: + +``` +{ + "push_data":{ + "pushed_at":1385141110, + "images":[ + "imagehash1", + "imagehash2", + "imagehash3" + ], + "pusher":"username" + }, + "repository":{ + "status":"Active", + "description":"my docker repo that does cool things", + "is_trusted":false, + "full_description":"This is my full description", + "repo_url":"https://index.docker.io/u/username/reponame/", + "owner":"username", + "is_official":false, + "is_private":false, + "name":"reponame", + "namespace":"username", + "star_count":1, + "comment_count":1, + "date_created":1370174400, + "dockerfile":"my full dockerfile is listed here", + "repo_name":"username/reponame" + } +} +``` + +Webhooks are available under the Settings tab of each Trusted +Build. + +> **Note:** If you want to test your webhook out then we recommend using +> a tool like [requestb.in](http://requestb.in/). + + ### Repository links Repository links are a way to associate one Trusted Build with another. If one diff --git a/docs/sources/use/workingwithrepository.md b/docs/sources/use/workingwithrepository.md index b78d9bdc0..2b4ad613c 100644 --- a/docs/sources/use/workingwithrepository.md +++ b/docs/sources/use/workingwithrepository.md @@ -146,17 +146,17 @@ or tag. ## Trusted Builds -Trusted Builds automate the building and updating of images from GitHub, -directly on Docker.io. It works by adding a commit hook to +Trusted Builds automate the building and updating of images from GitHub +or BitBucket, directly on Docker.io. It works by adding a commit hook to your selected repository, triggering a build and update when you push a commit. ### To setup a trusted build 1. Create a [Docker.io account](https://index.docker.io/) and login. -2. Link your GitHub account through the `Link Accounts` menu. +2. Link your GitHub or BitBucket account through the [`Link Accounts`](https://index.docker.io/account/accounts/) menu. 3. [Configure a Trusted build](https://index.docker.io/builds/). -4. Pick a GitHub project that has a `Dockerfile` that you want to build. +4. Pick a GitHub or BitBucket project that has a `Dockerfile` that you want to build. 5. Pick the branch you want to build (the default is the `master` branch). 6. Give the Trusted Build a name. 7. Assign an optional Docker tag to the Build. @@ -165,17 +165,17 @@ commit. Once the Trusted Build is configured it will automatically trigger a build, and in a few minutes, if there are no errors, you will see your new trusted build on the [Docker.io](https://index.docker.io) Registry. -It will will stay in sync with your GitHub repo until you deactivate the -Trusted Build. +It will stay in sync with your GitHub and BitBucket repository until you +deactivate the Trusted Build. If you want to see the status of your Trusted Builds you can go to your -[Trusted Builds page](https://index.docker.io/builds/) on the Docker -index, and it will show you the status of your builds, and the build -history. +[Trusted Builds page](https://index.docker.io/builds/) on the Docker.io, +and it will show you the status of your builds, and the build history. Once you've created a Trusted Build you can deactivate or delete it. You cannot however push to a Trusted Build with the `docker push` command. -You can only manage it by committing code to your GitHub repository. +You can only manage it by committing code to your GitHub or BitBucket +repository. You can create multiple Trusted Builds per repository and configure them to point to specific `Dockerfile`'s or Git branches. From b1464b2daa58057bc0aaa59adc9d5ae3b0671654 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 21 May 2014 22:48:31 +0000 Subject: [PATCH 219/400] add Tianon as maintainer of AUTHORS and .mailmap Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 581953cf8..1543c8f82 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2,6 +2,8 @@ Solomon Hykes (@shykes) Guillaume J. Charmes (@creack) Victor Vieux (@vieux) Michael Crosby (@crosbymichael) +.mailmap: Tianon Gravi (@tianon) .travis.yml: Tianon Gravi (@tianon) +AUTHORS: Tianon Gravi (@tianon) Dockerfile: Tianon Gravi (@tianon) Makefile: Tianon Gravi (@tianon) From e7babb28ccb2d451baec9a8cb83e627390cbcdd4 Mon Sep 17 00:00:00 2001 From: Nathan LeClaire Date: Tue, 20 May 2014 12:53:46 -0700 Subject: [PATCH 220/400] Change single-dash flag usage to double-dash The single-dash long-form flag usage is deprecated. Docker-DCO-1.1-Signed-off-by: Nathan LeClaire (github: nathanleclaire) --- docs/sources/use/working_with_links_names.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/use/working_with_links_names.md b/docs/sources/use/working_with_links_names.md index ea3a25ab7..4b7030d24 100644 --- a/docs/sources/use/working_with_links_names.md +++ b/docs/sources/use/working_with_links_names.md @@ -28,8 +28,8 @@ name using the `docker ps` command. ## Links: service discovery for docker Links allow containers to discover and securely communicate with each -other by using the flag `-link name:alias`. Inter-container -communication can be disabled with the daemon flag `-icc=false`. With +other by using the flag `--link name:alias`. Inter-container +communication can be disabled with the daemon flag `--icc=false`. With this flag set to `false`, Container A cannot access Container unless explicitly allowed via a link. This is a huge win for securing your containers. When two containers are linked together Docker creates a From 42708181b1976a768428568e664c566717ade8c4 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 22 May 2014 09:23:52 +0200 Subject: [PATCH 221/400] devmapper: Don't enable discard during mkfs.ext4 There is no reason to do discard durink mkfs, as the filesystem is on a newly allocated device anyway. Discard is a slow operation, so this may help initial startup a bit, especially if you use a larger thin pool. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- daemon/graphdriver/devmapper/deviceset.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 744f1e11b..4de7858c1 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -273,9 +273,9 @@ func (devices *DeviceSet) activateDeviceIfNeeded(info *DevInfo) error { func (devices *DeviceSet) createFilesystem(info *DevInfo) error { devname := info.DevName() - err := exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0,lazy_journal_init=0", devname).Run() + err := exec.Command("mkfs.ext4", "-E", "nodiscard,lazy_itable_init=0,lazy_journal_init=0", devname).Run() if err != nil { - err = exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0", devname).Run() + err = exec.Command("mkfs.ext4", "-E", "nodiscard,lazy_itable_init=0", devname).Run() } if err != nil { utils.Debugf("\n--->Err: %s\n", err) From f92d68a6cedd069c138ac83961ad54ceb937ebd0 Mon Sep 17 00:00:00 2001 From: Alexandr Morozov Date: Thu, 22 May 2014 14:39:00 +0400 Subject: [PATCH 222/400] Add synchronization between jobs and server closing Fixes #5154 Daemon waiting 15 seconds for finishing server jobs before shutdown. In this time it doesn't accept new jobs. After this time, it shutdown despite running jobs. Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- server/server.go | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/server/server.go b/server/server.go index 25aa1b005..63b110531 100644 --- a/server/server.go +++ b/server/server.go @@ -55,6 +55,17 @@ import ( "github.com/dotcloud/docker/utils" ) +func (srv *Server) handlerWrap(h engine.Handler) engine.Handler { + return func(job *engine.Job) engine.Status { + if !srv.IsRunning() { + return job.Errorf("Server is not running") + } + srv.tasks.Add(1) + defer srv.tasks.Done() + return h(job) + } +} + // jobInitApi runs the remote api server `srv` as a daemon, // Only one api server can run at the same time - this is enforced by a pidfile. // The signals SIGINT, SIGQUIT and SIGTERM are intercepted for cleanup. @@ -136,7 +147,7 @@ func InitServer(job *engine.Job) engine.Status { "push": srv.ImagePush, "containers": srv.Containers, } { - if err := job.Eng.Register(name, handler); err != nil { + if err := job.Eng.Register(name, srv.handlerWrap(handler)); err != nil { return job.Error(err) } } @@ -150,6 +161,7 @@ func InitServer(job *engine.Job) engine.Status { if err := srv.daemon.Install(job.Eng); err != nil { return job.Error(err) } + srv.SetRunning(true) return engine.StatusOK } @@ -2365,7 +2377,6 @@ func NewServer(eng *engine.Engine, config *daemonconfig.Config) (*Server, error) pushingPool: make(map[string]chan struct{}), events: make([]utils.JSONMessage, 0, 64), //only keeps the 64 last events listeners: make(map[int64]chan utils.JSONMessage), - running: true, } daemon.SetServer(srv) return srv, nil @@ -2414,6 +2425,16 @@ func (srv *Server) Close() error { return nil } srv.SetRunning(false) + done := make(chan struct{}) + go func() { + srv.tasks.Wait() + close(done) + }() + select { + // Waiting server jobs for 15 seconds, shutdown immediately after that time + case <-time.After(time.Second * 15): + case <-done: + } if srv.daemon == nil { return nil } @@ -2429,4 +2450,5 @@ type Server struct { listeners map[int64]chan utils.JSONMessage Eng *engine.Engine running bool + tasks sync.WaitGroup } From 4a33a757d540acd059ac957542527d641548e2cf Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Thu, 22 May 2014 20:50:51 +0000 Subject: [PATCH 223/400] Make all cgroup stats output int64s instead of float64. Docker-DCO-1.1-Signed-off-by: Victor Marmol (github: vmarmol) --- pkg/libcontainer/cgroups/fs/apply_raw.go | 4 +- pkg/libcontainer/cgroups/fs/blkio.go | 8 ++-- pkg/libcontainer/cgroups/fs/blkio_test.go | 34 ++++++++--------- pkg/libcontainer/cgroups/fs/cpu.go | 4 +- pkg/libcontainer/cgroups/fs/cpu_test.go | 8 ++-- pkg/libcontainer/cgroups/fs/cpuacct.go | 43 +++++++++------------- pkg/libcontainer/cgroups/fs/cpuset.go | 2 +- pkg/libcontainer/cgroups/fs/devices.go | 2 +- pkg/libcontainer/cgroups/fs/freezer.go | 7 ++-- pkg/libcontainer/cgroups/fs/memory.go | 6 +-- pkg/libcontainer/cgroups/fs/memory_test.go | 2 +- pkg/libcontainer/cgroups/fs/perf_event.go | 2 +- pkg/libcontainer/cgroups/fs/test_util.go | 2 +- pkg/libcontainer/cgroups/fs/utils.go | 16 ++++---- pkg/libcontainer/cgroups/fs/utils_test.go | 10 ++--- 15 files changed, 72 insertions(+), 78 deletions(-) diff --git a/pkg/libcontainer/cgroups/fs/apply_raw.go b/pkg/libcontainer/cgroups/fs/apply_raw.go index fd52c6074..be500781e 100644 --- a/pkg/libcontainer/cgroups/fs/apply_raw.go +++ b/pkg/libcontainer/cgroups/fs/apply_raw.go @@ -26,7 +26,7 @@ var ( type subsystem interface { Set(*data) error Remove(*data) error - Stats(*data) (map[string]float64, error) + Stats(*data) (map[string]int64, error) } type data struct { @@ -74,7 +74,7 @@ func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) { return d, nil } -func GetStats(c *cgroups.Cgroup, subsystem string, pid int) (map[string]float64, error) { +func GetStats(c *cgroups.Cgroup, subsystem string, pid int) (map[string]int64, error) { cgroupRoot, err := cgroups.FindCgroupMountpoint("cpu") if err != nil { return nil, err diff --git a/pkg/libcontainer/cgroups/fs/blkio.go b/pkg/libcontainer/cgroups/fs/blkio.go index 213d250d2..5cbef69f5 100644 --- a/pkg/libcontainer/cgroups/fs/blkio.go +++ b/pkg/libcontainer/cgroups/fs/blkio.go @@ -57,9 +57,9 @@ examples: 8:0 Total 0 Total 0 */ -func (s *blkioGroup) Stats(d *data) (map[string]float64, error) { +func (s *blkioGroup) Stats(d *data) (map[string]int64, error) { var ( - paramData = make(map[string]float64) + paramData = make(map[string]int64) params = []string{ "io_service_bytes_recursive", "io_serviced_recursive", @@ -91,7 +91,7 @@ func (s *blkioGroup) Stats(d *data) (map[string]float64, error) { fields := strings.Fields(sc.Text()) switch len(fields) { case 3: - v, err := strconv.ParseFloat(fields[2], 64) + v, err := strconv.ParseInt(fields[2], 10, 64) if err != nil { return nil, err } @@ -106,7 +106,7 @@ func (s *blkioGroup) Stats(d *data) (map[string]float64, error) { return paramData, nil } -func (s *blkioGroup) getSectors(path string) (string, float64, error) { +func (s *blkioGroup) getSectors(path string) (string, int64, error) { f, err := os.Open(filepath.Join(path, "blkio.sectors_recursive")) if err != nil { return "", 0, err diff --git a/pkg/libcontainer/cgroups/fs/blkio_test.go b/pkg/libcontainer/cgroups/fs/blkio_test.go index 5279ac437..d0244ad71 100644 --- a/pkg/libcontainer/cgroups/fs/blkio_test.go +++ b/pkg/libcontainer/cgroups/fs/blkio_test.go @@ -43,29 +43,29 @@ func TestBlkioStats(t *testing.T) { } // Verify expected stats. - expectedStats := map[string]float64{ - "blkio.sectors_recursive:8:0": 1024.0, + expectedStats := map[string]int64{ + "blkio.sectors_recursive:8:0": 1024, // Serviced bytes. - "io_service_bytes_recursive:8:0:Read": 100.0, - "io_service_bytes_recursive:8:0:Write": 400.0, - "io_service_bytes_recursive:8:0:Sync": 200.0, - "io_service_bytes_recursive:8:0:Async": 300.0, - "io_service_bytes_recursive:8:0:Total": 500.0, + "io_service_bytes_recursive:8:0:Read": 100, + "io_service_bytes_recursive:8:0:Write": 400, + "io_service_bytes_recursive:8:0:Sync": 200, + "io_service_bytes_recursive:8:0:Async": 300, + "io_service_bytes_recursive:8:0:Total": 500, // Serviced requests. - "io_serviced_recursive:8:0:Read": 10.0, - "io_serviced_recursive:8:0:Write": 40.0, - "io_serviced_recursive:8:0:Sync": 20.0, - "io_serviced_recursive:8:0:Async": 30.0, - "io_serviced_recursive:8:0:Total": 50.0, + "io_serviced_recursive:8:0:Read": 10, + "io_serviced_recursive:8:0:Write": 40, + "io_serviced_recursive:8:0:Sync": 20, + "io_serviced_recursive:8:0:Async": 30, + "io_serviced_recursive:8:0:Total": 50, // Queued requests. - "io_queued_recursive:8:0:Read": 1.0, - "io_queued_recursive:8:0:Write": 4.0, - "io_queued_recursive:8:0:Sync": 2.0, - "io_queued_recursive:8:0:Async": 3.0, - "io_queued_recursive:8:0:Total": 5.0, + "io_queued_recursive:8:0:Read": 1, + "io_queued_recursive:8:0:Write": 4, + "io_queued_recursive:8:0:Sync": 2, + "io_queued_recursive:8:0:Async": 3, + "io_queued_recursive:8:0:Total": 5, } expectStats(t, expectedStats, stats) } diff --git a/pkg/libcontainer/cgroups/fs/cpu.go b/pkg/libcontainer/cgroups/fs/cpu.go index 6a7f66c72..ad3078b3b 100644 --- a/pkg/libcontainer/cgroups/fs/cpu.go +++ b/pkg/libcontainer/cgroups/fs/cpu.go @@ -39,8 +39,8 @@ func (s *cpuGroup) Remove(d *data) error { return removePath(d.path("cpu")) } -func (s *cpuGroup) Stats(d *data) (map[string]float64, error) { - paramData := make(map[string]float64) +func (s *cpuGroup) Stats(d *data) (map[string]int64, error) { + paramData := make(map[string]int64) path, err := d.path("cpu") if err != nil { return nil, err diff --git a/pkg/libcontainer/cgroups/fs/cpu_test.go b/pkg/libcontainer/cgroups/fs/cpu_test.go index 698ae921d..cacf2f4ce 100644 --- a/pkg/libcontainer/cgroups/fs/cpu_test.go +++ b/pkg/libcontainer/cgroups/fs/cpu_test.go @@ -20,10 +20,10 @@ func TestCpuStats(t *testing.T) { t.Fatal(err) } - expected_stats := map[string]float64{ - "nr_periods": 2000.0, - "nr_throttled": 200.0, - "throttled_time": 42424242424.0, + expected_stats := map[string]int64{ + "nr_periods": 2000, + "nr_throttled": 200, + "throttled_time": 42424242424, } expectStats(t, expected_stats, stats) } diff --git a/pkg/libcontainer/cgroups/fs/cpuacct.go b/pkg/libcontainer/cgroups/fs/cpuacct.go index 3382440d2..c52049f3e 100644 --- a/pkg/libcontainer/cgroups/fs/cpuacct.go +++ b/pkg/libcontainer/cgroups/fs/cpuacct.go @@ -15,8 +15,8 @@ import ( ) var ( - cpuCount = float64(runtime.NumCPU()) - clockTicks = float64(system.GetClockTicks()) + cpuCount = int64(runtime.NumCPU()) + clockTicks = int64(system.GetClockTicks()) ) type cpuacctGroup struct { @@ -34,11 +34,11 @@ func (s *cpuacctGroup) Remove(d *data) error { return removePath(d.path("cpuacct")) } -func (s *cpuacctGroup) Stats(d *data) (map[string]float64, error) { +func (s *cpuacctGroup) Stats(d *data) (map[string]int64, error) { var ( - startCpu, lastCpu, startSystem, lastSystem, startUsage, lastUsage float64 - percentage float64 - paramData = make(map[string]float64) + startCpu, lastCpu, startSystem, lastSystem, startUsage, lastUsage int64 + percentage int64 + paramData = make(map[string]int64) ) path, err := d.path("cpuacct") if startCpu, err = s.getCpuUsage(d, path); err != nil { @@ -48,7 +48,7 @@ func (s *cpuacctGroup) Stats(d *data) (map[string]float64, error) { return nil, err } startUsageTime := time.Now() - if startUsage, err = getCgroupParamFloat64(path, "cpuacct.usage"); err != nil { + if startUsage, err = getCgroupParamInt(path, "cpuacct.usage"); err != nil { return nil, err } // sample for 100ms @@ -60,7 +60,7 @@ func (s *cpuacctGroup) Stats(d *data) (map[string]float64, error) { return nil, err } usageSampleDuration := time.Since(startUsageTime) - if lastUsage, err = getCgroupParamFloat64(path, "cpuacct.usage"); err != nil { + if lastUsage, err = getCgroupParamInt(path, "cpuacct.usage"); err != nil { return nil, err } @@ -77,19 +77,12 @@ func (s *cpuacctGroup) Stats(d *data) (map[string]float64, error) { paramData["percentage"] = percentage // Delta usage is in nanoseconds of CPU time so get the usage (in cores) over the sample time. - paramData["usage"] = deltaUsage / float64(usageSampleDuration.Nanoseconds()) + paramData["usage"] = deltaUsage / usageSampleDuration.Nanoseconds() return paramData, nil } -func (s *cpuacctGroup) getProcStarttime(d *data) (float64, error) { - rawStart, err := system.GetProcessStartTime(d.pid) - if err != nil { - return 0, err - } - return strconv.ParseFloat(rawStart, 64) -} - -func (s *cpuacctGroup) getSystemCpuUsage(d *data) (float64, error) { +// TODO(vmarmol): Use cgroups stats. +func (s *cpuacctGroup) getSystemCpuUsage(d *data) (int64, error) { f, err := os.Open("/proc/stat") if err != nil { @@ -106,11 +99,11 @@ func (s *cpuacctGroup) getSystemCpuUsage(d *data) (float64, error) { return 0, fmt.Errorf("invalid number of cpu fields") } - var total float64 + var total int64 for _, i := range parts[1:8] { - v, err := strconv.ParseFloat(i, 64) + v, err := strconv.ParseInt(i, 10, 64) if err != nil { - return 0.0, fmt.Errorf("Unable to convert value %s to float: %s", i, err) + return 0.0, fmt.Errorf("Unable to convert value %s to int: %s", i, err) } total += v } @@ -122,11 +115,11 @@ func (s *cpuacctGroup) getSystemCpuUsage(d *data) (float64, error) { return 0, fmt.Errorf("invalid stat format") } -func (s *cpuacctGroup) getCpuUsage(d *data, path string) (float64, error) { - cpuTotal := 0.0 +func (s *cpuacctGroup) getCpuUsage(d *data, path string) (int64, error) { + cpuTotal := int64(0) f, err := os.Open(filepath.Join(path, "cpuacct.stat")) if err != nil { - return 0.0, err + return 0, err } defer f.Close() @@ -134,7 +127,7 @@ func (s *cpuacctGroup) getCpuUsage(d *data, path string) (float64, error) { for sc.Scan() { _, v, err := getCgroupParamKeyValue(sc.Text()) if err != nil { - return 0.0, err + return 0, err } // set the raw data in map cpuTotal += v diff --git a/pkg/libcontainer/cgroups/fs/cpuset.go b/pkg/libcontainer/cgroups/fs/cpuset.go index f9f65ba42..af2dd528d 100644 --- a/pkg/libcontainer/cgroups/fs/cpuset.go +++ b/pkg/libcontainer/cgroups/fs/cpuset.go @@ -38,7 +38,7 @@ func (s *cpusetGroup) Remove(d *data) error { return removePath(d.path("cpuset")) } -func (s *cpusetGroup) Stats(d *data) (map[string]float64, error) { +func (s *cpusetGroup) Stats(d *data) (map[string]int64, error) { return nil, ErrNotSupportStat } diff --git a/pkg/libcontainer/cgroups/fs/devices.go b/pkg/libcontainer/cgroups/fs/devices.go index fd9a39d9b..00fea608f 100644 --- a/pkg/libcontainer/cgroups/fs/devices.go +++ b/pkg/libcontainer/cgroups/fs/devices.go @@ -55,6 +55,6 @@ func (s *devicesGroup) Remove(d *data) error { return removePath(d.path("devices")) } -func (s *devicesGroup) Stats(d *data) (map[string]float64, error) { +func (s *devicesGroup) Stats(d *data) (map[string]int64, error) { return nil, ErrNotSupportStat } diff --git a/pkg/libcontainer/cgroups/fs/freezer.go b/pkg/libcontainer/cgroups/fs/freezer.go index f3b1985a5..0738ec1f0 100644 --- a/pkg/libcontainer/cgroups/fs/freezer.go +++ b/pkg/libcontainer/cgroups/fs/freezer.go @@ -35,9 +35,9 @@ func (s *freezerGroup) Remove(d *data) error { return removePath(d.path("freezer")) } -func (s *freezerGroup) Stats(d *data) (map[string]float64, error) { +func (s *freezerGroup) Stats(d *data) (map[string]int64, error) { var ( - paramData = make(map[string]float64) + paramData = make(map[string]int64) params = []string{ "parent_freezing", "self_freezing", @@ -50,6 +50,7 @@ func (s *freezerGroup) Stats(d *data) (map[string]float64, error) { return nil, err } + // TODO(vmarmol): This currently outputs nothing since the output is a string, fix. for _, param := range params { f, err := os.Open(filepath.Join(path, fmt.Sprintf("freezer.%s", param))) if err != nil { @@ -62,7 +63,7 @@ func (s *freezerGroup) Stats(d *data) (map[string]float64, error) { return nil, err } - v, err := strconv.ParseFloat(strings.TrimSuffix(string(data), "\n"), 64) + v, err := strconv.ParseInt(strings.TrimSuffix(string(data), "\n"), 10, 64) if err != nil { return nil, err } diff --git a/pkg/libcontainer/cgroups/fs/memory.go b/pkg/libcontainer/cgroups/fs/memory.go index 837640c08..9964f8376 100644 --- a/pkg/libcontainer/cgroups/fs/memory.go +++ b/pkg/libcontainer/cgroups/fs/memory.go @@ -50,8 +50,8 @@ func (s *memoryGroup) Remove(d *data) error { return removePath(d.path("memory")) } -func (s *memoryGroup) Stats(d *data) (map[string]float64, error) { - paramData := make(map[string]float64) +func (s *memoryGroup) Stats(d *data) (map[string]int64, error) { + paramData := make(map[string]int64) path, err := d.path("memory") if err != nil { return nil, err @@ -79,7 +79,7 @@ func (s *memoryGroup) Stats(d *data) (map[string]float64, error) { "max_usage_in_bytes", } for _, param := range params { - value, err := getCgroupParamFloat64(path, fmt.Sprintf("memory.%s", param)) + value, err := getCgroupParamInt(path, fmt.Sprintf("memory.%s", param)) if err != nil { return nil, err } diff --git a/pkg/libcontainer/cgroups/fs/memory_test.go b/pkg/libcontainer/cgroups/fs/memory_test.go index 6c1fb735e..190d437b1 100644 --- a/pkg/libcontainer/cgroups/fs/memory_test.go +++ b/pkg/libcontainer/cgroups/fs/memory_test.go @@ -25,7 +25,7 @@ func TestMemoryStats(t *testing.T) { if err != nil { t.Fatal(err) } - expectedStats := map[string]float64{"cache": 512.0, "rss": 1024.0, "usage_in_bytes": 2048.0, "max_usage_in_bytes": 4096.0} + expectedStats := map[string]int64{"cache": 512, "rss": 1024, "usage_in_bytes": 2048, "max_usage_in_bytes": 4096} expectStats(t, expectedStats, stats) } diff --git a/pkg/libcontainer/cgroups/fs/perf_event.go b/pkg/libcontainer/cgroups/fs/perf_event.go index 063cec4b0..1cf1aeef1 100644 --- a/pkg/libcontainer/cgroups/fs/perf_event.go +++ b/pkg/libcontainer/cgroups/fs/perf_event.go @@ -19,6 +19,6 @@ func (s *perfEventGroup) Remove(d *data) error { return removePath(d.path("perf_event")) } -func (s *perfEventGroup) Stats(d *data) (map[string]float64, error) { +func (s *perfEventGroup) Stats(d *data) (map[string]int64, error) { return nil, ErrNotSupportStat } diff --git a/pkg/libcontainer/cgroups/fs/test_util.go b/pkg/libcontainer/cgroups/fs/test_util.go index 11b90b21d..333386c5d 100644 --- a/pkg/libcontainer/cgroups/fs/test_util.go +++ b/pkg/libcontainer/cgroups/fs/test_util.go @@ -61,7 +61,7 @@ func (c *cgroupTestUtil) writeFileContents(fileContents map[string]string) { } // Expect the specified stats. -func expectStats(t *testing.T, expected, actual map[string]float64) { +func expectStats(t *testing.T, expected, actual map[string]int64) { for stat, expectedValue := range expected { actualValue, ok := actual[stat] if !ok { diff --git a/pkg/libcontainer/cgroups/fs/utils.go b/pkg/libcontainer/cgroups/fs/utils.go index 8be65c97e..7213b5d6a 100644 --- a/pkg/libcontainer/cgroups/fs/utils.go +++ b/pkg/libcontainer/cgroups/fs/utils.go @@ -16,25 +16,25 @@ var ( // Parses a cgroup param and returns as name, value // i.e. "io_service_bytes 1234" will return as io_service_bytes, 1234 -func getCgroupParamKeyValue(t string) (string, float64, error) { +func getCgroupParamKeyValue(t string) (string, int64, error) { parts := strings.Fields(t) switch len(parts) { case 2: - value, err := strconv.ParseFloat(parts[1], 64) + value, err := strconv.ParseInt(parts[1], 10, 64) if err != nil { - return "", 0.0, fmt.Errorf("Unable to convert param value to float: %s", err) + return "", 0, fmt.Errorf("Unable to convert param value to int: %s", err) } return parts[0], value, nil default: - return "", 0.0, ErrNotValidFormat + return "", 0, ErrNotValidFormat } } -// Gets a single float64 value from the specified cgroup file. -func getCgroupParamFloat64(cgroupPath, cgroupFile string) (float64, error) { +// Gets a single int64 value from the specified cgroup file. +func getCgroupParamInt(cgroupPath, cgroupFile string) (int64, error) { contents, err := ioutil.ReadFile(filepath.Join(cgroupPath, cgroupFile)) if err != nil { - return -1.0, err + return -1, err } - return strconv.ParseFloat(strings.TrimSpace(string(contents)), 64) + return strconv.ParseInt(strings.TrimSpace(string(contents)), 10, 64) } diff --git a/pkg/libcontainer/cgroups/fs/utils_test.go b/pkg/libcontainer/cgroups/fs/utils_test.go index c8f1b0172..4dd2243ef 100644 --- a/pkg/libcontainer/cgroups/fs/utils_test.go +++ b/pkg/libcontainer/cgroups/fs/utils_test.go @@ -13,7 +13,7 @@ const ( floatString = "2048" ) -func TestGetCgroupParamsFloat64(t *testing.T) { +func TestGetCgroupParamsInt(t *testing.T) { // Setup tempdir. tempDir, err := ioutil.TempDir("", "cgroup_utils_test") if err != nil { @@ -27,7 +27,7 @@ func TestGetCgroupParamsFloat64(t *testing.T) { if err != nil { t.Fatal(err) } - value, err := getCgroupParamFloat64(tempDir, cgroupFile) + value, err := getCgroupParamInt(tempDir, cgroupFile) if err != nil { t.Fatal(err) } else if value != floatValue { @@ -39,7 +39,7 @@ func TestGetCgroupParamsFloat64(t *testing.T) { if err != nil { t.Fatal(err) } - value, err = getCgroupParamFloat64(tempDir, cgroupFile) + value, err = getCgroupParamInt(tempDir, cgroupFile) if err != nil { t.Fatal(err) } else if value != floatValue { @@ -51,7 +51,7 @@ func TestGetCgroupParamsFloat64(t *testing.T) { if err != nil { t.Fatal(err) } - _, err = getCgroupParamFloat64(tempDir, cgroupFile) + _, err = getCgroupParamInt(tempDir, cgroupFile) if err == nil { t.Fatal("Expecting error, got none") } @@ -61,7 +61,7 @@ func TestGetCgroupParamsFloat64(t *testing.T) { if err != nil { t.Fatal(err) } - _, err = getCgroupParamFloat64(tempDir, cgroupFile) + _, err = getCgroupParamInt(tempDir, cgroupFile) if err == nil { t.Fatal("Expecting error, got none") } From 0abb52c7a97940dc17c45ac45226af8156d0e712 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 22 May 2014 19:14:10 +0000 Subject: [PATCH 224/400] add recursive device nodes Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- .../native/template/default_template.go | 2 +- pkg/libcontainer/mount/nodes/nodes.go | 44 ++++++++++++------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/daemon/execdriver/native/template/default_template.go b/daemon/execdriver/native/template/default_template.go index cbef06fbf..a80b609a1 100644 --- a/daemon/execdriver/native/template/default_template.go +++ b/daemon/execdriver/native/template/default_template.go @@ -35,7 +35,7 @@ func New() *libcontainer.Container { }, Context: libcontainer.Context{}, RequiredDeviceNodes: nodes.DefaultNodes, - OptionalDeviceNodes: []string{"fuse"}, + OptionalDeviceNodes: []string{"/dev/fuse"}, } if apparmor.IsEnabled() { container.Context["apparmor_profile"] = "docker-default" diff --git a/pkg/libcontainer/mount/nodes/nodes.go b/pkg/libcontainer/mount/nodes/nodes.go index 14b6f5ae5..f8e6e9745 100644 --- a/pkg/libcontainer/mount/nodes/nodes.go +++ b/pkg/libcontainer/mount/nodes/nodes.go @@ -14,12 +14,12 @@ import ( // Default list of device nodes to copy var DefaultNodes = []string{ - "null", - "zero", - "full", - "random", - "urandom", - "tty", + "/dev/null", + "/dev/zero", + "/dev/full", + "/dev/random", + "/dev/urandom", + "/dev/tty", } // CopyN copies the device node from the host into the rootfs @@ -39,7 +39,7 @@ func CopyN(rootfs string, nodesToCopy []string, shouldExist bool) error { // on the host system does not exist and the boolean flag is passed // an error will be returned func Copy(rootfs, node string, shouldExist bool) error { - stat, err := os.Stat(filepath.Join("/dev", node)) + stat, err := os.Stat(node) if err != nil { if os.IsNotExist(err) && !shouldExist { return nil @@ -48,27 +48,41 @@ func Copy(rootfs, node string, shouldExist bool) error { } var ( - dest = filepath.Join(rootfs, "dev", node) - st = stat.Sys().(*syscall.Stat_t) + dest = filepath.Join(rootfs, node) + st = stat.Sys().(*syscall.Stat_t) + parent = filepath.Dir(dest) ) + if err := os.MkdirAll(parent, 0755); err != nil { + return err + } + if err := system.Mknod(dest, st.Mode, int(st.Rdev)); err != nil && !os.IsExist(err) { return fmt.Errorf("mknod %s %s", node, err) } return nil } -func GetHostDeviceNodes() ([]string, error) { - files, err := ioutil.ReadDir("/dev") +func getNodes(path string) ([]string, error) { + out := []string{} + files, err := ioutil.ReadDir(path) if err != nil { return nil, err } - - out := []string{} for _, f := range files { - if f.Mode()&os.ModeDevice == os.ModeDevice { - out = append(out, f.Name()) + if f.IsDir() && f.Name() != "pts" && f.Name() != "shm" { + sub, err := getNodes(filepath.Join(path, f.Name())) + if err != nil { + return nil, err + } + out = append(out, sub...) + } else if f.Mode()&os.ModeDevice == os.ModeDevice { + out = append(out, filepath.Join(path, f.Name())) } } return out, nil } + +func GetHostDeviceNodes() ([]string, error) { + return getNodes("/dev") +} From b6c65df093111072bb44d42d650b591adb1bbbe0 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 22 May 2014 22:50:41 +0000 Subject: [PATCH 225/400] update test Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- pkg/libcontainer/container.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/libcontainer/container.json b/pkg/libcontainer/container.json index 7156260bc..ba8117091 100644 --- a/pkg/libcontainer/container.json +++ b/pkg/libcontainer/container.json @@ -45,11 +45,11 @@ } ], "required_device_nodes": [ - "null", - "zero", - "full", - "random", - "urandom", - "tty" + "/dev/null", + "/dev/zero", + "/dev/full", + "/dev/random", + "/dev/urandom", + "/dev/tty" ] } From c8ada301baf66360af6a3445b7c2abd629da3fd1 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 23 May 2014 00:03:00 +0000 Subject: [PATCH 226/400] Fix add hang when dest is . Fixes #3960 Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- server/buildfile.go | 97 +++++++++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/server/buildfile.go b/server/buildfile.go index b3c614b9f..d20666444 100644 --- a/server/buildfile.go +++ b/server/buildfile.go @@ -415,17 +415,18 @@ func (b *buildFile) addContext(container *daemon.Container, orig, dest string, r } // Preserve the trailing '/' - if strings.HasSuffix(dest, "/") { + if strings.HasSuffix(dest, "/") || dest == "." { destPath = destPath + "/" } + destStat, err := os.Stat(destPath) if err != nil { - if os.IsNotExist(err) { - destExists = false - } else { + if !os.IsNotExist(err) { return err } + destExists = false } + fi, err := os.Stat(origPath) if err != nil { if os.IsNotExist(err) { @@ -434,57 +435,29 @@ func (b *buildFile) addContext(container *daemon.Container, orig, dest string, r return err } - fixPermsR := func(destPath string, uid, gid int) error { - return filepath.Walk(destPath, func(path string, info os.FileInfo, err error) error { - if err := os.Lchown(path, uid, gid); err != nil && !os.IsNotExist(err) { - return err - } - return nil - }) - } - if fi.IsDir() { - if err := archive.CopyWithTar(origPath, destPath); err != nil { - return err - } - if destExists { - files, err := ioutil.ReadDir(origPath) - if err != nil { - return err - } - for _, file := range files { - if err := fixPermsR(filepath.Join(destPath, file.Name()), 0, 0); err != nil { - return err - } - } - } else { - if err := fixPermsR(destPath, 0, 0); err != nil { - return err - } - } - return nil - } - - // First try to unpack the source as an archive - // to support the untar feature we need to clean up the path a little bit - // because tar is very forgiving. First we need to strip off the archive's - // filename from the path but this is only added if it does not end in / . - tarDest := destPath - if strings.HasSuffix(tarDest, "/") { - tarDest = filepath.Dir(destPath) + return copyAsDirectory(origPath, destPath, destExists) } // If we are adding a remote file, do not try to untar it if !remote { + // First try to unpack the source as an archive + // to support the untar feature we need to clean up the path a little bit + // because tar is very forgiving. First we need to strip off the archive's + // filename from the path but this is only added if it does not end in / . + tarDest := destPath + if strings.HasSuffix(tarDest, "/") { + tarDest = filepath.Dir(destPath) + } + // try to successfully untar the orig if err := archive.UntarPath(origPath, tarDest); err == nil { return nil + } else if err != io.EOF { + utils.Debugf("Couldn't untar %s to %s: %s", origPath, tarDest, err) } - utils.Debugf("Couldn't untar %s to %s: %s", origPath, destPath, err) } - // If that fails, just copy it as a regular file - // but do not use all the magic path handling for the tar path if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil { return err } @@ -497,10 +470,7 @@ func (b *buildFile) addContext(container *daemon.Container, orig, dest string, r resPath = path.Join(destPath, path.Base(origPath)) } - if err := fixPermsR(resPath, 0, 0); err != nil { - return err - } - return nil + return fixPermissions(resPath, 0, 0) } func (b *buildFile) CmdAdd(args string) error { @@ -873,6 +843,37 @@ func stripComments(raw []byte) string { return strings.Join(out, "\n") } +func copyAsDirectory(source, destination string, destinationExists bool) error { + if err := archive.CopyWithTar(source, destination); err != nil { + return err + } + + if destinationExists { + files, err := ioutil.ReadDir(source) + if err != nil { + return err + } + + for _, file := range files { + if err := fixPermissions(filepath.Join(destination, file.Name()), 0, 0); err != nil { + return err + } + } + return nil + } + + return fixPermissions(destination, 0, 0) +} + +func fixPermissions(destination string, uid, gid int) error { + return filepath.Walk(destination, func(path string, info os.FileInfo, err error) error { + if err := os.Lchown(path, uid, gid); err != nil && !os.IsNotExist(err) { + return err + } + return nil + }) +} + func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeCache, rm bool, forceRm bool, outOld io.Writer, sf *utils.StreamFormatter, auth *registry.AuthConfig, authConfigFile *registry.ConfigFile) BuildFile { return &buildFile{ daemon: srv.daemon, From 1ce5457d57d783d6c61557681970bf0977621e78 Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Thu, 22 May 2014 15:12:41 -0700 Subject: [PATCH 227/400] adding test for hanging ADD src . Docker-DCO-1.1-Signed-off-by: Tibor Vass (github: tiborvass) --- .../TestAdd/SingleFileToWorkdir/Dockerfile | 2 + integration-cli/docker_cli_build_test.go | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 integration-cli/build_tests/TestAdd/SingleFileToWorkdir/Dockerfile diff --git a/integration-cli/build_tests/TestAdd/SingleFileToWorkdir/Dockerfile b/integration-cli/build_tests/TestAdd/SingleFileToWorkdir/Dockerfile new file mode 100644 index 000000000..3f076718f --- /dev/null +++ b/integration-cli/build_tests/TestAdd/SingleFileToWorkdir/Dockerfile @@ -0,0 +1,2 @@ +FROM busybox +ADD test_file . diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 8e4ba0551..4bbe4c6dc 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" "testing" + "time" ) func TestBuildCacheADD(t *testing.T) { @@ -77,6 +78,42 @@ func TestAddSingleFileToRoot(t *testing.T) { logDone("build - add single file to root") } +// Issue #3960: "ADD src ." hangs +func TestAddSingleFileToWorkdir(t *testing.T) { + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestAdd", "SingleFileToWorkdir") + f, err := os.OpenFile(filepath.Join(buildDirectory, "test_file"), os.O_CREATE, 0644) + if err != nil { + t.Fatal(err) + } + f.Close() + buildCmd := exec.Command(dockerBinary, "build", "-t", "testaddimg", ".") + buildCmd.Dir = buildDirectory + done := make(chan error) + go func() { + out, exitCode, err := runCommandWithOutput(buildCmd) + if err != nil || exitCode != 0 { + done <- fmt.Errorf("build failed to complete: %s %v", out, err) + return + } + done <- nil + }() + select { + case <-time.After(5 * time.Second): + if err := buildCmd.Process.Kill(); err != nil { + fmt.Printf("could not kill build (pid=%d): %v\n", buildCmd.Process.Pid, err) + } + t.Fatal("build timed out") + case err := <-done: + if err != nil { + t.Fatal(err) + } + } + + deleteImages("testaddimg") + + logDone("build - add single file to workdir") +} + func TestAddSingleFileToExistDir(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestAdd") buildCmd := exec.Command(dockerBinary, "build", "-t", "testaddimg", "SingleFileToExistDir") From b315c380f4acd65cc0428009702f99a266f96c59 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 23 May 2014 01:24:58 +0000 Subject: [PATCH 228/400] Add wait flag to iptables Fixes #1573 Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/iptables/iptables.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/iptables/iptables.go b/pkg/iptables/iptables.go index 4cdd67ef7..6809daef6 100644 --- a/pkg/iptables/iptables.go +++ b/pkg/iptables/iptables.go @@ -150,7 +150,7 @@ func Raw(args ...string) ([]byte, error) { if os.Getenv("DEBUG") != "" { fmt.Printf("[DEBUG] [iptables]: %s, %v\n", path, args) } - output, err := exec.Command(path, args...).CombinedOutput() + output, err := exec.Command(path, append([]string{"--wait"}, args...)...).CombinedOutput() if err != nil { return nil, fmt.Errorf("iptables failed: iptables %v: %s (%s)", strings.Join(args, " "), output, err) } From c0529e5fc1a50f6fff47646bcc2461aeef20f906 Mon Sep 17 00:00:00 2001 From: James Mills Date: Sat, 24 May 2014 01:18:00 +1000 Subject: [PATCH 229/400] Updated installation docs for CRUX as a Docker Host. Docker-DCO-1.1-Signed-off-by: James Mills (github: therealprologic) --- docs/sources/installation/cruxlinux.md | 68 ++++++++++---------------- 1 file changed, 26 insertions(+), 42 deletions(-) diff --git a/docs/sources/installation/cruxlinux.md b/docs/sources/installation/cruxlinux.md index d1a4de736..6f0bfff74 100644 --- a/docs/sources/installation/cruxlinux.md +++ b/docs/sources/installation/cruxlinux.md @@ -17,50 +17,24 @@ page_keywords: crux linux, virtualization, Docker, documentation, installation > some binaries to be updated and published. Installing on CRUX Linux can be handled via the ports from [James -Mills](http://prologic.shortcircuit.net.au/): +Mills](http://prologic.shortcircuit.net.au/) and are included in the +official [contrib](http://crux.nu/portdb/?a=repo&q=contrib) ports: -- [docker](https://bitbucket.org/prologic/ports/src/tip/docker/) -- [docker-bin](https://bitbucket.org/prologic/ports/src/tip/docker-bin/) -- [docker-git](https://bitbucket.org/prologic/ports/src/tip/docker-git/) +- docker +- docker-bin The `docker` port will install the latest tagged version of Docker. The `docker-bin` port will -install the latest tagged versin of Docker from upstream built binaries. -The `docker-git` package will build from the current -master branch. +install the latest tagged version of Docker from upstream built binaries. ## Installation -For the time being (*until the CRUX Docker port(s) get into the official -contrib repository*) you will need to install [James -Mills`](https://bitbucket.org/prologic/ports) ports repository. You can -do so via: +Assuming you have contrib enabled, update your ports tree and install docker (*as root*): -Download the `httpup` file to -`/etc/ports/`: + # prt-get depinst docker - $ curl -q -o - http://crux.nu/portdb/?a=getup&q=prologic > /etc/ports/prologic.httpup +You can install `docker-bin` instead if you wish to avoid compilation time. -Add `prtdir /usr/ports/prologic` to -`/etc/prt-get.conf`: - - $ vim /etc/prt-get.conf - - # or: - $ echo "prtdir /usr/ports/prologic" >> /etc/prt-get.conf - -Update ports and prt-get cache: - - $ ports -u - $ prt-get cache - -To install (*and its dependencies*): - - $ prt-get depinst docker - -Use `docker-bin` for the upstream binary or -`docker-git` to build and install from the master -branch from git. ## Kernel Requirements @@ -68,24 +42,34 @@ To have a working **CRUX+Docker** Host you must ensure your Kernel has the necessary modules enabled for LXC containers to function correctly and Docker Daemon to work properly. -Please read the `README.rst`: +Please read the `README`: $ prt-get readme docker -There is a `test_kernel_config.sh` script in the -above ports which you can use to test your Kernel configuration: +The `docker` and `docker-bin` ports install the `contrib/check-config.sh` +script provided by the Docker contributors for checking your kernel +configuration as a suitable Docker Host. - $ cd /usr/ports/prologic/docker - $ ./test_kernel_config.sh /usr/src/linux/.config + $ /usr/share/docker/check-config.sh ## Starting Docker -There is a rc script created for Docker. To start the Docker service: +There is a rc script created for Docker. To start the Docker service (*as root*): - $ sudo su - - $ /etc/rc.d/docker start + # /etc/rc.d/docker start To start on system boot: - Edit `/etc/rc.conf` - Put `docker` into the `SERVICES=(...)` array after `net`. + +## Issues + +If you have any issues please file a bug with the +[CRUX Bug Tracker](http://crux.nu/bugs/). + +## Support + +For support contact the [CRUX Mailing List](http://crux.nu/Main/MailingLists) +or join CRUX's [IRC Channels](http://crux.nu/Main/IrcChannels). on the +[FreeNode](http://freenode.net/) IRC Network. From a3ff8a98f770ae35103e3ce3c4221c00b4efcb7f Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Fri, 23 May 2014 11:43:46 -0400 Subject: [PATCH 230/400] We need to umount /var/lib/docker when the daemon exits. Currently we are leaving it bind mounted on stop. Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) --- daemon/daemon.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/daemon/daemon.go b/daemon/daemon.go index 210e1a540..4ea6416ca 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -900,6 +900,10 @@ func (daemon *Daemon) Close() error { utils.Errorf("daemon.containerGraph.Close(): %s", err.Error()) errorsStrings = append(errorsStrings, err.Error()) } + if err := mount.Unmount(daemon.config.Root); err != nil { + utils.Errorf("daemon.Umount(%s): %s", daemon.config.Root, err.Error()) + errorsStrings = append(errorsStrings, err.Error()) + } if len(errorsStrings) > 0 { return fmt.Errorf("%s", strings.Join(errorsStrings, ", ")) } From 2a35e41a9c825b6891440015ae95633fb84b9604 Mon Sep 17 00:00:00 2001 From: Jim Perrin Date: Fri, 23 May 2014 08:53:11 -0500 Subject: [PATCH 231/400] Squashed for pull request Updated documentation to include CentOS installation instructions. Extraneous line removal, description change Docker-DCO-1.1-Signed-off-by: Jim Perrin (github: jimperrin) --- docs/mkdocs.yml | 1 + docs/sources/installation/centos.md | 89 +++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 docs/sources/installation/centos.md diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index e3f9e2819..1e6b8b16d 100755 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -35,6 +35,7 @@ pages: - ['installation/mac.md', 'Installation', 'Mac OS X'] - ['installation/ubuntulinux.md', 'Installation', 'Ubuntu'] - ['installation/rhel.md', 'Installation', 'Red Hat Enterprise Linux'] +- ['installation/centos.md', 'Installation', 'CentOS'] - ['installation/debian.md', 'Installation', 'Debian'] - ['installation/gentoolinux.md', 'Installation', 'Gentoo'] - ['installation/google.md', 'Installation', 'Google Cloud Platform'] diff --git a/docs/sources/installation/centos.md b/docs/sources/installation/centos.md new file mode 100644 index 000000000..74d1ec858 --- /dev/null +++ b/docs/sources/installation/centos.md @@ -0,0 +1,89 @@ +page_title: Installation on CentOS +page_description: This page provides documentation for installing docker on CentOS +page_keywords: Docker, Docker documentation, requirements, linux, centos, epel, docker.io, docker-io + +# CentOS + +> **Note**: +> Docker is still under heavy development! We don't recommend using it in +> production yet, but we're getting closer with each release. Please see +> our blog post, [Getting to Docker 1.0]( +> http://blog.docker.io/2013/08/getting-to-docker-1-0/) + +> **Note**: +> This is a community contributed installation path. The only `official` +> installation is using the [*Ubuntu*](../ubuntulinux/#ubuntu-linux) +> installation path. This version may be out of date because it depends on +> some binaries to be updated and published + +The Docker package is available via the EPEL repository. These instructions work +for CentOS 6 and later. They will likely work for other binary compatible EL6 +distributions such as Scientific Linux, but they haven't been tested. + +Please note that this package is part of [Extra Packages for Enterprise +Linux (EPEL)](https://fedoraproject.org/wiki/EPEL), a community effort +to create and maintain additional packages for the RHEL distribution. + +Also note that due to the current Docker limitations, Docker is able to +run only on the **64 bit** architecture. + +To run Docker, you will need [CentOS6](http://www.centos.org) or higher, with +a kernel version 2.6.32-431 or higher as this has specific kernel fixes +to allow Docker to run. + +## Installation + +Firstly, you need to ensure you have the EPEL repository enabled. Please +follow the [EPEL installation instructions]( +https://fedoraproject.org/wiki/EPEL#How_can_I_use_these_extra_packages.3F). + +The `docker-io` package provides Docker on EPEL. + +If you already have the (unrelated) `docker` package +installed, it will conflict with `docker-io`. +There's a [bug report]( +https://bugzilla.redhat.com/show_bug.cgi?id=1043676) filed for it. +To proceed with `docker-io` installation, please remove `docker` first. + +Next, let's install the `docker-io` package which +will install Docker on our host. + + sudo yum install docker-io + +Now that it's installed, let's start the Docker daemon. + + sudo service docker start + +If we want Docker to start at boot, we should also: + + sudo chkconfig docker on + +Now let's verify that Docker is working. First we'll need to get the latest +centos image. + + sudo docker pull centos:latest + +Next we'll make sure that we can see the image by running: + + sudo docker images centos + +This should generate some output similar to: + + [your-user@lappy ~]# sudo docker images centos + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + centos latest 0b443ba03958 2 hours ago 297.6 MB + +Run a simple bash shell to test the image: + sudo docker run -i -t centos /bin/bash + +If everything is working properly, you'll get a simple bash prompt. Type exit to continue. + +**Done!** +You can either continue with the [*Hello World*](/examples/hello_world/#hello-world) example, +or explore and build on the images yourself. + +## Issues? + +If you have any issues - please report them directly in the +[CentOS bug tracker]( +http://bugs.centos.org). From 0c0ffb777354ab0d65fdb92fac47272584b2d217 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 23 May 2014 10:27:09 -0600 Subject: [PATCH 232/400] Add more required cgroup subsystems to check-config.sh (specifically, SCHED for cpu and CPUACCT for cpuacct) Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- contrib/check-config.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/check-config.sh b/contrib/check-config.sh index 498ede8af..8dd618cb6 100755 --- a/contrib/check-config.sh +++ b/contrib/check-config.sh @@ -116,7 +116,7 @@ fi flags=( NAMESPACES {NET,PID,IPC,UTS}_NS DEVPTS_MULTIPLE_INSTANCES - CGROUPS CGROUP_DEVICE + CGROUPS CGROUP_CPUACCT CGROUP_DEVICE CGROUP_SCHED MACVLAN VETH BRIDGE NF_NAT_IPV4 IP_NF_TARGET_MASQUERADE NETFILTER_XT_MATCH_{ADDRTYPE,CONNTRACK} From 1e74cd0e01009bfa8afb6058f748f282da5200d0 Mon Sep 17 00:00:00 2001 From: Nathan LeClaire Date: Tue, 20 May 2014 14:38:41 -0700 Subject: [PATCH 233/400] Fix a few issues. -single-dash => --double-dash for a few flags, and also there was a missing "B", as in "Container B". Docker-DCO-1.1-Signed-off-by: Nathan LeClaire (github: nathanleclaire) --- docs/sources/use/working_with_links_names.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/use/working_with_links_names.md b/docs/sources/use/working_with_links_names.md index 4b7030d24..d69f3f175 100644 --- a/docs/sources/use/working_with_links_names.md +++ b/docs/sources/use/working_with_links_names.md @@ -30,7 +30,7 @@ name using the `docker ps` command. Links allow containers to discover and securely communicate with each other by using the flag `--link name:alias`. Inter-container communication can be disabled with the daemon flag `--icc=false`. With -this flag set to `false`, Container A cannot access Container unless +this flag set to `false`, Container A cannot access Container B unless explicitly allowed via a link. This is a huge win for securing your containers. When two containers are linked together Docker creates a parent child relationship between the containers. The parent container From 581e8e891886e6db387ed27aabda7dd8f1d14174 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 23 May 2014 11:31:01 -0700 Subject: [PATCH 234/400] Update integration tests with --net flag Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- integration-cli/docker_cli_run_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 10b9f6a7c..3be5cbd93 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -251,13 +251,13 @@ func TestDockerRunWorkingDirectory(t *testing.T) { // pinging Google's DNS resolver should fail when we disable the networking func TestDockerRunWithoutNetworking(t *testing.T) { - runCmd := exec.Command(dockerBinary, "run", "--networking=false", "busybox", "ping", "-c", "1", "8.8.8.8") + runCmd := exec.Command(dockerBinary, "run", "--net=none", "busybox", "ping", "-c", "1", "8.8.8.8") out, _, exitCode, err := runCommandWithStdoutStderr(runCmd) if err != nil && exitCode != 1 { t.Fatal(out, err) } if exitCode != 1 { - t.Errorf("--networking=false should've disabled the network; the container shouldn't have been able to ping 8.8.8.8") + t.Errorf("--net=none should've disabled the network; the container shouldn't have been able to ping 8.8.8.8") } runCmd = exec.Command(dockerBinary, "run", "-n=false", "busybox", "ping", "-c", "1", "8.8.8.8") @@ -271,7 +271,7 @@ func TestDockerRunWithoutNetworking(t *testing.T) { deleteAllContainers() - logDone("run - disable networking with --networking=false") + logDone("run - disable networking with --net=none") logDone("run - disable networking with -n=false") } @@ -678,7 +678,7 @@ func TestContainerNetwork(t *testing.T) { // Issue #4681 func TestLoopbackWhenNetworkDisabled(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "--networking=false", "busybox", "ping", "-c", "1", "127.0.0.1") + cmd := exec.Command(dockerBinary, "run", "--net=none", "busybox", "ping", "-c", "1", "127.0.0.1") if _, err := runCommand(cmd); err != nil { t.Fatal(err) } @@ -689,7 +689,7 @@ func TestLoopbackWhenNetworkDisabled(t *testing.T) { } func TestLoopbackOnlyExistsWhenNetworkingDisabled(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "--networking=false", "busybox", "ip", "a", "show", "up") + cmd := exec.Command(dockerBinary, "run", "--net=none", "busybox", "ip", "a", "show", "up") out, _, err := runCommandWithOutput(cmd) if err != nil { t.Fatal(err, out) From 9429fe6f49a4396cdd0ab2f0b88240ff0239a070 Mon Sep 17 00:00:00 2001 From: Ron Smits Date: Fri, 23 May 2014 21:15:44 +0200 Subject: [PATCH 235/400] Update using_supervisord.md changed the version of ubuntu to 13.04. In latest sshd will not work. --- docs/sources/examples/using_supervisord.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/examples/using_supervisord.md b/docs/sources/examples/using_supervisord.md index 29d2fa452..6fc47b0c0 100644 --- a/docs/sources/examples/using_supervisord.md +++ b/docs/sources/examples/using_supervisord.md @@ -30,7 +30,7 @@ install and manage both an SSH daemon and an Apache daemon. Let's start by creating a basic `Dockerfile` for our new image. - FROM ubuntu:latest + FROM ubuntu:13.04 MAINTAINER examples@docker.io RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list RUN apt-get update From 24872379375dd66518f09b8063698c2d1fb08df9 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 23 May 2014 13:22:01 -0700 Subject: [PATCH 236/400] Update ip test to parse new output Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- integration-cli/docker_cli_run_test.go | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 3be5cbd93..b72d8e32c 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -689,18 +689,29 @@ func TestLoopbackWhenNetworkDisabled(t *testing.T) { } func TestLoopbackOnlyExistsWhenNetworkingDisabled(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "--net=none", "busybox", "ip", "a", "show", "up") + cmd := exec.Command(dockerBinary, "run", "--net=none", "busybox", "ip", "-o", "-4", "a", "show", "up") out, _, err := runCommandWithOutput(cmd) if err != nil { t.Fatal(err, out) } - interfaces := regexp.MustCompile(`(?m)^[0-9]+: [a-zA-Z0-9]+`).FindAllString(out, -1) - if len(interfaces) != 1 { - t.Fatalf("Wrong interface count in test container: expected [*: lo], got %s", interfaces) + var ( + count = 0 + parts = strings.Split(out, "\n") + ) + + for _, l := range parts { + if l != "" { + count++ + } } - if !strings.HasSuffix(interfaces[0], ": lo") { - t.Fatalf("Wrong interface in test container: expected [*: lo], got %s", interfaces) + + if count != 1 { + t.Fatalf("Wrong interface count in container %d", count) + } + + if !strings.HasPrefix(out, "1: lo") { + t.Fatalf("Wrong interface in test container: expected [1: lo], got %s", out) } deleteAllContainers() From efa79a09a92b41b07d0aee3c728d98eec5120730 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 23 May 2014 14:28:32 -0600 Subject: [PATCH 237/400] Add specific branch of jpetazzo's busybox to clone from so we can assume nice things about it Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 41f0f9294..dd8ccfd1f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -81,7 +81,7 @@ RUN go get code.google.com/p/go.tools/cmd/cover RUN gem install --no-rdoc --no-ri fpm --version 1.0.2 # Get the "busybox" image source so we can build locally instead of pulling -RUN git clone https://github.com/jpetazzo/docker-busybox.git /docker-busybox +RUN git clone -b buildroot-2014.02 https://github.com/jpetazzo/docker-busybox.git /docker-busybox # Setup s3cmd config RUN /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY' > /.s3cfg From 886d3c939687672c6589120229c05435273b7d59 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 23 May 2014 14:29:31 -0600 Subject: [PATCH 238/400] Simplify "adduser" in the Dockerfile to use the more scripting-friendly "useradd" Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index dd8ccfd1f..283e0a326 100644 --- a/Dockerfile +++ b/Dockerfile @@ -90,9 +90,8 @@ RUN /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_ RUN git config --global user.email 'docker-dummy@example.com' # Add an unprivileged user to be used for tests which need it -RUN adduser unprivilegeduser -RUN groupadd docker -RUN gpasswd -a unprivilegeduser docker +RUN groupadd -r docker +RUN useradd --create-home --gid docker unprivilegeduser VOLUME /var/lib/docker WORKDIR /go/src/github.com/dotcloud/docker From 321b457044f287435780274bef0b4a65231892bc Mon Sep 17 00:00:00 2001 From: Vishnu Kannan Date: Fri, 23 May 2014 19:45:53 +0000 Subject: [PATCH 239/400] Added stats.go which provides strong types for all stats that will be exported by libcontainer. This commit only introduces the strong type. Docker-DCO-1.1-Signed-off-by: Vishnu Kannan (github: vishh) --- pkg/libcontainer/cgroups/stats.go | 59 +++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 pkg/libcontainer/cgroups/stats.go diff --git a/pkg/libcontainer/cgroups/stats.go b/pkg/libcontainer/cgroups/stats.go new file mode 100644 index 000000000..fbcd5dd23 --- /dev/null +++ b/pkg/libcontainer/cgroups/stats.go @@ -0,0 +1,59 @@ +package cgroups + +type ThrottlingData struct { + // Number of periods with throttling active + Periods int64 `json:"periods,omitempty"` + // Number of periods when the container hit its throttling limit. + ThrottledPeriods int64 `json:"throttled_periods,omitempty"` + // Aggregate time the container was throttled for in nanoseconds. + ThrottledTime int64 `json:"throttled_time,omitempty"` +} + +type CpuUsage struct { + // percentage of available CPUs currently being used. + PercentUsage int64 `json:"percent_usage,omitempty"` + // nanoseconds of cpu time consumed over the last 100 ms. + CurrentUsage int64 `json:"current_usage,omitempty"` +} + +type CpuStats struct { + CpuUsage CpuUsage `json:"cpu_usage,omitempty"` + ThrottlingData ThrottlingData `json:"throlling_data,omitempty"` +} + +type MemoryStats struct { + // current res_counter usage for memory + Usage int64 `json:"usage,omitempty"` + // maximum usage ever recorded. + MaxUsage int64 `json:"max_usage,omitempty"` + // TODO(vishh): Export these as stronger types. + // all the stats exported via memory.stat. + Stats map[string]int64 `json:"stats,omitempty"` +} + +type BlkioStatEntry struct { + Major int64 `json:"major,omitempty"` + Minor int64 `json:"minor,omitempty"` + Op string `json:"op,omitempty"` + Value int64 `json:"value,omitempty"` +} + +type BlockioStats struct { + // number of bytes tranferred to and from the block device + IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive,omitempty"` + IoServicedRecursive []BlkioStatEntry `json:"io_serviced_recusrive,omitempty"` + IoQueuedRecursive []BlkioStatEntry `json:"io_queue_recursive,omitempty"` +} + +// TODO(Vishh): Remove freezer from stats since it does not logically belong in stats. +type FreezerStats struct { + ParentState string `json:"parent_state,omitempty"` + SelfState string `json:"self_state,omitempty"` +} + +type Stats struct { + CpuStats CpuStats `json:"cpu_stats,omitempty"` + MemoryStats MemoryStats `json:"memory_stats,omitempty"` + BlockioStats BlockioStats `json:"blockio_stats,omitempty"` + FreezerStats FreezerStats `json:"freezer_stats,omitempty"` +} From 0be44d1a0a2dfd70c35367d7ee8383feeab8d3b9 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 23 May 2014 20:51:06 +0000 Subject: [PATCH 240/400] now busybox as nc Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- integration-cli/docker_cli_nat_test.go | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/integration-cli/docker_cli_nat_test.go b/integration-cli/docker_cli_nat_test.go index 90af933be..3816e5405 100644 --- a/integration-cli/docker_cli_nat_test.go +++ b/integration-cli/docker_cli_nat_test.go @@ -3,22 +3,14 @@ package main import ( "encoding/json" "fmt" - "github.com/dotcloud/docker/daemon" "net" "os/exec" - "path/filepath" "testing" + + "github.com/dotcloud/docker/daemon" ) func TestNetworkNat(t *testing.T) { - ncPath, err := exec.LookPath("nc") - if err != nil { - t.Skip("Test not running with `make test`. Netcat not found: %s", err) - } - ncPath, err = filepath.EvalSymlinks(ncPath) - if err != nil { - t.Fatalf("Error resolving netcat symlink: %s", err) - } iface, err := net.InterfaceByName("eth0") if err != nil { t.Skip("Test not running with `make test`. Interface eth0 not found: %s", err) @@ -34,10 +26,7 @@ func TestNetworkNat(t *testing.T) { t.Fatalf("Error retrieving the up for eth0: %s", err) } - runCmd := exec.Command(dockerBinary, "run", "-d", - "-v", ncPath+":/bin/nc", - "-v", "/lib/x86_64-linux-gnu/libc.so.6:/lib/libc.so.6", "-v", "/lib/x86_64-linux-gnu/libresolv.so.2:/lib/libresolv.so.2", "-v", "/lib/x86_64-linux-gnu/libbsd.so.0:/lib/libbsd.so.0", "-v", "/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2:/lib/ld-linux-x86-64.so.2", - "-p", "8080", "busybox", "/bin/nc", "-lp", "8080") + runCmd := exec.Command(dockerBinary, "run", "-d", "-p", "8080", "busybox", "nc", "-lp", "8080") out, _, err := runCommandWithOutput(runCmd) errorOut(err, t, fmt.Sprintf("run1 failed with errors: %v (%s)", err, out)) @@ -60,10 +49,7 @@ func TestNetworkNat(t *testing.T) { t.Fatal("Port 8080/tcp not found in NetworkSettings") } - runCmd = exec.Command(dockerBinary, "run", - "-v", ncPath+":/bin/nc", - "-v", "/lib/x86_64-linux-gnu/libc.so.6:/lib/libc.so.6", "-v", "/lib/x86_64-linux-gnu/libresolv.so.2:/lib/libresolv.so.2", "-v", "/lib/x86_64-linux-gnu/libbsd.so.0:/lib/libbsd.so.0", "-v", "/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2:/lib/ld-linux-x86-64.so.2", - "-p", "8080", "busybox", "sh", "-c", fmt.Sprintf("echo hello world | /bin/nc -w 30 %s %s", ifaceIp, port8080[0].HostPort)) + runCmd = exec.Command(dockerBinary, "run", "-p", "8080", "busybox", "sh", "-c", fmt.Sprintf("echo hello world | nc -w 30 %s %s", ifaceIp, port8080[0].HostPort)) out, _, err = runCommandWithOutput(runCmd) errorOut(err, t, fmt.Sprintf("run2 failed with errors: %v (%s)", err, out)) From 034babf1753741184c1155a7346ecec86fc51e2c Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 23 May 2014 14:18:50 -0700 Subject: [PATCH 241/400] Add check for iptables xlock support Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/iptables/iptables.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/pkg/iptables/iptables.go b/pkg/iptables/iptables.go index 6809daef6..b44c45223 100644 --- a/pkg/iptables/iptables.go +++ b/pkg/iptables/iptables.go @@ -20,6 +20,7 @@ const ( var ( ErrIptablesNotFound = errors.New("Iptables not found") nat = []string{"-t", "nat"} + supportsXlock = false ) type Chain struct { @@ -27,6 +28,10 @@ type Chain struct { Bridge string } +func init() { + supportsXlock = exec.Command("iptables", "--wait", "-L", "-n").Run() == nil +} + func NewChain(name, bridge string) (*Chain, error) { if output, err := Raw("-t", "nat", "-N", name); err != nil { return nil, err @@ -147,12 +152,19 @@ func Raw(args ...string) ([]byte, error) { if err != nil { return nil, ErrIptablesNotFound } + + if supportsXlock { + args = append([]string{"--wait"}, args...) + } + if os.Getenv("DEBUG") != "" { fmt.Printf("[DEBUG] [iptables]: %s, %v\n", path, args) } - output, err := exec.Command(path, append([]string{"--wait"}, args...)...).CombinedOutput() + + output, err := exec.Command(path, args...).CombinedOutput() if err != nil { return nil, fmt.Errorf("iptables failed: iptables %v: %s (%s)", strings.Join(args, " "), output, err) } + return output, err } From e89cb9a5e0105afbb0c1ba1176bda07d110295bc Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Fri, 23 May 2014 17:16:56 -0700 Subject: [PATCH 242/400] docs/reference/builder: fix USER doc Docker-DCO-1.1-Signed-off-by: Johan Euphrosine (github: proppy) --- docs/sources/reference/builder.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index cd7f4eff8..6a4ae4ad2 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -346,7 +346,8 @@ instructions via the Docker client, refer to [*Share Directories via Volumes*]( USER daemon -The `USER` instruction sets the username or UID to use when running the image. +The `USER` instruction sets the username or UID to use when running the image +and for any following `RUN` directives. ## WORKDIR From 6ec86cb6e517bfb5ded818244b9db9510a2ed0b9 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 23 May 2014 17:51:16 -0700 Subject: [PATCH 243/400] Improve name generation on concurrent requests Fixes #2586 This fixes a few races where the name generator asks if a name is free but another container takes the name before it can be reserved. This solves this by generating the name and setting it. If the set fails with a non unique error then we try again. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/daemon.go | 75 ++++++++++++++-------- daemon/utils.go | 19 +----- pkg/namesgenerator/names-generator.go | 21 +++--- pkg/namesgenerator/names-generator_test.go | 28 +------- 4 files changed, 63 insertions(+), 80 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index 4ea6416ca..78dee6252 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -29,6 +29,7 @@ import ( "github.com/dotcloud/docker/pkg/graphdb" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/mount" + "github.com/dotcloud/docker/pkg/namesgenerator" "github.com/dotcloud/docker/pkg/networkfs/resolvconf" "github.com/dotcloud/docker/pkg/selinux" "github.com/dotcloud/docker/pkg/sysinfo" @@ -250,20 +251,15 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err func (daemon *Daemon) ensureName(container *Container) error { if container.Name == "" { - name, err := generateRandomName(daemon) + name, err := daemon.generateNewName(container.ID) if err != nil { - name = utils.TruncateID(container.ID) + return err } container.Name = name if err := container.ToDisk(); err != nil { utils.Debugf("Error saving container name %s", err) } - if !daemon.containerGraph.Exists(name) { - if _, err := daemon.containerGraph.Set(name, container.ID); err != nil { - utils.Debugf("Setting default id - %s", err) - } - } } return nil } @@ -370,12 +366,8 @@ func (daemon *Daemon) restore() error { // Any containers that are left over do not exist in the graph for _, container := range containers { // Try to set the default name for a container if it exists prior to links - container.Name, err = generateRandomName(daemon) + container.Name, err = daemon.generateNewName(container.ID) if err != nil { - container.Name = utils.TruncateID(container.ID) - } - - if _, err := daemon.containerGraph.Set(container.Name, container.ID); err != nil { utils.Debugf("Setting default id - %s", err) } registerContainer(container) @@ -470,42 +462,75 @@ func (daemon *Daemon) generateIdAndName(name string) (string, string, error) { ) if name == "" { - name, err = generateRandomName(daemon) - if err != nil { - name = utils.TruncateID(id) - } - } else { - if !validContainerNamePattern.MatchString(name) { - return "", "", fmt.Errorf("Invalid container name (%s), only %s are allowed", name, validContainerNameChars) + if name, err = daemon.generateNewName(id); err != nil { + return "", "", err } + return id, name, nil } + + if name, err = daemon.reserveName(id, name); err != nil { + return "", "", err + } + + return id, name, nil +} + +func (daemon *Daemon) reserveName(id, name string) (string, error) { + if !validContainerNamePattern.MatchString(name) { + return "", fmt.Errorf("Invalid container name (%s), only %s are allowed", name, validContainerNameChars) + } + if name[0] != '/' { name = "/" + name } - // Set the enitity in the graph using the default name specified + if _, err := daemon.containerGraph.Set(name, id); err != nil { if !graphdb.IsNonUniqueNameError(err) { - return "", "", err + return "", err } conflictingContainer, err := daemon.GetByName(name) if err != nil { if strings.Contains(err.Error(), "Could not find entity") { - return "", "", err + return "", err } // Remove name and continue starting the container if err := daemon.containerGraph.Delete(name); err != nil { - return "", "", err + return "", err } } else { nameAsKnownByUser := strings.TrimPrefix(name, "/") - return "", "", fmt.Errorf( + return "", fmt.Errorf( "Conflict, The name %s is already assigned to %s. You have to delete (or rename) that container to be able to assign %s to a container again.", nameAsKnownByUser, utils.TruncateID(conflictingContainer.ID), nameAsKnownByUser) } } - return id, name, nil + return name, nil +} + +func (daemon *Daemon) generateNewName(id string) (string, error) { + var name string + for i := 1; i < 6; i++ { + name = namesgenerator.GetRandomName(i) + if name[0] != '/' { + name = "/" + name + } + + if _, err := daemon.containerGraph.Set(name, id); err != nil { + if !graphdb.IsNonUniqueNameError(err) { + return "", err + } + continue + } + return name, nil + } + + name = "/" + utils.TruncateID(id) + if _, err := daemon.containerGraph.Set(name, id); err != nil { + return "", err + } + return name, nil } func (daemon *Daemon) generateHostname(id string, config *runconfig.Config) { diff --git a/daemon/utils.go b/daemon/utils.go index 15b62e2a0..d60d98515 100644 --- a/daemon/utils.go +++ b/daemon/utils.go @@ -2,10 +2,10 @@ package daemon import ( "fmt" - "github.com/dotcloud/docker/nat" - "github.com/dotcloud/docker/pkg/namesgenerator" - "github.com/dotcloud/docker/runconfig" "strings" + + "github.com/dotcloud/docker/nat" + "github.com/dotcloud/docker/runconfig" ) func migratePortMappings(config *runconfig.Config, hostConfig *runconfig.HostConfig) error { @@ -49,16 +49,3 @@ func mergeLxcConfIntoOptions(hostConfig *runconfig.HostConfig, driverConfig map[ driverConfig["lxc"] = lxc } } - -type checker struct { - daemon *Daemon -} - -func (c *checker) Exists(name string) bool { - return c.daemon.containerGraph.Exists("/" + name) -} - -// Generate a random and unique name -func generateRandomName(daemon *Daemon) (string, error) { - return namesgenerator.GenerateRandomName(&checker{daemon}) -} diff --git a/pkg/namesgenerator/names-generator.go b/pkg/namesgenerator/names-generator.go index 07fadf817..a89e5b29e 100644 --- a/pkg/namesgenerator/names-generator.go +++ b/pkg/namesgenerator/names-generator.go @@ -6,10 +6,6 @@ import ( "time" ) -type NameChecker interface { - Exists(name string) bool -} - var ( left = [...]string{"happy", "jolly", "dreamy", "sad", "angry", "pensive", "focused", "sleepy", "grave", "distracted", "determined", "stoic", "stupefied", "sharp", "agitated", "cocky", "tender", "goofy", "furious", "desperate", "hopeful", "compassionate", "silly", "lonely", "condescending", "naughty", "kickass", "drunk", "boring", "nostalgic", "ecstatic", "insane", "cranky", "mad", "jovial", "sick", "hungry", "thirsty", "elegant", "backstabbing", "clever", "trusting", "loving", "suspicious", "berserk", "high", "romantic", "prickly", "evil"} // Docker 0.7.x generates names from notable scientists and hackers. @@ -79,16 +75,17 @@ var ( right = [...]string{"lovelace", "franklin", "tesla", "einstein", "bohr", "davinci", "pasteur", "nobel", "curie", "darwin", "turing", "ritchie", "torvalds", "pike", "thompson", "wozniak", "galileo", "euclid", "newton", "fermat", "archimedes", "poincare", "heisenberg", "feynman", "hawking", "fermi", "pare", "mccarthy", "engelbart", "babbage", "albattani", "ptolemy", "bell", "wright", "lumiere", "morse", "mclean", "brown", "bardeen", "brattain", "shockley", "goldstine", "hoover", "hopper", "bartik", "sammet", "jones", "perlman", "wilson", "kowalevski", "hypatia", "goodall", "mayer", "elion", "blackwell", "lalande", "kirch", "ardinghelli", "colden", "almeida", "leakey", "meitner", "mestorf", "rosalind", "sinoussi", "carson", "mcclintock", "yonath"} ) -func GenerateRandomName(checker NameChecker) (string, error) { - retry := 5 +func GetRandomName(retry int) string { rand.Seed(time.Now().UnixNano()) + +begin: name := fmt.Sprintf("%s_%s", left[rand.Intn(len(left))], right[rand.Intn(len(right))]) - for checker != nil && checker.Exists(name) && retry > 0 || name == "boring_wozniak" /* Steve Wozniak is not boring */ { + if name == "boring_wozniak" /* Steve Wozniak is not boring */ { + goto begin + } + + if retry > 0 { name = fmt.Sprintf("%s%d", name, rand.Intn(10)) - retry = retry - 1 } - if retry == 0 { - return name, fmt.Errorf("Error generating random name") - } - return name, nil + return name } diff --git a/pkg/namesgenerator/names-generator_test.go b/pkg/namesgenerator/names-generator_test.go index bcee7c86a..2652d42ab 100644 --- a/pkg/namesgenerator/names-generator_test.go +++ b/pkg/namesgenerator/names-generator_test.go @@ -4,35 +4,9 @@ import ( "testing" ) -type FalseChecker struct{} - -func (n *FalseChecker) Exists(name string) bool { - return false -} - -type TrueChecker struct{} - -func (n *TrueChecker) Exists(name string) bool { - return true -} - -func TestGenerateRandomName(t *testing.T) { - if _, err := GenerateRandomName(&FalseChecker{}); err != nil { - t.Error(err) - } - - if _, err := GenerateRandomName(&TrueChecker{}); err == nil { - t.Error("An error was expected") - } - -} - // Make sure the generated names are awesome func TestGenerateAwesomeNames(t *testing.T) { - name, err := GenerateRandomName(&FalseChecker{}) - if err != nil { - t.Error(err) - } + name := GetRandomName(0) if !isAwesome(name) { t.Fatalf("Generated name '%s' is not awesome.", name) } From 328d2cba116067a2ad0f161b9ee098ed024825b3 Mon Sep 17 00:00:00 2001 From: cyphar Date: Fri, 23 May 2014 17:48:01 +1000 Subject: [PATCH 244/400] daemon: container: properly handle paths with symlink path components This patch fixes the incorrect handling of paths which contain a symlink as a path component when copying data from a container. Essentially, this patch changes the container.Copy() method to first "resolve" the resource by resolving all of symlinks encountered in the path relative to the container's rootfs (using pkg/symlink). Docker-DCO-1.1-Signed-off-by: Aleksa Sarai (github: cyphar) --- daemon/container.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/daemon/container.go b/daemon/container.go index e8bc7d478..2ae263289 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -25,6 +25,7 @@ import ( "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/networkfs/etchosts" "github.com/dotcloud/docker/pkg/networkfs/resolvconf" + "github.com/dotcloud/docker/pkg/symlink" "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" ) @@ -760,7 +761,13 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) { var filter []string - basePath := container.getResourcePath(resource) + resPath := container.getResourcePath(resource) + basePath, err := symlink.FollowSymlinkInScope(resPath, container.basefs) + if err != nil { + container.Unmount() + return nil, err + } + stat, err := os.Stat(basePath) if err != nil { container.Unmount() @@ -780,6 +787,7 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) { Includes: filter, }) if err != nil { + container.Unmount() return nil, err } return utils.NewReadCloserWrapper(archive, func() error { From ff24a328765335d9bff1babaac4fac6cf4180af6 Mon Sep 17 00:00:00 2001 From: cyphar Date: Fri, 23 May 2014 22:42:46 +1000 Subject: [PATCH 245/400] integration-cli: cp: added symlink-related tests This patch adds cli integration tests for #5619, which are tests to ensure that symlinks are kept relative to the container rootfs (even when a path component). Docker-DCO-1.1-Signed-off-by: Aleksa Sarai (github: cyphar) --- integration-cli/docker_cli_cp_test.go | 128 ++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/integration-cli/docker_cli_cp_test.go b/integration-cli/docker_cli_cp_test.go index 7421ed0fa..aecc68edb 100644 --- a/integration-cli/docker_cli_cp_test.go +++ b/integration-cli/docker_cli_cp_test.go @@ -208,6 +208,134 @@ func TestCpAbsolutePath(t *testing.T) { logDone("cp - absolute paths relative to container's rootfs") } +// Test for #5619 +// Check that absolute symlinks are still relative to the container's rootfs +func TestCpAbsoluteSymlink(t *testing.T) { + out, exitCode, err := cmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpFullPath+" container_path") + if err != nil || exitCode != 0 { + t.Fatal("failed to create a container", out, err) + } + + cleanedContainerID := stripTrailingCharacters(out) + defer deleteContainer(cleanedContainerID) + + out, _, err = cmd(t, "wait", cleanedContainerID) + if err != nil || stripTrailingCharacters(out) != "0" { + t.Fatal("failed to set up container", out, err) + } + + if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { + t.Fatal(err) + } + + hostFile, err := os.Create(cpFullPath) + if err != nil { + t.Fatal(err) + } + defer hostFile.Close() + defer os.RemoveAll(cpTestPathParent) + + fmt.Fprintf(hostFile, "%s", cpHostContents) + + tmpdir, err := ioutil.TempDir("", "docker-integration") + + if err != nil { + t.Fatal(err) + } + + tmpname := filepath.Join(tmpdir, cpTestName) + defer os.RemoveAll(tmpdir) + + path := filepath.Join("/", "container_path") + + _, _, err = cmd(t, "cp", cleanedContainerID+":"+path, tmpdir) + if err != nil { + t.Fatalf("couldn't copy from absolute path: %s:%s %s", cleanedContainerID, path, err) + } + + file, _ := os.Open(tmpname) + defer file.Close() + + test, err := ioutil.ReadAll(file) + if err != nil { + t.Fatal(err) + } + + if string(test) == cpHostContents { + t.Errorf("output matched host file -- absolute symlink can escape container rootfs") + } + + if string(test) != cpContainerContents { + t.Errorf("output doesn't match the input for absolute symlink") + } + + logDone("cp - absolute symlink relative to container's rootfs") +} + +// Test for #5619 +// Check that symlinks which are part of the resource path are still relative to the container's rootfs +func TestCpSymlinkComponent(t *testing.T) { + out, exitCode, err := cmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpTestPath+" container_path") + if err != nil || exitCode != 0 { + t.Fatal("failed to create a container", out, err) + } + + cleanedContainerID := stripTrailingCharacters(out) + defer deleteContainer(cleanedContainerID) + + out, _, err = cmd(t, "wait", cleanedContainerID) + if err != nil || stripTrailingCharacters(out) != "0" { + t.Fatal("failed to set up container", out, err) + } + + if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { + t.Fatal(err) + } + + hostFile, err := os.Create(cpFullPath) + if err != nil { + t.Fatal(err) + } + defer hostFile.Close() + defer os.RemoveAll(cpTestPathParent) + + fmt.Fprintf(hostFile, "%s", cpHostContents) + + tmpdir, err := ioutil.TempDir("", "docker-integration") + + if err != nil { + t.Fatal(err) + } + + tmpname := filepath.Join(tmpdir, cpTestName) + defer os.RemoveAll(tmpdir) + + path := filepath.Join("/", "container_path", cpTestName) + + _, _, err = cmd(t, "cp", cleanedContainerID+":"+path, tmpdir) + if err != nil { + t.Fatalf("couldn't copy from symlink path component: %s:%s %s", cleanedContainerID, path, err) + } + + file, _ := os.Open(tmpname) + defer file.Close() + + test, err := ioutil.ReadAll(file) + if err != nil { + t.Fatal(err) + } + + if string(test) == cpHostContents { + t.Errorf("output matched host file -- symlink path component can escape container rootfs") + } + + if string(test) != cpContainerContents { + t.Errorf("output doesn't match the input for symlink path component") + } + + logDone("cp - symlink path components relative to container's rootfs") +} + // Check that cp with unprivileged user doesn't return any error func TestCpUnprivilegedUser(t *testing.T) { out, exitCode, err := cmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch "+cpTestName) From 1cb2570a276020749a0b87444b17f4e6ce78b92d Mon Sep 17 00:00:00 2001 From: Jim Perrin Date: Sat, 24 May 2014 08:33:41 -0500 Subject: [PATCH 246/400] Correct appearance of shell output Docker-DCO-1.1-Signed-off-by: Jim Perrin (github: jimperrin) --- docs/sources/installation/centos.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/sources/installation/centos.md b/docs/sources/installation/centos.md index 74d1ec858..97c3402c7 100644 --- a/docs/sources/installation/centos.md +++ b/docs/sources/installation/centos.md @@ -48,33 +48,34 @@ To proceed with `docker-io` installation, please remove `docker` first. Next, let's install the `docker-io` package which will install Docker on our host. - sudo yum install docker-io + $ sudo yum install docker-io Now that it's installed, let's start the Docker daemon. - sudo service docker start + $ sudo service docker start If we want Docker to start at boot, we should also: - sudo chkconfig docker on + $ sudo chkconfig docker on Now let's verify that Docker is working. First we'll need to get the latest centos image. - sudo docker pull centos:latest + $ sudo docker pull centos:latest Next we'll make sure that we can see the image by running: - sudo docker images centos + $ sudo docker images centos This should generate some output similar to: - [your-user@lappy ~]# sudo docker images centos + $ sudo docker images centos REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE centos latest 0b443ba03958 2 hours ago 297.6 MB Run a simple bash shell to test the image: - sudo docker run -i -t centos /bin/bash + + $ sudo docker run -i -t centos /bin/bash If everything is working properly, you'll get a simple bash prompt. Type exit to continue. From 955bfda99a991e71af3352d18a007d39970cc508 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 23 May 2014 18:29:50 +1000 Subject: [PATCH 247/400] Some fixes to the Intro pages Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- docs/sources/index.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/sources/index.md b/docs/sources/index.md index 7860a82f2..16f29f570 100644 --- a/docs/sources/index.md +++ b/docs/sources/index.md @@ -27,16 +27,16 @@ possible. - **Faster delivery of your applications** * We want to help your environment work better. Docker containers, and the work flow that comes with them, helps your developers, - sysadmins, QA folks, and release engineers all work together to get code + sysadmins, QA folks, and release engineers work together to get code into production and doing something useful. We've created a standard container format that allows developers to care about their applications inside containers and sysadmins and operators to care about running the container. This creates a separation of duties that makes managing and deploying code much easier and much more streamlined. - * We make it easy to build new containers and expose how each rapid - iterations and updates as well as visibility of changes. - container is built. This helps everyone in your organization understand - how an application works and how it is built. + * We make it easy to build new containers, enable rapid iteration of + your applications and increase the visibility of changes. This + helps everyone in your organization understand how an application works + and how it is built. * Docker containers are lightweight and fast! Containers have sub-second launch times! With containers you can reduce the cycle time in development, testing and deployment. @@ -44,7 +44,7 @@ possible. - **Deploy and scale more easily** * Docker containers run (almost!) everywhere. You can deploy your containers on desktops, physical servers, virtual machines, into - data centers and to public and private cloud. + data centers and to public and private clouds. * As Docker runs on so many platforms it makes it easy to move your appications around. You can easily move an application from a testing environment into the cloud and back whenever you need. From c52889db27a2af09ed7f6c92f2d6c6fd9737bf63 Mon Sep 17 00:00:00 2001 From: Jeffrey Bolle Date: Sat, 24 May 2014 16:04:57 +0100 Subject: [PATCH 248/400] Ensure networking is up before starting docker This resolves a problem that I have been having where docker starts before networking is up. See issue #5944 for more details. Docker-DCO-1.1-Signed-off-by: Jeffrey Bolle (github: jeffreybolle) --- contrib/init/upstart/docker.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/init/upstart/docker.conf b/contrib/init/upstart/docker.conf index db00e4f47..5a3f88887 100644 --- a/contrib/init/upstart/docker.conf +++ b/contrib/init/upstart/docker.conf @@ -1,6 +1,6 @@ description "Docker daemon" -start on local-filesystems +start on (local-filesystems and net-device-up IFACE!=lo) stop on runlevel [!2345] limit nofile 524288 1048576 limit nproc 524288 1048576 From dd3319636d749bac3d57fbcd1c3cf4d31fded8b7 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sun, 25 May 2014 21:59:43 -0600 Subject: [PATCH 249/400] Fix embarrassing debootstrap typo :) Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- contrib/mkimage/debootstrap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/mkimage/debootstrap b/contrib/mkimage/debootstrap index fe13ccde9..4747a84d3 100755 --- a/contrib/mkimage/debootstrap +++ b/contrib/mkimage/debootstrap @@ -47,7 +47,7 @@ if strings "$rootfsDir/usr/bin/dpkg" | grep -q unsafe-io; then echo 'force-unsafe-io' > "$rootfsDir/etc/dpkg/dpkg.cfg.d/docker-apt-speedup" fi -if [ -d /etc/apt/apt.conf.d ]; then +if [ -d "$rootfsDir/etc/apt/apt.conf.d" ]; then # _keep_ us lean by effectively running "apt-get clean" after every install aptGetClean='"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true";' echo >&2 "+ cat > '$rootfsDir/etc/apt/apt.conf.d/docker-clean'" From 41a913b3906348f6ec3f1929e6d33926c84c1bf1 Mon Sep 17 00:00:00 2001 From: Zac Dover Date: Mon, 19 May 2014 03:21:31 +1000 Subject: [PATCH 250/400] Dockerfile.5.md stub Properly formatted DCO: Docker-DCO-1.1-Signed-off-by: Zac Dover (github: zdover23) Docker-DCO-1.1-Signed-off-by: Zac Dover (github: ) --- contrib/man/md/Dockerfile.5.md | 41 ++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 contrib/man/md/Dockerfile.5.md diff --git a/contrib/man/md/Dockerfile.5.md b/contrib/man/md/Dockerfile.5.md new file mode 100644 index 000000000..c90ebf9a5 --- /dev/null +++ b/contrib/man/md/Dockerfile.5.md @@ -0,0 +1,41 @@ +% DOCKERFILE(1) Docker User Manuals +% Zac Dover +% May 2014 +# NAME + +Dockerfile - automate the steps of creating a Docker image + +# INTRODUCTION +**Dockerfile** is a configuration file that automates the steps of creating a Docker image. Docker can act as a builder and can read instructions from **Dockerfile** to automate the steps that you would otherwise manually perform to create an image. To build an image from a source repository, create a description file called **Dockerfile** at the root of your repository. This file describes the steps that will be taken to assemble the image. When **Dockerfile** has been created, call **docker build** with the path of the source repository as the argument. + +# SYNOPSIS + +INSTRUCTION arguments + +For example: + +FROM image + +# DESCRIPTION + +Dockerfile is a file that automates the steps of creating a Docker image. + +# USAGE + +$ sudo docker build . + -- runs the steps and commits them, building a final image + The path to the source repository defines where to find the context of the build. + The build is run by the docker daemon, not the CLI. The whole context must be + transferred to the daemon. The Docker CLI reports "Uploading context" when the + context is sent to the daemon. + +$ sudo docker build -t repository/tag . + -- specifies a repository and tag at which to save the new image if the build succeeds. + The Docker daemon runs the steps one-by-one, commiting the result to a new image + if necessary before finally outputting the ID of the new image. The Docker + daemon automatically cleans up the context it is given. + +Docker re-uses intermediate images whenever possible. This significantly accelerates the *docker build* process. + +# HISTORY +May 2014, Compiled by Zac Dover (zdover at redhat dot com) based on docker.io Dockerfile documentation. From b26bebac318803d9dbcd908ccc3be4f2fb9a88b8 Mon Sep 17 00:00:00 2001 From: David Corking Date: Mon, 26 May 2014 12:29:27 +0100 Subject: [PATCH 251/400] Update fedora.md The renamed package 'wmdocker' did not make it into Fedora 20. It is still available for rawhide (will be Fedora 21.) --- docs/sources/installation/fedora.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/fedora.md b/docs/sources/installation/fedora.md index 93b5b05b1..20ab4477d 100644 --- a/docs/sources/installation/fedora.md +++ b/docs/sources/installation/fedora.md @@ -32,7 +32,7 @@ it. To proceed with `docker-io` installation on Fedora 19, please remove $ sudo yum -y remove docker -For Fedora 20 and later, the `wmdocker` package will +For Fedora 21 and later, the `wmdocker` package will provide the same functionality as `docker` and will also not conflict with `docker-io`. From 11f7f0bf9b510e09a853a9fd2030a95b72462164 Mon Sep 17 00:00:00 2001 From: LK4D4 Date: Thu, 22 May 2014 23:16:27 +0400 Subject: [PATCH 252/400] Move volume build test to integration-cli Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- integration-cli/docker_cli_build_test.go | 33 ++++++++++++++++++++++++ integration/buildfile_test.go | 20 -------------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 4bbe4c6dc..931a61b57 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -10,6 +10,25 @@ import ( "time" ) +func checkSimpleBuild(t *testing.T, dockerfile, name, inspectFormat, expected string) { + buildCmd := exec.Command(dockerBinary, "build", "-t", name, "-") + buildCmd.Stdin = strings.NewReader(dockerfile) + out, exitCode, err := runCommandWithOutput(buildCmd) + errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) + if err != nil || exitCode != 0 { + t.Fatal("failed to build the image") + } + inspectCmd := exec.Command(dockerBinary, "inspect", "-f", inspectFormat, name) + out, exitCode, err = runCommandWithOutput(inspectCmd) + if err != nil || exitCode != 0 { + t.Fatalf("failed to inspect the image: %s", out) + } + out = strings.TrimSpace(out) + if out != expected { + t.Fatalf("From format %s expected %s, got %s", inspectFormat, expected, out) + } +} + func TestBuildCacheADD(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestBuildCacheADD", "1") buildCmd := exec.Command(dockerBinary, "build", "-t", "testcacheadd1", ".") @@ -413,6 +432,20 @@ func TestBuildRm(t *testing.T) { logDone("build - ensure --rm=false overrides the default") } +func TestBuildWithVolume(t *testing.T) { + checkSimpleBuild(t, + ` + FROM scratch + VOLUME /test + `, + "testbuildimg", + "{{json .config.Volumes}}", + `{"/test":{}}`) + + deleteImages("testbuildimg") + logDone("build - with volume") +} + // TODO: TestCaching // TODO: TestADDCacheInvalidation diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index e113cdf51..c326e94cf 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -414,26 +414,6 @@ func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, u return image, err } -func TestVolume(t *testing.T) { - img, err := buildImage(testContextTemplate{` - from {IMAGE} - volume /test - cmd Hello world - `, nil, nil}, t, nil, true) - if err != nil { - t.Fatal(err) - } - - if len(img.Config.Volumes) == 0 { - t.Fail() - } - for key := range img.Config.Volumes { - if key != "/test" { - t.Fail() - } - } -} - func TestBuildMaintainer(t *testing.T) { img, err := buildImage(testContextTemplate{` from {IMAGE} From 3dd4c5f49977bb9538ae1c39605895fde69c86ee Mon Sep 17 00:00:00 2001 From: LK4D4 Date: Sun, 25 May 2014 22:26:50 +0400 Subject: [PATCH 253/400] Move maintainer build test to integration-cli Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- integration-cli/docker_cli_build_test.go | 14 ++++++++++++++ integration/buildfile_test.go | 14 -------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 931a61b57..696f95b8e 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -446,6 +446,20 @@ func TestBuildWithVolume(t *testing.T) { logDone("build - with volume") } +func TestBuildMaintainer(t *testing.T) { + checkSimpleBuild(t, + ` + FROM scratch + MAINTAINER dockerio + `, + "testbuildimg", + "{{json .author}}", + `"dockerio"`) + + deleteImages("testbuildimg") + logDone("build - maintainer") +} + // TODO: TestCaching // TODO: TestADDCacheInvalidation diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index c326e94cf..89154e343 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -414,20 +414,6 @@ func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, u return image, err } -func TestBuildMaintainer(t *testing.T) { - img, err := buildImage(testContextTemplate{` - from {IMAGE} - maintainer dockerio - `, nil, nil}, t, nil, true) - if err != nil { - t.Fatal(err) - } - - if img.Author != "dockerio" { - t.Fail() - } -} - func TestBuildUser(t *testing.T) { img, err := buildImage(testContextTemplate{` from {IMAGE} From 360fb3d4ea192e28a4d2579589cd16954bb959c1 Mon Sep 17 00:00:00 2001 From: LK4D4 Date: Sun, 25 May 2014 22:28:14 +0400 Subject: [PATCH 254/400] Move user build test to integration-cli Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- integration-cli/docker_cli_build_test.go | 16 ++++++++++++++++ integration/buildfile_test.go | 14 -------------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 696f95b8e..af106a95e 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -460,6 +460,22 @@ func TestBuildMaintainer(t *testing.T) { logDone("build - maintainer") } +func TestBuildUser(t *testing.T) { + checkSimpleBuild(t, + ` + FROM busybox + RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd + USER dockerio + RUN [ $(whoami) = 'dockerio' ] + `, + "testbuildimg", + "{{json .config.User}}", + `"dockerio"`) + + deleteImages("testbuildimg") + logDone("build - user") +} + // TODO: TestCaching // TODO: TestADDCacheInvalidation diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index 89154e343..26a41c651 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -414,20 +414,6 @@ func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, u return image, err } -func TestBuildUser(t *testing.T) { - img, err := buildImage(testContextTemplate{` - from {IMAGE} - user dockerio - `, nil, nil}, t, nil, true) - if err != nil { - t.Fatal(err) - } - - if img.Config.User != "dockerio" { - t.Fail() - } -} - func TestBuildRelativeWorkdir(t *testing.T) { img, err := buildImage(testContextTemplate{` FROM {IMAGE} From 40630ce4b65755683e496bb7dbf3779b96ec5393 Mon Sep 17 00:00:00 2001 From: LK4D4 Date: Sun, 25 May 2014 22:41:47 +0400 Subject: [PATCH 255/400] Move relative workdir build test to integration-cli Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- integration-cli/docker_cli_build_test.go | 20 ++++++++++++++++++++ integration/buildfile_test.go | 19 ------------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index af106a95e..b2cea9e52 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -476,6 +476,26 @@ func TestBuildUser(t *testing.T) { logDone("build - user") } +func TestBuildRelativeWorkdir(t *testing.T) { + checkSimpleBuild(t, + ` + FROM busybox + RUN [ "$PWD" = '/' ] + WORKDIR test1 + RUN [ "$PWD" = '/test1' ] + WORKDIR /test2 + RUN [ "$PWD" = '/test2' ] + WORKDIR test3 + RUN [ "$PWD" = '/test2/test3' ] + `, + "testbuildimg", + "{{json .config.WorkingDir}}", + `"/test2/test3"`) + + deleteImages("testbuildimg") + logDone("build - relative workdir") +} + // TODO: TestCaching // TODO: TestADDCacheInvalidation diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index 26a41c651..2416e140b 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -414,25 +414,6 @@ func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, u return image, err } -func TestBuildRelativeWorkdir(t *testing.T) { - img, err := buildImage(testContextTemplate{` - FROM {IMAGE} - RUN [ "$PWD" = '/' ] - WORKDIR test1 - RUN [ "$PWD" = '/test1' ] - WORKDIR /test2 - RUN [ "$PWD" = '/test2' ] - WORKDIR test3 - RUN [ "$PWD" = '/test2/test3' ] - `, nil, nil}, t, nil, true) - if err != nil { - t.Fatal(err) - } - if img.Config.WorkingDir != "/test2/test3" { - t.Fatalf("Expected workdir to be '/test2/test3', received '%s'", img.Config.WorkingDir) - } -} - func TestBuildEnv(t *testing.T) { img, err := buildImage(testContextTemplate{` from {IMAGE} From b05be686ec490ba68de82434f1ecaff01a84fcfa Mon Sep 17 00:00:00 2001 From: LK4D4 Date: Mon, 26 May 2014 23:09:33 +0400 Subject: [PATCH 256/400] Move env build test to integration-cli Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- integration-cli/docker_cli_build_test.go | 15 +++++++++++++++ integration/buildfile_test.go | 22 ---------------------- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index b2cea9e52..e22b09476 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -496,6 +496,21 @@ func TestBuildRelativeWorkdir(t *testing.T) { logDone("build - relative workdir") } +func TestBuildEnv(t *testing.T) { + checkSimpleBuild(t, + ` + FROM busybox + ENV PORT 4243 + RUN [ $(env | grep PORT) = 'PORT=4243' ] + `, + "testbuildimg", + "{{json .config.Env}}", + `["HOME=/","PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","PORT=4243"]`) + + deleteImages("testbuildimg") + logDone("build - env") +} + // TODO: TestCaching // TODO: TestADDCacheInvalidation diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index 2416e140b..28d5f3011 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -414,28 +414,6 @@ func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, u return image, err } -func TestBuildEnv(t *testing.T) { - img, err := buildImage(testContextTemplate{` - from {IMAGE} - env port 4243 - `, - nil, nil}, t, nil, true) - if err != nil { - t.Fatal(err) - } - - hasEnv := false - for _, envVar := range img.Config.Env { - if envVar == "port=4243" { - hasEnv = true - break - } - } - if !hasEnv { - t.Fail() - } -} - func TestBuildCmd(t *testing.T) { img, err := buildImage(testContextTemplate{` from {IMAGE} From c58991f31a6a2ceaee7a3a327bfbc697b059ff51 Mon Sep 17 00:00:00 2001 From: LK4D4 Date: Mon, 26 May 2014 23:15:40 +0400 Subject: [PATCH 257/400] Move cmd build test to integration-cli Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- integration-cli/docker_cli_build_test.go | 14 ++++++++++++++ integration/buildfile_test.go | 20 -------------------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index e22b09476..ade7efe99 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -511,6 +511,20 @@ func TestBuildEnv(t *testing.T) { logDone("build - env") } +func TestBuildCmd(t *testing.T) { + checkSimpleBuild(t, + ` + FROM scratch + CMD ["/bin/echo", "Hello World"] + `, + "testbuildimg", + "{{json .config.Cmd}}", + `["/bin/echo","Hello World"]`) + + deleteImages("testbuildimg") + logDone("build - cmd") +} + // TODO: TestCaching // TODO: TestADDCacheInvalidation diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index 28d5f3011..5d508ff8f 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -414,26 +414,6 @@ func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, u return image, err } -func TestBuildCmd(t *testing.T) { - img, err := buildImage(testContextTemplate{` - from {IMAGE} - cmd ["/bin/echo", "Hello World"] - `, - nil, nil}, t, nil, true) - if err != nil { - t.Fatal(err) - } - - if img.Config.Cmd[0] != "/bin/echo" { - t.Log(img.Config.Cmd[0]) - t.Fail() - } - if img.Config.Cmd[1] != "Hello World" { - t.Log(img.Config.Cmd[1]) - t.Fail() - } -} - func TestBuildExpose(t *testing.T) { img, err := buildImage(testContextTemplate{` from {IMAGE} From 81d16411398c20a7ddc0bbe3e5e1ce0bd95d6519 Mon Sep 17 00:00:00 2001 From: LK4D4 Date: Mon, 26 May 2014 23:23:46 +0400 Subject: [PATCH 258/400] Move expose build test to integration-cli Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- integration-cli/docker_cli_build_test.go | 15 +++++++++++++++ integration/buildfile_test.go | 15 --------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index ade7efe99..4a2c5eda6 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -525,6 +525,21 @@ func TestBuildCmd(t *testing.T) { logDone("build - cmd") } +func TestBuildExpose(t *testing.T) { + checkSimpleBuild(t, + ` + FROM scratch + EXPOSE 4243 + `, + + "testbuildimg", + "{{json .config.ExposedPorts}}", + `{"4243/tcp":{}}`) + + deleteImages("testbuildimg") + logDone("build - expose") +} + // TODO: TestCaching // TODO: TestADDCacheInvalidation diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index 5d508ff8f..db56537ad 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -414,21 +414,6 @@ func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, u return image, err } -func TestBuildExpose(t *testing.T) { - img, err := buildImage(testContextTemplate{` - from {IMAGE} - expose 4243 - `, - nil, nil}, t, nil, true) - if err != nil { - t.Fatal(err) - } - - if _, exists := img.Config.ExposedPorts[nat.NewPort("tcp", "4243")]; !exists { - t.Fail() - } -} - func TestBuildEntrypoint(t *testing.T) { img, err := buildImage(testContextTemplate{` from {IMAGE} From b25a9b7138e44950bee646c1bcb691a3dd273ea1 Mon Sep 17 00:00:00 2001 From: Alexandr Morozov Date: Tue, 27 May 2014 08:36:00 +0400 Subject: [PATCH 259/400] Move entrypoint build test to integration-cli Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- integration-cli/docker_cli_build_test.go | 14 ++++++++++++++ integration/buildfile_test.go | 16 ---------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 4a2c5eda6..d804ba796 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -540,6 +540,20 @@ func TestBuildExpose(t *testing.T) { logDone("build - expose") } +func TestBuildEntrypoint(t *testing.T) { + checkSimpleBuild(t, + ` + FROM scratch + ENTRYPOINT ["/bin/echo"] + `, + "testbuildimg", + "{{json .config.Entrypoint}}", + `["/bin/echo"]`) + + deleteImages("testbuildimg") + logDone("build - entrypoint") +} + // TODO: TestCaching // TODO: TestADDCacheInvalidation diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index db56537ad..c60bb64c7 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -414,22 +414,6 @@ func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, u return image, err } -func TestBuildEntrypoint(t *testing.T) { - img, err := buildImage(testContextTemplate{` - from {IMAGE} - entrypoint ["/bin/echo"] - `, - nil, nil}, t, nil, true) - if err != nil { - t.Fatal(err) - } - - if img.Config.Entrypoint[0] != "/bin/echo" { - t.Log(img.Config.Entrypoint[0]) - t.Fail() - } -} - // testing #1405 - config.Cmd does not get cleaned up if // utilizing cache func TestBuildEntrypointRunCleanup(t *testing.T) { From 250229605f895226fa868120f1a0ea7f4928bf8c Mon Sep 17 00:00:00 2001 From: Thomas LEVEIL Date: Tue, 27 May 2014 14:17:01 +0200 Subject: [PATCH 260/400] Update ubuntulinux.md and debian.md to add bash completion for the docker command --- docs/sources/installation/debian.md | 1 + docs/sources/installation/ubuntulinux.md | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index 3deda4763..8f4f31c29 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -30,6 +30,7 @@ To install the latest Debian package (may not be the latest Docker release): $ sudo apt-get update $ sudo apt-get install docker.io $ sudo ln -sf /usr/bin/docker.io /usr/local/bin/docker + $ sudo sed -i '$acomplete -F _docker docker' /etc/bash_completion.d/docker.io To verify that everything has worked as expected: diff --git a/docs/sources/installation/ubuntulinux.md b/docs/sources/installation/ubuntulinux.md index bf9992f71..876eb5d29 100644 --- a/docs/sources/installation/ubuntulinux.md +++ b/docs/sources/installation/ubuntulinux.md @@ -36,6 +36,7 @@ To install the latest Ubuntu package (may not be the latest Docker release): $ sudo apt-get update $ sudo apt-get install docker.io $ sudo ln -sf /usr/bin/docker.io /usr/local/bin/docker + $ sudo sed -i '$acomplete -F _docker docker' /etc/bash_completion.d/docker.io To verify that everything has worked as expected: From b7e88250f421d34d25f89737c65954150248ee28 Mon Sep 17 00:00:00 2001 From: Jim Perrin Date: Tue, 27 May 2014 08:59:30 -0500 Subject: [PATCH 261/400] Update documentation per suggestions from ostezer Docker-DCO-1.1-Signed-off-by: Jim Perrin (github: jimperrin) --- docs/sources/installation/centos.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/sources/installation/centos.md b/docs/sources/installation/centos.md index 97c3402c7..7d2a9ae5d 100644 --- a/docs/sources/installation/centos.md +++ b/docs/sources/installation/centos.md @@ -1,5 +1,5 @@ page_title: Installation on CentOS -page_description: This page provides documentation for installing docker on CentOS +page_description: Instructions for installing Docker on CentOS page_keywords: Docker, Docker documentation, requirements, linux, centos, epel, docker.io, docker-io # CentOS @@ -14,7 +14,7 @@ page_keywords: Docker, Docker documentation, requirements, linux, centos, epel, > This is a community contributed installation path. The only `official` > installation is using the [*Ubuntu*](../ubuntulinux/#ubuntu-linux) > installation path. This version may be out of date because it depends on -> some binaries to be updated and published +> some binaries to be updated and published. The Docker package is available via the EPEL repository. These instructions work for CentOS 6 and later. They will likely work for other binary compatible EL6 @@ -86,5 +86,4 @@ or explore and build on the images yourself. ## Issues? If you have any issues - please report them directly in the -[CentOS bug tracker]( -http://bugs.centos.org). +[CentOS bug tracker](http://bugs.centos.org). From 838e69e11e15dcf6cc1bc73f26602f814ce3698c Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 27 May 2014 13:08:19 -0400 Subject: [PATCH 262/400] Removed Docker production warnings * Removed warnings. * Removed inconsistent Community installation sections. * Fixed all installation page descriptions. * Removed old .inc files. Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- docs/sources/installation/amazon.md | 8 +----- docs/sources/installation/archlinux.md | 14 +--------- docs/sources/installation/binaries.md | 8 +----- docs/sources/installation/cruxlinux.md | 12 --------- docs/sources/installation/debian.md | 8 +----- docs/sources/installation/fedora.md | 14 +--------- docs/sources/installation/frugalware.md | 14 +--------- docs/sources/installation/gentoolinux.md | 26 +++++-------------- docs/sources/installation/google.md | 10 +------ docs/sources/installation/install_header.inc | 7 ----- .../installation/install_unofficial.inc | 7 ----- docs/sources/installation/openSUSE.md | 14 +--------- docs/sources/installation/rackspace.md | 8 +----- docs/sources/installation/rhel.md | 14 +--------- docs/sources/installation/softlayer.md | 10 +------ docs/sources/installation/ubuntulinux.md | 6 ----- 16 files changed, 18 insertions(+), 162 deletions(-) delete mode 100644 docs/sources/installation/install_header.inc delete mode 100644 docs/sources/installation/install_unofficial.inc diff --git a/docs/sources/installation/amazon.md b/docs/sources/installation/amazon.md index 61a12d6b4..f7abec155 100644 --- a/docs/sources/installation/amazon.md +++ b/docs/sources/installation/amazon.md @@ -1,15 +1,9 @@ page_title: Installation on Amazon EC2 -page_description: Please note this project is currently under heavy development. It should not be used in production. +page_description: Installation instructions for Docker on Amazon EC2. page_keywords: amazon ec2, virtualization, cloud, docker, documentation, installation # Amazon EC2 -> **Note**: -> Docker is still under heavy development! We don't recommend using it in -> production yet, but we're getting closer with each release. Please see -> our blog post, [Getting to Docker 1.0]( -> http://blog.docker.io/2013/08/getting-to-docker-1-0/) - There are several ways to install Docker on AWS EC2: - [*Amazon QuickStart (Release Candidate - March 2014)*]( diff --git a/docs/sources/installation/archlinux.md b/docs/sources/installation/archlinux.md index c6d4f73fb..81cc21fb0 100644 --- a/docs/sources/installation/archlinux.md +++ b/docs/sources/installation/archlinux.md @@ -1,21 +1,9 @@ page_title: Installation on Arch Linux -page_description: Please note this project is currently under heavy development. It should not be used in production. +page_description: Installation instructions for Docker on ArchLinux. page_keywords: arch linux, virtualization, docker, documentation, installation # Arch Linux -> **Note**: -> Docker is still under heavy development! We don't recommend using it in -> production yet, but we're getting closer with each release. Please see -> our blog post, [Getting to Docker 1.0]( -> http://blog.docker.io/2013/08/getting-to-docker-1-0/) - -> **Note**: -> This is a community contributed installation path. The only `official` -> installation is using the [*Ubuntu*](../ubuntulinux/#ubuntu-linux) -> installation path. This version may be out of date because it depends on -> some binaries to be updated and published - Installing on Arch Linux can be handled via the package in community: - [docker](https://www.archlinux.org/packages/community/x86_64/docker/) diff --git a/docs/sources/installation/binaries.md b/docs/sources/installation/binaries.md index 36aa0ae24..494530a23 100644 --- a/docs/sources/installation/binaries.md +++ b/docs/sources/installation/binaries.md @@ -1,15 +1,9 @@ page_title: Installation from Binaries -page_description: This instruction set is meant for hackers who want to try out Docker on a variety of environments. +page_description: Instructions for installing Docker as a binary. Mostly meant for hackers who want to try out Docker on a variety of environments. page_keywords: binaries, installation, docker, documentation, linux # Binaries -> **Note**: -> Docker is still under heavy development! We don't recommend using it in -> production yet, but we're getting closer with each release. Please see -> our blog post, [Getting to Docker 1.0]( -> http://blog.docker.io/2013/08/getting-to-docker-1-0/) - **This instruction set is meant for hackers who want to try out Docker on a variety of environments.** diff --git a/docs/sources/installation/cruxlinux.md b/docs/sources/installation/cruxlinux.md index 6f0bfff74..28efde376 100644 --- a/docs/sources/installation/cruxlinux.md +++ b/docs/sources/installation/cruxlinux.md @@ -4,18 +4,6 @@ page_keywords: crux linux, virtualization, Docker, documentation, installation # CRUX Linux -> **Note**: -> Docker is still under heavy development! We don't recommend using it in -> production yet, but we're getting closer with each release. Please see -> our blog post, [Getting to Docker 1.0]( -> http://blog.docker.io/2013/08/getting-to-docker-1-0/) - -> **Note**: -> This is a community contributed installation path. The only `official` -> installation is using the [*Ubuntu*](../ubuntulinux/#ubuntu-linux) -> installation path. This version may be out of date because it depends on -> some binaries to be updated and published. - Installing on CRUX Linux can be handled via the ports from [James Mills](http://prologic.shortcircuit.net.au/) and are included in the official [contrib](http://crux.nu/portdb/?a=repo&q=contrib) ports: diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index 8f4f31c29..def8cb77c 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -1,15 +1,9 @@ page_title: Installation on Debian -page_description: Instructions for installing Docker on Debian +page_description: Instructions for installing Docker on Debian. page_keywords: Docker, Docker documentation, installation, debian # Debian -> **Note**: -> Docker is still under heavy development! We don't recommend using it in -> production yet, but we're getting closer with each release. Please see -> our blog post, [Getting to Docker 1.0]( -> http://blog.docker.io/2013/08/getting-to-docker-1-0/) - Docker is supported on the following versions of Debian: - [*Debian 8.0 Jessie (64-bit)*](#debian-jessie-8-64-bit) diff --git a/docs/sources/installation/fedora.md b/docs/sources/installation/fedora.md index 20ab4477d..d615d4f6c 100644 --- a/docs/sources/installation/fedora.md +++ b/docs/sources/installation/fedora.md @@ -1,21 +1,9 @@ page_title: Installation on Fedora -page_description: Please note this project is currently under heavy development. It should not be used in production. +page_description: Installation instructions for Docker on Fedora. page_keywords: Docker, Docker documentation, Fedora, requirements, virtualbox, vagrant, git, ssh, putty, cygwin, linux # Fedora -> **Note**: -> Docker is still under heavy development! We don't recommend using it in -> production yet, but we're getting closer with each release. Please see -> our blog post, [Getting to Docker 1.0]( -> http://blog.docker.io/2013/08/getting-to-docker-1-0/) - -> **Note**: -> This is a community contributed installation path. The only `official` -> installation is using the [*Ubuntu*](../ubuntulinux/#ubuntu-linux) -> installation path. This version may be out of date because it depends on -> some binaries to be updated and published. - Docker is available in **Fedora 19 and later**. Please note that due to the current Docker limitations Docker is able to run only on the **64 bit** architecture. diff --git a/docs/sources/installation/frugalware.md b/docs/sources/installation/frugalware.md index eb409d8d3..2c2f92261 100644 --- a/docs/sources/installation/frugalware.md +++ b/docs/sources/installation/frugalware.md @@ -1,21 +1,9 @@ page_title: Installation on FrugalWare -page_description: Please note this project is currently under heavy development. It should not be used in production. +page_description: Installation instructions for Docker on FrugalWare. page_keywords: frugalware linux, virtualization, docker, documentation, installation # FrugalWare -> **Note**: -> Docker is still under heavy development! We don't recommend using it in -> production yet, but we're getting closer with each release. Please see -> our blog post, [Getting to Docker 1.0]( -> http://blog.docker.io/2013/08/getting-to-docker-1-0/) - -> **Note**: -> This is a community contributed installation path. The only `official` -> installation is using the [*Ubuntu*](../ubuntulinux/#ubuntu-linux) -> installation path. This version may be out of date because it depends on -> some binaries to be updated and published - Installing on FrugalWare is handled via the official packages: - [lxc-docker i686](http://www.frugalware.org/packages/200141) diff --git a/docs/sources/installation/gentoolinux.md b/docs/sources/installation/gentoolinux.md index 92329dca9..62fdc9f00 100644 --- a/docs/sources/installation/gentoolinux.md +++ b/docs/sources/installation/gentoolinux.md @@ -1,33 +1,21 @@ page_title: Installation on Gentoo -page_description: Please note this project is currently under heavy development. It should not be used in production. +page_description: Installation instructions for Docker on Gentoo. page_keywords: gentoo linux, virtualization, docker, documentation, installation # Gentoo -> **Note**: -> Docker is still under heavy development! We don't recommend using it in -> production yet, but we're getting closer with each release. Please see -> our blog post, [Getting to Docker 1.0]( -> http://blog.docker.io/2013/08/getting-to-docker-1-0/) - -> **Note**: -> This is a community contributed installation path. The only `official` -> installation is using the [*Ubuntu*](../ubuntulinux/#ubuntu-linux) -> installation path. This version may be out of date because it depends on -> some binaries to be updated and published - Installing Docker on Gentoo Linux can be accomplished using one of two methods. The first and best way if you're looking for a stable experience is to use the official app-emulation/docker package directly in the portage tree. -If you're looking for a `-bin` ebuild, a live -ebuild, or bleeding edge ebuild changes/fixes, the second installation -method is to use the overlay provided at +If you're looking for a `-bin` ebuild, a live ebuild, or bleeding edge +ebuild changes/fixes, the second installation method is to use the +overlay provided at [https://github.com/tianon/docker-overlay](https://github.com/tianon/docker-overlay) -which can be added using `app-portage/layman`. The -most accurate and up-to-date documentation for properly installing and -using the overlay can be found in [the overlay +which can be added using `app-portage/layman`. The most accurate and +up-to-date documentation for properly installing and using the overlay +can be found in [the overlay README](https://github.com/tianon/docker-overlay/blob/master/README.md#using-this-overlay). Note that sometimes there is a disparity between the latest version and diff --git a/docs/sources/installation/google.md b/docs/sources/installation/google.md index 29ffe0d73..bb8a96147 100644 --- a/docs/sources/installation/google.md +++ b/docs/sources/installation/google.md @@ -1,17 +1,9 @@ page_title: Installation on Google Cloud Platform -page_description: Please note this project is currently under heavy development. It should not be used in production. +page_description: Installation instructions for Docker on the Google Cloud Platform. page_keywords: Docker, Docker documentation, installation, google, Google Compute Engine, Google Cloud Platform # Google Cloud Platform -> **Note**: -> Docker is still under heavy development! We don't recommend using it in -> production yet, but we're getting closer with each release. Please see -> our blog post, [Getting to Docker 1.0]( -> http://blog.docker.io/2013/08/getting-to-docker-1-0/) - -## Compute Engine QuickStart for Debian - 1. Go to [Google Cloud Console](https://cloud.google.com/console) and create a new Cloud Project with [Compute Engine enabled](https://developers.google.com/compute/docs/signup). diff --git a/docs/sources/installation/install_header.inc b/docs/sources/installation/install_header.inc deleted file mode 100644 index c9b9e4c49..000000000 --- a/docs/sources/installation/install_header.inc +++ /dev/null @@ -1,7 +0,0 @@ - -.. note:: - - Docker is still under heavy development! We don't recommend using - it in production yet, but we're getting closer with each - release. Please see our blog post, `"Getting to Docker 1.0" - `_ diff --git a/docs/sources/installation/install_unofficial.inc b/docs/sources/installation/install_unofficial.inc deleted file mode 100644 index 8d121918b..000000000 --- a/docs/sources/installation/install_unofficial.inc +++ /dev/null @@ -1,7 +0,0 @@ - -.. note:: - - This is a community contributed installation path. The only - 'official' installation is using the :ref:`ubuntu_linux` - installation path. This version may be out of date because it - depends on some binaries to be updated and published diff --git a/docs/sources/installation/openSUSE.md b/docs/sources/installation/openSUSE.md index 07f2ca43d..d2c8e5484 100644 --- a/docs/sources/installation/openSUSE.md +++ b/docs/sources/installation/openSUSE.md @@ -1,21 +1,9 @@ page_title: Installation on openSUSE -page_description: Please note this project is currently under heavy development. It should not be used in production. +page_description: Installation instructions for Docker on openSUSE. page_keywords: openSUSE, virtualbox, docker, documentation, installation # openSUSE -> **Note**: -> Docker is still under heavy development! We don't recommend using it in -> production yet, but we're getting closer with each release. Please see -> our blog post, [Getting to Docker 1.0]( -> http://blog.docker.io/2013/08/getting-to-docker-1-0/) - -> **Note**: -> This is a community contributed installation path. The only `official` -> installation is using the [*Ubuntu*](../ubuntulinux/#ubuntu-linux) -> installation path. This version may be out of date because it depends on -> some binaries to be updated and published - Docker is available in **openSUSE 12.3 and later**. Please note that due to the current Docker limitations Docker is able to run only on the **64 bit** architecture. diff --git a/docs/sources/installation/rackspace.md b/docs/sources/installation/rackspace.md index c93af388e..1aa969d1e 100644 --- a/docs/sources/installation/rackspace.md +++ b/docs/sources/installation/rackspace.md @@ -1,15 +1,9 @@ page_title: Installation on Rackspace Cloud -page_description: Please note this project is currently under heavy development. It should not be used in production. +page_description: Installation instructions for Docker on Rackspace Cloud. page_keywords: Rackspace Cloud, installation, docker, linux, ubuntu # Rackspace Cloud -> **Note**: -> This is a community contributed installation path. The only `official` -> installation is using the [*Ubuntu*](../ubuntulinux/#ubuntu-linux) -> installation path. This version may be out of date because it depends on -> some binaries to be updated and published - Installing Docker on Ubuntu provided by Rackspace is pretty straightforward, and you should mostly be able to follow the [*Ubuntu*](../ubuntulinux/#ubuntu-linux) installation guide. diff --git a/docs/sources/installation/rhel.md b/docs/sources/installation/rhel.md index 632743a2b..7cb71ec0d 100644 --- a/docs/sources/installation/rhel.md +++ b/docs/sources/installation/rhel.md @@ -1,21 +1,9 @@ page_title: Installation on Red Hat Enterprise Linux -page_description: Please note this project is currently under heavy development. It should not be used in production. +page_description: Installation instructions for Docker on Red Hat Enterprise Linux. page_keywords: Docker, Docker documentation, requirements, linux, rhel, centos # Red Hat Enterprise Linux -> **Note**: -> Docker is still under heavy development! We don't recommend using it in -> production yet, but we're getting closer with each release. Please see -> our blog post, [Getting to Docker 1.0]( -> http://blog.docker.io/2013/08/getting-to-docker-1-0/) - -> **Note**: -> This is a community contributed installation path. The only `official` -> installation is using the [*Ubuntu*](../ubuntulinux/#ubuntu-linux) -> installation path. This version may be out of date because it depends on -> some binaries to be updated and published - Docker is available for **RHEL** on EPEL. These instructions should work for both RHEL and CentOS. They will likely work for other binary compatible EL6 distributions as well, but they haven't been tested. diff --git a/docs/sources/installation/softlayer.md b/docs/sources/installation/softlayer.md index 11a192c61..80b12741f 100644 --- a/docs/sources/installation/softlayer.md +++ b/docs/sources/installation/softlayer.md @@ -1,17 +1,9 @@ page_title: Installation on IBM SoftLayer -page_description: Please note this project is currently under heavy development. It should not be used in production. +page_description: Installation instructions for Docker on IBM Softlayer. page_keywords: IBM SoftLayer, virtualization, cloud, docker, documentation, installation # IBM SoftLayer -> **Note**: -> Docker is still under heavy development! We don't recommend using it in -> production yet, but we're getting closer with each release. Please see -> our blog post, [Getting to Docker 1.0]( -> http://blog.docker.io/2013/08/getting-to-docker-1-0/) - -## IBM SoftLayer QuickStart - 1. Create an [IBM SoftLayer account]( https://www.softlayer.com/cloud-servers/). 2. Log in to the [SoftLayer Console]( diff --git a/docs/sources/installation/ubuntulinux.md b/docs/sources/installation/ubuntulinux.md index 876eb5d29..daba2c4ad 100644 --- a/docs/sources/installation/ubuntulinux.md +++ b/docs/sources/installation/ubuntulinux.md @@ -4,12 +4,6 @@ page_keywords: Docker, Docker documentation, requirements, virtualbox, installat # Ubuntu -> **Note**: -> Docker is still under heavy development! We don't recommend using it in -> production yet, but we're getting closer with each release. Please see -> our blog post, [Getting to Docker 1.0]( -> http://blog.docker.io/2013/08/getting-to-docker-1-0/) - Docker is supported on the following versions of Ubuntu: - [*Ubuntu Trusty 14.04 (LTS) (64-bit)*](#ubuntu-trusty-1404-lts-64-bit) From 2bbf87504bc9eb6ee9a4694ce4d1d98287bccbc9 Mon Sep 17 00:00:00 2001 From: Zac Dover Date: Tue, 27 May 2014 02:25:39 +1000 Subject: [PATCH 263/400] fixed spacing, removed extraneous line Docker-DCO-1.1-Signed-off-by: Zac Dover (github: zdover23) --- contrib/man/md/Dockerfile.5.md | 195 ++++++++++++++++++++++++++++++--- 1 file changed, 180 insertions(+), 15 deletions(-) diff --git a/contrib/man/md/Dockerfile.5.md b/contrib/man/md/Dockerfile.5.md index c90ebf9a5..06970eb87 100644 --- a/contrib/man/md/Dockerfile.5.md +++ b/contrib/man/md/Dockerfile.5.md @@ -1,4 +1,4 @@ -% DOCKERFILE(1) Docker User Manuals +% DOCKERFILE(5) Docker User Manuals % Zac Dover % May 2014 # NAME @@ -6,7 +6,13 @@ Dockerfile - automate the steps of creating a Docker image # INTRODUCTION -**Dockerfile** is a configuration file that automates the steps of creating a Docker image. Docker can act as a builder and can read instructions from **Dockerfile** to automate the steps that you would otherwise manually perform to create an image. To build an image from a source repository, create a description file called **Dockerfile** at the root of your repository. This file describes the steps that will be taken to assemble the image. When **Dockerfile** has been created, call **docker build** with the path of the source repository as the argument. +**Dockerfile** is a configuration file that automates the steps of creating a +Docker image. It is similar to a Makefile. Docker reads instructions from +**Dockerfile** to automate the steps otherwise performed manually to create +an image. To build an image, create a file called **Dockerfile**. The +**Dockerfile** describes the steps taken to assemble the image. When the +**Dockerfile** has been created, call the **docker build** command, using the +path of directory that contains **Dockerfile** as the argument. # SYNOPSIS @@ -18,24 +24,183 @@ FROM image # DESCRIPTION -Dockerfile is a file that automates the steps of creating a Docker image. +A Dockerfile is a file that automates the steps of creating a Docker image. +A Dockerfile is similar to a Makefile. # USAGE -$ sudo docker build . +**sudo docker build .** -- runs the steps and commits them, building a final image - The path to the source repository defines where to find the context of the build. - The build is run by the docker daemon, not the CLI. The whole context must be - transferred to the daemon. The Docker CLI reports "Uploading context" when the - context is sent to the daemon. + The path to the source repository defines where to find the context of the + build. The build is run by the docker daemon, not the CLI. The whole + context must be transferred to the daemon. The Docker CLI reports + "Uploading context" when the context is sent to the daemon. -$ sudo docker build -t repository/tag . - -- specifies a repository and tag at which to save the new image if the build succeeds. - The Docker daemon runs the steps one-by-one, commiting the result to a new image - if necessary before finally outputting the ID of the new image. The Docker - daemon automatically cleans up the context it is given. +**sudo docker build -t repository/tag .** + -- specifies a repository and tag at which to save the new image if the build + succeeds. The Docker daemon runs the steps one-by-one, commiting the result + to a new image if necessary before finally outputting the ID of the new + image. The Docker daemon automatically cleans up the context it is given. -Docker re-uses intermediate images whenever possible. This significantly accelerates the *docker build* process. +Docker re-uses intermediate images whenever possible. This significantly +accelerates the *docker build* process. +# FORMAT + +**FROM image** +or +**FROM image:tag** + -- The FROM instruction sets the base image for subsequent instructions. A + valid Dockerfile must have FROM as its first instruction. The image can be any + valid image. It is easy to start by pulling an image from the public + repositories. + -- FROM must be he first non-comment instruction in Dockerfile. + -- FROM may appear multiple times within a single Dockerfile in order to create + multiple images. Make a note of the last image id output by the commit before + each new FROM command. + -- If no tag is given to the FROM instruction, latest is assumed. If the used + tag does not exist, an error is returned. + +**MAINTAINER** + --The MAINTAINER instruction sets the Author field for the generated images. + +**RUN** + --RUN has two forms: + **RUN ** + -- (the command is run in a shell - /bin/sh -c) + **RUN ["executable", "param1", "param2"]** + --The above is executable form. + --The RUN instruction executes any commands in a new layer on top of the + current image and commits the results. The committed image is used for the next + step in Dockerfile. + --Layering RUN instructions and generating commits conforms to the core + concepts of Docker where commits are cheap and containers can be created from + any point in the history of an image. This is similar to source control. The + exec form makes it possible to avoid shell string munging. The exec form makes + it possible to RUN commands using a base image that does not contain /bin/sh. + +**CMD** + --CMD has three forms: + **CMD ["executable", "param1", "param2"]** This is the preferred form, the + exec form. + **CMD ["param1", "param2"]** This command provides default parameters to + ENTRYPOINT) + **CMD command param1 param2** This command is run as a shell. + --There can be only one CMD in a Dockerfile. If more than one CMD is listed, only + the last CMD takes effect. + The main purpose of a CMD is to provide defaults for an executing container. + These defaults may include an executable, or they can omit the executable. If + they omit the executable, an ENTRYPOINT must be specified. + When used in the shell or exec formats, the CMD instruction sets the command to + be executed when running the image. + If you use the shell form of of the CMD, the executes in /bin/sh -c: + **FROM ubuntu** + **CMD echo "This is a test." | wc -** + If you run wihtout a shell, then you must express the command as a + JSON arry and give the full path to the executable. This array form is the + preferred form of CMD. All additional parameters must be individually expressed + as strings in the array: + **FROM ubuntu** + **CMD ["/usr/bin/wc","--help"]** + To make the container run the same executable every time, use ENTRYPOINT in + combination with CMD. + If the user specifies arguments to docker run, the specified commands override + the default in CMD. + Do not confuse **RUN** with **CMD**. RUN runs a command and commits the result. CMD + executes nothing at build time, but specifies the intended command for the + image. + +**EXPOSE** + --**EXPOSE [...]** + The **EXPOSE** instruction informs Docker that the container listens on the + specified network ports at runtime. Docker uses this information to + interconnect containers using links, and to set up port redirection on the host + system. + +**ENV** + --**ENV ** + The ENV instruction sets the environment variable to + the value . This value is passed to all future RUN instructions. This is + functionally equivalent to prefixing the command with **=**. The + environment variables that are set with ENV persist when a container is run + from the resulting image. Use docker inspect to inspect these values, and + change them using docker run **--env =.** + + Note that setting Setting **ENV DEBIAN_FRONTEND noninteractive** may cause + unintended consequences, because it will persist when the container is run + interactively, as with the following command: **docker run -t -i image bash** + +**ADD** + --**ADD ** The ADD instruction copies new files from and adds them + to the filesystem of the container at path . must be the path to a + file or directory relative to the source directory that is being built (the + context of the build) or a remote file URL. is the absolute path to + which the source is copied inside the target container. All new files and + directories are created with mode 0755, with uid and gid 0. + +**ENTRYPOINT** + --**ENTRYPOINT** has two forms: ENTRYPOINT ["executable", "param1", "param2"] + (This is like an exec, and is the preferred form.) ENTRYPOINT command param1 + param2 (This is running as a shell.) An ENTRYPOINT helps you configure a + container that can be run as an executable. When you specify an ENTRYPOINT, + the whole container runs as if it was only that executable. The ENTRYPOINT + instruction adds an entry command that is not overwritten when arguments are + passed to docker run. This is different from the behavior of CMD. This allows + arguments to be passed to the entrypoint, for instance docker run -d + passes the -d argument to the ENTRYPOINT. Specify parameters either in the + ENTRYPOINT JSON array (as in the preferred exec form above), or by using a CMD + statement. Parameters in the ENTRYPOINT are not overwritten by the docker run + arguments. Parameters specifies via CMD are overwritten by docker run + arguments. Specify a plain string for the ENTRYPOINT, and it will execute in + /bin/sh -c, like a CMD instruction: + FROM ubuntu + ENTRYPOINT wc -l - + This means that the Dockerfile's image always takes stdin as input (that's + what "-" means), and prints the number of lines (that's what "-l" means). To + make this optional but default, use a CMD: + FROM ubuntu + CMD ["-l", "-"] + ENTRYPOINT ["/usr/bin/wc"] + +**VOLUME** + --**VOLUME ["/data"]** + The VOLUME instruction creates a mount point with the specified name and marks + it as holding externally-mounted volumes from the native host or from other + containers. + +**USER** + -- **USER daemon** + The USER instruction sets the username or UID that is used when running the + image. + +**WORKDIR** + -- **WORKDIR /path/to/workdir** + The WORKDIR instruction sets the working directory for the **RUN**, **CMD**, and **ENTRYPOINT** Dockerfile commands that follow it. + It can be used multiple times in a single Dockerfile. Relative paths are defined relative to the path of the previous **WORKDIR** instruction. For example: + **WORKDIR /a WORKDIR /b WORKDIR c RUN pwd** + In the above example, the output of the **pwd** command is **a/b/c**. + +**ONBUILD** + -- **ONBUILD [INSTRUCTION]** + The ONBUILD instruction adds a trigger instruction to the image, which is + executed at a later time, when the image is used as the base for another + build. The trigger is executed in the context of the downstream build, as + if it had been inserted immediately after the FROM instruction in the + downstream Dockerfile. Any build instruction can be registered as a + trigger. This is useful if you are building an image to be + used as a base for building other images, for example an application build + environment or a daemon to be customized with a user-specific + configuration. For example, if your image is a reusable python + application builder, it requires application source code to be + added in a particular directory, and might require a build script + to be called after that. You can't just call ADD and RUN now, because + you don't yet have access to the application source code, and it + is different for each application build. Providing + application developers with a boilerplate Dockerfile to copy-paste + into their application is inefficient, error-prone, and + difficult to update because it mixes with application-specific code. + The solution is to use **ONBUILD** to register instructions in advance, to + run later, during the next build stage. + # HISTORY -May 2014, Compiled by Zac Dover (zdover at redhat dot com) based on docker.io Dockerfile documentation. +*May 2014, Compiled by Zac Dover (zdover at redhat dot com) based on docker.io Dockerfile documentation. From 2cb184ade59ccb578d50f5ff535d1d02e7855563 Mon Sep 17 00:00:00 2001 From: William Henry Date: Tue, 27 May 2014 11:56:11 -0600 Subject: [PATCH 264/400] Some changes to semantics around tagging, detaching. Tried to clarify some of the semantics regarding --tag and renaming etc. Added some text clarifying how to detach from a running containers. Fixed a bug that reference /varlog instead of /dev/log. Docker-DCO-1.1-Signed-off-by: William Henry (github: ipbabble) Changes to be committed: modified: docker-attach.1.md modified: docker-build.1.md modified: docker-run.1.md modified: docker-tag.1.md --- contrib/man/md/docker-attach.1.md | 7 +++--- contrib/man/md/docker-build.1.md | 37 +++++++++++++++++++++++++++++-- contrib/man/md/docker-run.1.md | 5 ++++- contrib/man/md/docker-tag.1.md | 9 +++++--- 4 files changed, 49 insertions(+), 9 deletions(-) diff --git a/contrib/man/md/docker-attach.1.md b/contrib/man/md/docker-attach.1.md index 2755fd27f..5a3b7a285 100644 --- a/contrib/man/md/docker-attach.1.md +++ b/contrib/man/md/docker-attach.1.md @@ -9,10 +9,11 @@ docker-attach - Attach to a running container # DESCRIPTION If you **docker run** a container in detached mode (**-d**), you can reattach to - the detached container with **docker attach** using the container's ID or name. +the detached container with **docker attach** using the container's ID or name. -You can detach from the container again (and leave it running) with `CTRL-c` (for -a quiet exit) or `CTRL-\` to get a stacktrace of the Docker client when it quits. +You can detach from the container again (and leave it running) with `CTRL-q +CTRL-q` (for a quiet exit), or `CTRL-c` which will send a SIGKILL to the +container, or `CTRL-\` to get a stacktrace of the Docker client when it quits. When you detach from a container the exit code will be returned to the client. diff --git a/contrib/man/md/docker-build.1.md b/contrib/man/md/docker-build.1.md index b3e9a2842..f242bdd2c 100644 --- a/contrib/man/md/docker-build.1.md +++ b/contrib/man/md/docker-build.1.md @@ -34,8 +34,9 @@ as context. build process. The default is true. **-t**, **--tag**=*tag* - Tag to be applied to the resulting image on successful completion of -the build. + The name to be applied to the resulting image on successful completion of +the build. 'Tag' is this context means the entire image name including the +optional TAG after the ':'. **--no-cache**=*true*|*false* When set to true, do not use a cache when building the image. The @@ -66,6 +67,38 @@ in the Dockerfile. Note: If you include a tar file (a good practice!), then Docker will automatically extract the contents of the tar file specified within the `ADD` instruction into the specified target. +## Building an image and naming that image + +A good practice is to give a name to the image you are building. There are +not hard rules here but it is best to give the names consideration. + +The '-t'/'--tag' flag is used to rename an image. Here are some examples: + +Though not a good practice image names can be aribtrary: + + docker build -t myimage . + +Better is provide a fully qualified and meaningful repository name, name, +and tag (where tag in this context means the qualifier after the ':'). In +this example we build a Jboss image for the Fedora repository and give it +a version 1.0: + + docker build -t fedora/jboss:1.0 + +The next example is for the 'whenry' user repository and uses Fedora and +JBoss and gives it a version 2.1 : + + docker build -t whenry/fedora-jboss:V2.1 + +Or: + + docker build -t whenry/fedora-jboss:latest + +So renaming an image is arbitrary but consideration should be given to +a useful convention that makes sense for consumers and should also take +into account Docker community conventions. + + ## Building an image using a URL This will clone the specified Github repository from the URL and use it diff --git a/contrib/man/md/docker-run.1.md b/contrib/man/md/docker-run.1.md index 2ebf82b6a..72b01419b 100644 --- a/contrib/man/md/docker-run.1.md +++ b/contrib/man/md/docker-run.1.md @@ -64,6 +64,9 @@ the other shell to view a list of the running containers. You can reattach to a detached container with **docker attach**. If you choose to run a container in the detached mode, then you cannot use the **-rm** option. + When attached in tty mode, you can detach from a running container by pressing +the keys ctrl+p ctrl+q. + **--dns**=*IP-address* Set custom DNS servers. This option can be used to override the DNS @@ -237,7 +240,7 @@ can override the working directory by using the **-w** option. ## Exposing log messages from the container to the host's log If you want messages that are logged in your container to show up in the host's -syslog/journal then you should bind mount the /var/log directory as follows. +syslog/journal then you should bind mount the /dev/log directory as follows. # docker run -v /dev/log:/dev/log -i -t fedora /bin/bash diff --git a/contrib/man/md/docker-tag.1.md b/contrib/man/md/docker-tag.1.md index 49f5a6c4d..687b6ddb4 100644 --- a/contrib/man/md/docker-tag.1.md +++ b/contrib/man/md/docker-tag.1.md @@ -9,7 +9,8 @@ docker-tag - Tag an image in the repository IMAGE [REGISTRYHOST/][USERNAME/]NAME[:TAG] # DESCRIPTION -This will tag an image in the repository. +This will rename an image in the repository. "Tag" is this context means the +entire image name including the optional TAG after the ':'. # "OPTIONS" **-f**, **--force**=*true*|*false* @@ -26,13 +27,15 @@ separated by a ':' The image name. **TAG** - The tag you are assigning to the image. + The tag you are assigning to the image. This is often a version or other +'tag' to distinguish from other similarly named images. # EXAMPLES ## Tagging an image -Here is an example of tagging an image with the tag version1.0 : +Here is an example of renaming an image with the repository 'fedora', name +'httpd', and tag version1.0 : docker tag 0e5574283393 fedora/httpd:version1.0 From a14a88e53dc71b1013d7b629d23d3908d15d57f4 Mon Sep 17 00:00:00 2001 From: William Henry Date: Tue, 27 May 2014 12:05:48 -0600 Subject: [PATCH 265/400] Fixed some typos in docker-build. Docker-DCO-1.1-Signed-off-by: William Henry (github: ipbabble) Changes to be committed: modified: contrib/man/md/docker-build.1.md --- contrib/man/md/docker-build.1.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/contrib/man/md/docker-build.1.md b/contrib/man/md/docker-build.1.md index f242bdd2c..da4f5c017 100644 --- a/contrib/man/md/docker-build.1.md +++ b/contrib/man/md/docker-build.1.md @@ -35,7 +35,7 @@ build process. The default is true. **-t**, **--tag**=*tag* The name to be applied to the resulting image on successful completion of -the build. 'Tag' is this context means the entire image name including the +the build. `tag` in this context means the entire image name including the optional TAG after the ':'. **--no-cache**=*true*|*false* @@ -72,20 +72,20 @@ specified within the `ADD` instruction into the specified target. A good practice is to give a name to the image you are building. There are not hard rules here but it is best to give the names consideration. -The '-t'/'--tag' flag is used to rename an image. Here are some examples: +The **-t**/**--tag** flag is used to rename an image. Here are some examples: -Though not a good practice image names can be aribtrary: +Though t is not good practice, image names can be aribtrary: docker build -t myimage . -Better is provide a fully qualified and meaningful repository name, name, -and tag (where tag in this context means the qualifier after the ':'). In -this example we build a Jboss image for the Fedora repository and give it -a version 1.0: +A better approach is provide a fully qualified and meaningful repository +name, name, and tag (where tag in this context means the qualifier after +the ":"). In this example we build a Jboss image for the Fedora repository +and give it a version 1.0: docker build -t fedora/jboss:1.0 -The next example is for the 'whenry' user repository and uses Fedora and +The next example is for the "whenry" user repository and uses Fedora and JBoss and gives it a version 2.1 : docker build -t whenry/fedora-jboss:V2.1 From ef94ac7d2fd42a09c99567b0393fb48b9d782a9e Mon Sep 17 00:00:00 2001 From: Alexandr Morozov Date: Tue, 20 May 2014 18:26:10 +0400 Subject: [PATCH 266/400] Ip allocator refactoring We don't need ordered set anymore, also some cleanings and simple benchmark. Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- daemon/networkdriver/ipallocator/allocator.go | 114 +++++++----------- .../ipallocator/allocator_test.go | 17 +++ 2 files changed, 60 insertions(+), 71 deletions(-) diff --git a/daemon/networkdriver/ipallocator/allocator.go b/daemon/networkdriver/ipallocator/allocator.go index 4bcce6517..f154b0bd4 100644 --- a/daemon/networkdriver/ipallocator/allocator.go +++ b/daemon/networkdriver/ipallocator/allocator.go @@ -4,19 +4,18 @@ import ( "encoding/binary" "errors" "github.com/dotcloud/docker/daemon/networkdriver" - "github.com/dotcloud/docker/pkg/collections" "net" "sync" - "sync/atomic" ) +// allocatedMap is thread-unsafe set of allocated IP type allocatedMap struct { - *collections.OrderedIntSet + p map[int32]struct{} last int32 } func newAllocatedMap() *allocatedMap { - return &allocatedMap{OrderedIntSet: collections.NewOrderedIntSet()} + return &allocatedMap{p: make(map[int32]struct{})} } type networkSet map[string]*allocatedMap @@ -35,70 +34,65 @@ var ( // will return the next available ip if the ip provided is nil. If the // ip provided is not nil it will validate that the provided ip is available // for use or return an error -func RequestIP(address *net.IPNet, ip *net.IP) (*net.IP, error) { +func RequestIP(network *net.IPNet, ip *net.IP) (*net.IP, error) { lock.Lock() defer lock.Unlock() - - checkAddress(address) + key := network.String() + allocated, ok := allocatedIPs[key] + if !ok { + allocated = newAllocatedMap() + allocatedIPs[key] = allocated + } if ip == nil { - next, err := getNextIp(address) - if err != nil { - return nil, err - } - return next, nil + return allocated.getNextIP(network) } - - if err := registerIP(address, ip); err != nil { - return nil, err - } - return ip, nil + return allocated.checkIP(network, ip) } // ReleaseIP adds the provided ip back into the pool of // available ips to be returned for use. -func ReleaseIP(address *net.IPNet, ip *net.IP) error { +func ReleaseIP(network *net.IPNet, ip *net.IP) error { lock.Lock() defer lock.Unlock() - - checkAddress(address) - - var ( - allocated = allocatedIPs[address.String()] - pos = getPosition(address, ip) - ) - - allocated.Remove(int(pos)) - + if allocated, exists := allocatedIPs[network.String()]; exists { + pos := getPosition(network, ip) + delete(allocated.p, pos) + } return nil } // convert the ip into the position in the subnet. Only // position are saved in the set -func getPosition(address *net.IPNet, ip *net.IP) int32 { - var ( - first, _ = networkdriver.NetworkRange(address) - base = ipToInt(&first) - i = ipToInt(ip) - ) - return i - base +func getPosition(network *net.IPNet, ip *net.IP) int32 { + first, _ := networkdriver.NetworkRange(network) + return ipToInt(ip) - ipToInt(&first) +} + +func (allocated *allocatedMap) checkIP(network *net.IPNet, ip *net.IP) (*net.IP, error) { + pos := getPosition(network, ip) + if _, ok := allocated.p[pos]; ok { + return nil, ErrIPAlreadyAllocated + } + allocated.p[pos] = struct{}{} + allocated.last = pos + return ip, nil } // return an available ip if one is currently available. If not, // return the next available ip for the nextwork -func getNextIp(address *net.IPNet) (*net.IP, error) { +func (allocated *allocatedMap) getNextIP(network *net.IPNet) (*net.IP, error) { var ( - ownIP = ipToInt(&address.IP) - allocated = allocatedIPs[address.String()] - first, _ = networkdriver.NetworkRange(address) - base = ipToInt(&first) - size = int(networkdriver.NetworkSize(address.Mask)) - max = int32(size - 2) // size -1 for the broadcast address, -1 for the gateway address - pos = atomic.LoadInt32(&allocated.last) + ownIP = ipToInt(&network.IP) + first, _ = networkdriver.NetworkRange(network) + base = ipToInt(&first) + size = int(networkdriver.NetworkSize(network.Mask)) + max = int32(size - 2) // size -1 for the broadcast network, -1 for the gateway network + pos = allocated.last ) var ( - firstNetIP = address.IP.To4().Mask(address.Mask) + firstNetIP = network.IP.To4().Mask(network.Mask) firstAsInt = ipToInt(&firstNetIP) + 1 ) @@ -109,31 +103,16 @@ func getNextIp(address *net.IPNet) (*net.IP, error) { if next == ownIP || next == firstAsInt { continue } - - if !allocated.Exists(int(pos)) { - ip := intToIP(next) - allocated.Push(int(pos)) - atomic.StoreInt32(&allocated.last, pos) - return ip, nil + if _, ok := allocated.p[pos]; ok { + continue } + allocated.p[pos] = struct{}{} + allocated.last = pos + return intToIP(next), nil } return nil, ErrNoAvailableIPs } -func registerIP(address *net.IPNet, ip *net.IP) error { - var ( - allocated = allocatedIPs[address.String()] - pos = getPosition(address, ip) - ) - - if allocated.Exists(int(pos)) { - return ErrIPAlreadyAllocated - } - atomic.StoreInt32(&allocated.last, pos) - - return nil -} - // Converts a 4 bytes IP into a 32 bit integer func ipToInt(ip *net.IP) int32 { return int32(binary.BigEndian.Uint32(ip.To4())) @@ -146,10 +125,3 @@ func intToIP(n int32) *net.IP { ip := net.IP(b) return &ip } - -func checkAddress(address *net.IPNet) { - key := address.String() - if _, exists := allocatedIPs[key]; !exists { - allocatedIPs[key] = newAllocatedMap() - } -} diff --git a/daemon/networkdriver/ipallocator/allocator_test.go b/daemon/networkdriver/ipallocator/allocator_test.go index 2b63d9a03..6897a0a44 100644 --- a/daemon/networkdriver/ipallocator/allocator_test.go +++ b/daemon/networkdriver/ipallocator/allocator_test.go @@ -324,3 +324,20 @@ func assertIPEquals(t *testing.T, ip1, ip2 *net.IP) { t.Fatalf("Expected IP %s, got %s", ip1, ip2) } } + +func BenchmarkRequestIP(b *testing.B) { + network := &net.IPNet{ + IP: []byte{192, 168, 0, 1}, + Mask: []byte{255, 255, 255, 0}, + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + for j := 0; j < 253; j++ { + _, err := RequestIP(network, nil) + if err != nil { + b.Fatal(err) + } + } + reset() + } +} From 92e41a02ce40c7d3446b8ca7ec5c5671ac3d8917 Mon Sep 17 00:00:00 2001 From: Erik Hollensbe Date: Thu, 22 May 2014 15:56:10 -0700 Subject: [PATCH 267/400] Add Wait() calls in the appropriate spots Docker-DCO-1.1-Signed-off-by: Erik Hollensbe (github: erikh) --- daemon/container.go | 1 + daemon/execdriver/lxc/driver.go | 1 + pkg/libcontainer/nsinit/exec.go | 5 +++++ pkg/libcontainer/nsinit/init.go | 4 +++- 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/daemon/container.go b/daemon/container.go index 2ae263289..20b86f7e2 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -584,6 +584,7 @@ func (container *Container) Stop(seconds int) error { log.Printf("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.ID, seconds) // 3. If it doesn't, then send SIGKILL if err := container.Kill(); err != nil { + container.Wait() return err } } diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index 6b2b2cc46..2e84fcc84 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -181,6 +181,7 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba if err != nil { if c.Process != nil { c.Process.Kill() + c.Process.Wait() } return -1, err } diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index fbc751204..354527873 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -40,7 +40,9 @@ func Exec(container *libcontainer.Container, term Terminal, rootfs, dataPath str } command := createCommand(container, console, rootfs, dataPath, os.Args[0], syncPipe.child, args) + if err := term.Attach(command); err != nil { + command.Wait() return -1, err } defer term.Close() @@ -55,6 +57,7 @@ func Exec(container *libcontainer.Container, term Terminal, rootfs, dataPath str } if err := WritePid(dataPath, command.Process.Pid, started); err != nil { command.Process.Kill() + command.Process.Wait() return -1, err } defer DeletePid(dataPath) @@ -64,6 +67,7 @@ func Exec(container *libcontainer.Container, term Terminal, rootfs, dataPath str cleaner, err := SetupCgroups(container, command.Process.Pid) if err != nil { command.Process.Kill() + command.Process.Wait() return -1, err } if cleaner != nil { @@ -72,6 +76,7 @@ func Exec(container *libcontainer.Container, term Terminal, rootfs, dataPath str if err := InitializeNetworking(container, command.Process.Pid, syncPipe); err != nil { command.Process.Kill() + command.Process.Wait() return -1, err } diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 301210676..509f3a279 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -126,7 +126,9 @@ func RestoreParentDeathSignal(old int) error { // Signal self if parent is already dead. Does nothing if running in a new // PID namespace, as Getppid will always return 0. if syscall.Getppid() == 1 { - return syscall.Kill(syscall.Getpid(), syscall.Signal(old)) + err := syscall.Kill(syscall.Getpid(), syscall.Signal(old)) + syscall.Wait4(syscall.Getpid(), nil, 0, nil) + return err } return nil From b01c3283fafa01228a566e128dbf4b016c308d04 Mon Sep 17 00:00:00 2001 From: Erik Hollensbe Date: Tue, 27 May 2014 12:25:27 -0700 Subject: [PATCH 268/400] libcontainer/nsinit: remove Wait call from Exec and Kill from Attach in tty_term.go Docker-DCO-1.1-Signed-off-by: Erik Hollensbe (github: erikh) --- pkg/libcontainer/nsinit/exec.go | 1 - pkg/libcontainer/nsinit/tty_term.go | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 354527873..0813470a9 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -42,7 +42,6 @@ func Exec(container *libcontainer.Container, term Terminal, rootfs, dataPath str command := createCommand(container, console, rootfs, dataPath, os.Args[0], syncPipe.child, args) if err := term.Attach(command); err != nil { - command.Wait() return -1, err } defer term.Close() diff --git a/pkg/libcontainer/nsinit/tty_term.go b/pkg/libcontainer/nsinit/tty_term.go index fcbd085c8..fc6e1ab49 100644 --- a/pkg/libcontainer/nsinit/tty_term.go +++ b/pkg/libcontainer/nsinit/tty_term.go @@ -28,10 +28,11 @@ func (t *TtyTerminal) Attach(command *exec.Cmd) error { go io.Copy(t.master, t.stdin) state, err := t.setupWindow(t.master, os.Stdin) + if err != nil { - command.Process.Kill() return err } + t.state = state return err } From 8b77e0183eaa7b3a87921f9655e2799d300bf775 Mon Sep 17 00:00:00 2001 From: Erik Hollensbe Date: Tue, 27 May 2014 12:04:20 -0700 Subject: [PATCH 269/400] Fix race condition in CLI tests: diff was not acquiring a container lock Docker-DCO-1.1-Signed-off-by: Erik Hollensbe (github: erikh) --- daemon/container.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/daemon/container.go b/daemon/container.go index 2ae263289..a4bc83539 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -672,6 +672,8 @@ func (container *Container) Mount() error { } func (container *Container) Changes() ([]archive.Change, error) { + container.Lock() + defer container.Unlock() return container.daemon.Changes(container) } From b9de22e82860a7e389f356d349ccb61b7d530c54 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 27 May 2014 13:38:24 -0700 Subject: [PATCH 270/400] Update wait calls to call Wait on Command Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/exec.go | 6 +++--- pkg/libcontainer/nsinit/init.go | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 0813470a9..f266303f8 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -56,7 +56,7 @@ func Exec(container *libcontainer.Container, term Terminal, rootfs, dataPath str } if err := WritePid(dataPath, command.Process.Pid, started); err != nil { command.Process.Kill() - command.Process.Wait() + command.Wait() return -1, err } defer DeletePid(dataPath) @@ -66,7 +66,7 @@ func Exec(container *libcontainer.Container, term Terminal, rootfs, dataPath str cleaner, err := SetupCgroups(container, command.Process.Pid) if err != nil { command.Process.Kill() - command.Process.Wait() + command.Wait() return -1, err } if cleaner != nil { @@ -75,7 +75,7 @@ func Exec(container *libcontainer.Container, term Terminal, rootfs, dataPath str if err := InitializeNetworking(container, command.Process.Pid, syncPipe); err != nil { command.Process.Kill() - command.Process.Wait() + command.Wait() return -1, err } diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 509f3a279..8139865a0 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -126,9 +126,7 @@ func RestoreParentDeathSignal(old int) error { // Signal self if parent is already dead. Does nothing if running in a new // PID namespace, as Getppid will always return 0. if syscall.Getppid() == 1 { - err := syscall.Kill(syscall.Getpid(), syscall.Signal(old)) - syscall.Wait4(syscall.Getpid(), nil, 0, nil) - return err + return syscall.Kill(syscall.Getpid(), syscall.SIGKILL) } return nil From 5310e8575f5a251000bbecd6d998eb11cb95fe04 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 27 May 2014 13:52:05 -0700 Subject: [PATCH 271/400] Update lxc to use cmd.Wait() Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/execdriver/lxc/driver.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index 2e84fcc84..54f105419 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -167,6 +167,7 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba waitErr error waitLock = make(chan struct{}) ) + go func() { if err := c.Wait(); err != nil { if _, ok := err.(*exec.ExitError); !ok { // Do not propagate the error if it's simply a status code != 0 @@ -181,10 +182,11 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba if err != nil { if c.Process != nil { c.Process.Kill() - c.Process.Wait() + c.Wait() } return -1, err } + c.ContainerPid = pid if startCallback != nil { From f2baa364a13ea3e22719e3637bf6f5f42e5dc677 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 27 May 2014 21:03:12 +0000 Subject: [PATCH 272/400] improve numeric only id detection Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- utils/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/utils.go b/utils/utils.go index 1a140f650..0495dc6fa 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -514,7 +514,7 @@ func GenerateRandomID() string { // if we try to parse the truncated for as an int and we don't have // an error then the value is all numberic and causes issues when // used as a hostname. ref #3869 - if _, err := strconv.Atoi(TruncateID(value)); err == nil { + if _, err := strconv.ParseInt(TruncateID(value), 10, 64); err == nil { continue } return value From 5f5d66ade63f8040dd14114f2c7161b16a806c5c Mon Sep 17 00:00:00 2001 From: SvenDowideit Date: Mon, 26 May 2014 11:52:57 +1000 Subject: [PATCH 273/400] Rewrite the mac and windows docs to use the boot2docker installers. Docker-DCO-1.1-Signed-off-by: SvenDowideit (github: SvenDowideit) --- .../installation/images/osx-installer.png | Bin 0 -> 117896 bytes .../images/windows-boot2docker-start.png | Bin 0 -> 70768 bytes .../installation/images/windows-installer.png | Bin 0 -> 81366 bytes docs/sources/installation/mac.md | 196 ++++-------------- docs/sources/installation/windows.md | 99 ++++----- 5 files changed, 86 insertions(+), 209 deletions(-) create mode 100644 docs/sources/installation/images/osx-installer.png create mode 100644 docs/sources/installation/images/windows-boot2docker-start.png create mode 100644 docs/sources/installation/images/windows-installer.png diff --git a/docs/sources/installation/images/osx-installer.png b/docs/sources/installation/images/osx-installer.png new file mode 100644 index 0000000000000000000000000000000000000000..635ac354edf9c96c8362e14c394dcad4f94653d6 GIT binary patch literal 117896 zcmZsCWl$W=_x0cef)m^=xXa@179==O-98v(*wWaCq4N@!?`8F=X6+Nt4dY@R z?J$1xd3`!aCteX1UB+=1D+84_w!{F(eg|#mZfWnzw!EPEVp#4oKdA&Ox)~1oNV=5T zX3ii&)nyow?V_1wcM#xH1kdlfGFzvI@9&R&;vUUPnq2u8$4$H5akX?;)wSmA+I;+b zJ^HPjFh3?mU#nbkd^|zY75;|u#>7Ap|pKobs7WnSBUdM z0R&U)!2$rom@f6b_*qElDGM(Vu@1@aezm?F5^e9kbM>#+f8KG+KVxH_@^G{_`)U$7 zzH&qvG0Vl(=e@zC5S_yIk_8n=MyCNBQN6rr^jQ>u)b!b|!PA(m={~^ntWI75fc6f{ z(A))o`eI(1O>FU)#!Qo)gF#+4*``1pT6UPZ=bl2GVO>SYk(EL2ua3qR@f;M+55uMB zQGrnMcz;vM`|s~HUPTgH;H4V;q6m?TtUEP>uzBq!qb`HLaQr;|6IVA~f%-^cphO6S z_s*L0_K{{-P_1VVX!2%oHE$SWYv^nfTTopaTsa7!L&7_3M<`Zr7BjMonIp`T!_gTv(0V&#{WZt?$J#j|CKMuYDLX=IxV09#=l}eAH0>zfzKqA}c`m+MrpM8E& zVxJvkr~qLwm_8V@LQ1B>siqE{m7+W!7vGwpL`8E^Pn4JGmI1Ja1|vqB(|eIW0(CVcWUb^biee$1Flv$4#8u%G=wOKqy3ju*VCUt&P##U?=_Qm-4ocXd=C{O~JE1{_= zgJ26Dy#C|?HgAP=e+35#_o0f1(Lt^~RgRC{ARI;#CJ)7`c1tc@&Z}tOcy0H!v-G z_y7tm*>Y=rwt&tjG~n6`=oLanoSFbcWfV>5+aRqnb&LrBXC@{m95!X}0IUOYBQQ#7 z{}HjF$f1AVdVaKE2+2*AxltnaLlwmB#27L-xj;^)A^8Y41$iV!(M7=JH| zEbO78CXq;Q0mO1z9LeL9pQVH5R@%Ix<;o1L8|ueR+Kd8Oyfnr%siU#=K1c3LGKb(0 zPPU?)J29yQF0|);+)z-$JTGcVd+}MpTYY3V;Yxu3mob!5X2K@==J}06Mg4ieR_@00 z^G^d{jdf2Q}SDatPMa*yJL4J0oQmTo9jpUt0JO7R)24yn8K{82$O}FP!h;lW$hLexCB6pgM&(^Xg z5ktB;4NcAWuTR*B0Crvwyymlk*}sc|QI%vIs#w7TpD-THXA#H(zlkB=XT? z@w7mR8@n9(e2P4pd3f4NUi%UvzECp|gsVi6+L7 z^XQu}+~N}#$ibqu=JPn7X%@=~ikwiQfQsi}%4SI&PtZrT_ho5bOd}-F_mFy_4LV7woExzTuE+f-< zjyF7Na4+I9!dt>}{gs=#^S)%5d}JD-0`|<};-W{*_|?Nsn-?_|b>nRM>^;A;BBYg& zjEJ&YVS}IWr9xRF;_kVZzM8f;M~ujFS;1pFJ|d!G?rlxlUGB%BgH5x z3T6}hN=gc4q|XoZH(sf6R43sp_jlL!%JF754ZLL%{^T{q>OOsrN;#o=Cpx?u&^Ki0 zsjcd}rbU;|U4FAUFZLp@Xk^ADm>+j!0K>HDJ@Md2ak*0k@XWi&(%FR6BGL^HzruK& z#DcLY^5GB2y*{L-(|!(a*hDjF44$I+trAq&VtaSl6?ApeEgN)s`1gnLbdhW=q0g)t z3P9j3^8q~5jgU-^a>=)#VL{E$d*K*6?OW_jimBN9oyNQ9V1M;?ZmlawxfB-wCGbM@ zmwn5~xpP9UI?Q)kzEW}Gp-?JDX|dye04d{8du~iTOyw@ zyHRawY8v_krcf;<#a?d4st*}Xp_g52{?gLIM>@Ols?}|T#%VgM2owlQ5?gC^)b@ET z!r=IaiJl!_<5TQj2RobLeej>pg~blnW0G6krgbTImjOU@Lf2 z@2G8HK;ocfHrM;Bt=haL&*l8~-<3JhW`5G2N6Nf>hF%Z}8w0d-G~s z^3}kD>c4n+0F({O*Y)OavtP19L+<|1PIzHZ-p(XJjG~znkG8JvG$e`I&7{-uW>W0S z+^0m_;bb}~|C3{^;}m1ajF_D}$iQXGU0dy3?e}+?JRF`E>8Yqbqbub(nxym-o52&` z2t3nbz@<0O2A`BhbfZt2dd|OK7uFHkjZnBsO)qx^&7V*A@H-pX3R8)DUZ^=Zzy}Cq zJKZ<>d$S3js|^H9#oMtN99O`lvG4QqQhwgq3AQ;*7hYwv!S6`w_Hx-xsLHI{ z#q`fdWc|5+P$AzUurg~&!u`71s(+~unPF}+fql;d*QXqwJ!E+em+~H%wnFFhGO@4-*Z*3V2xX|P}1+1obTRd z@hmgTwMfbfa^7k6uy0KNcoMhM(x}u=B=(sD>XlaTvQ)|D#NtZa>IT-`l)y8hCxH$i zzqy0lj8|O_50MzSw%c{z=JCm{5y?l(%aRgc&LZg`->Gw#L7|H2&WlaW?UL=Q6)%E@ zjG0(G;|JZDYAmSRa`QveylC{mMpjmqA!n28@_%KtZchFsVC6+x4Fo9)ax_~XY9=gLZb4e5V>479bilVR9XA(@{u3Ty52 ziZV1U@LM=k-f?1E3AVJ=_!SCa>a$D{d|2uXSi28Mk-4oTJ-a@&t?jzJ?e@EDYHVa+ zvVC4|Rm_L`FEKJ4?FI1zsz|f|B;ISYS2&!FG~O|q0LZFsoNIMMKtJ~c8VlakWM#G0 zKcB(}@F!0leYr(ti33iLs&nIu#eJGxRb5a3aWb^<)ENy91e9Q7uuw#7uF70MlEx2w z7NeY0=P2z=I%>N*Tqs&6)VgJxWp7V`B8T1ZJX|1`j!I(X#)w!Vt2*y@Clm2AbzzMx z14ZRvUcTjS*ss1zp1)q(pY}WAd)Z$sT$2QRN-WZqr7Ir($B(@6OX(0fXL<_v!!k2S z!`^*d@zRg``xU>&iDVI7((3S>h-5eBEv|mlvpa9nndnk1bX!tk8O>OI4z`JpXRoAK zO96fcaogwG?-mN68slmS8t~TMjmodQA79;~S5C$^;Nl@Sa;jHHNeKr#SBps`c2gaS zTm&8ip(SKgaEy_eb{Rj8f=lrugBMD#0)2?GN}3))Cjt!#-#C|B!k!-KW&d4__xK;L zhuzA4Ix?9E7tI?@Jd`Tc1C+Kv$#`_XowA2e!lTR^jj@4`ivTng7wF5G02_`8LQwhC zKs1G7etn&7)e2XX6Ytuhl#+J<=qWY;;s*fU6d>Fll`rt8F*{KbyqqM;4Y6=`9CXYi zneO>;`30U1?^+1vs`&L*Q4PJ62ymH1kvj!`pabl_Zxvzz9+0=6Z&#PIs)U5|ks`sW zM1>C5!_J)Xo3IeYfs&>Dyn5T0%kSSG&|5sc^71-wUv^7TVO~kTWfz|>glEQ7-_5T+ ze4&3_o)df1b0v*f?{;1OCgku4B`J#cOwO$;EhfXQP&*&OI??!buM|4v^+K{Ud6-I6j(P>6^&xr8zYKAAZf=&Bmwb8k zkEmpB7le;z+={{v;Aweng8fSrYFoR~8Oq!EaD7@x{$^uI`vtF0?|9~u!m3^=^-&xg z?z^wL*el1JzAmqo&7brj{c0$f&Y3c(P;!xbd5IC>yo??&#O6*)ZQDNWqhgyT?)Bbl zF<&qWQ%{f%EbF(n((PT-f(T~ur*Gzdm$<0)&e5?Fxm!z1ZLF<(?F&Wo1muL1VSnbu zzDhnkMm$=u7+VmeSwNQZ^^TV>@>n6IhnVy|`J@A4K2$qq7}_KjfOY2+Jq{k(;y$hb{@ULH5i zI1W-A9v?3?y9Nc_QF2t5mzQJF2NNb(%>UIu!lmpjdQ?M%>fR-Tfr%?%2v3Myy&!J8aq&_%8wWGMP@g-ENe4xmi%-M#E!@ znwq*%RNNLUKsFyg!c%8A`pIwgz6Oakj6{T8{b5P#k4Nh@)zu;p1|mkW_;lY1exb>O z3hnc36rtpM!v$o|rV$OX=+g-&VRS0j=WrfE{`x%ppk0Gwx&GgYTq($NV)TTmRHR! zZCza||@RUywMr(95 zY2@aVh^@M7ULj60?10r4tsU0sBf}xyuOt9XxF|`)JS%Us&eLID(qu>OBN%4kB)W+J z1U%cO#gaMtMuaMTMxfefjOtLc;7dDZ>TGc5uD_;Y_JzgIT;BG&MLtCa>LY)YJ%vGM=L10kCl z8;{?DT!u~d=UGRResN#yGf(+#87NRFh0%Kxas;B~fZkV{fDS{rlRa1^s%8?}Y;N>n zy}j+gCFphMy@Rf-B#8ecB93m%PN>#pC68jP^Qw#B-@mJ_i-~FJ>5hQEJf~y+sUo!3 zxtK!!*IN-SSBUPTW^?m{_kq*RZY!P2Tt0V4&PzIlD|JUlYL)qSw|`DaD%ykoBgyiW zacPGIAQVVqwX?Icz1>VB0QTwT9R8P}fIjeah;M>>urNTZp3Wr{nhXDZy`& z)A=hwrCy8m+dNGTqv0j!KzMT!K{B@$$jxcJcQFb}^zVe_{Nn0P#X{ceE4m{DaS68p zs<4%WUuZx;z;Z&%)lq2I`#wBy9WAwQON$@Ro6esl-AGKwQQa#&m6johme5H1@%3w? zFDTU^Kzt16&a~;ztKL(K*e2dLk4p!&vRCPdD$5SvnMuEB1;(W3K&cKptSl{qxe9A; z-EZrpkK8KwTY34r$-EA53Bzmx!%OMK9UL*MZtMHBm7=|$c|Q{F4I%ky0ksR9klEg%jcr%$4I=Boecyj>;E+JS^cS7bFgGoSyED0ULG62`QpXoagS;K}&P9sIi2~!RsGJ)o;4myGzH4^^h5m(iYj9;*xQL0xx!vIC*FCbC}I^&F>0b z+56M!rsigi9?Y4}I@>F4&Frkd;dAf$A~6WMu1YkB)Exwy3-~U7tKjmQnVUQB+hF0- zS#;m-ki1NupPyfMAHb{dz2aEsOH}#Lx`A28prbT)uv}-WDwsQAH>;XVV_I)FD(#G@ z;~ua7ItRIms-e2sHDd@*V=syrX-q}ZrD|Nr`GVL`zqwh|vz%!y1p{WWuF zFm(l21TQxq{5SUTn+q)4JdprI0UP^$09xUAO-YbMMdl_hYb&O&%;#}7Op*qNY5W)k z!ZC?NgaBv>dbowpzg{Rb;PY!EqZqDn z`v7sAvFc@{0tA2*k{D}Ee%8Sz%oTaMq{dD4afpSG5k)MXcuWdY!)B#s+bjw} z(0iYji zpvy5lV?k+To*9jjNU=E3DjFthd1rX~JOH6sV2 zar*Fq?dzu9PIq^ANy(tL7>{`<%(a*Y=2xo0lD!V zGE*8uI<9)$z*}1|;!cBuF?x>flZ;n6GflFDj^PbO-QmZNAGIWsl#tZewr6#9b+f&L zkT@hq00-y=sWe#VEE;W)7uR)#k0>0A5om~&&cpK9*uaU7{Xah3j2a4O zOA7&g%051suI6cK+P!Hm%lO313MH?KxiMLF`*WOD>|S{nO~*^fDPh#MD6ufDUwO6V zGYCnLO#Jy{SS0JmyK~}i-&xHPDEvVy!7My_wREEV^#h#Qrd!GNlAF4 zcC*dgni}KH)f?YV)e$kdNFc|{*TaxTrp&Bb*trF-y`aK(drDo5okM0qQq z@TJ{Cvr9FpgNp?{#9I=_vdR4NqqBQi#&2z|2a)tc($u0#q4%c^)~Qh2?KDRw0e=_7 z)N03iY1^M0$<65gwpHImJrKI{QUlL8EL|-q-on?YE3( zZmtvYT8lQ%(^BfIqvfk9$vf`lH*KEwCoA>bkQ<~Lg(T(LwzJI1c`UmFewWKn|21>2 zb{%J8n)fNo$g*#%ac(4%-$I@7v5aEp!36~Ogxh(6!-ej|sH$(@_7L>H%>G?oPPta8 z2q!4@Pelz;jN3KXL)18=r+=F{yMF)9`DJF|+wY2IWYMr0!cipz24R_g>ml=#nr^~N z#V;_!yA9$p^fC3EClfG6IUwH2V?#fn$4 zU36hnFyj53q~zgGpCI(%m#~M%C$;>>CNU)B64$*+2LMZT(~SZhb-0y&DU54 z?LeJBi@B*(HzKf>6jLutGB`sK`RCvF_icw$u8_Z$n?JtVtoUwOBA(x9Wxa4b|!v*VMXLm@BU_Vagf^q8; zwkwCra<_!Al?CLz>6{YSX8$?-{hM8J<*i!n)>%nTT#?Ybc!z ziqV72b4urtYEejI25V~lZk!ICvztNG!^4B+Ozi%?DYw+J{cEtNV+Q5%Ej7A)f|>?L znY*P}fzHsF3k7c|;s7yj-;kLpg4|xi$+TWxALRD;v;=dtq6c&&3dX)R!S%zQpt+X~ zlxI?WaTI6(F97`J$bLm3jH#VU91m%&p;l}C?J)Ice!k-Y4H@I&K9?7+ek=Nx2COW| zqA1$cmR8$=?E4Pan%YVyYHGOWLq!jMOfLIX?J&_itN1k9-Yx+#)pk7!@vD*XxF6i1 zH5VS1M3zKR?wSO@7Fx-LSFhjb=?P>^#3(QV*xokCnST2VuqGVFQ&Z#E-T|6KoPEoV zpfey6H0FOWc<_6N7<#C7a4GLN5|TG&mZK zDqq-YQJ>G1YY$$d5y+4xLFU%O5L~rAb#70OpN&^ll)%F8wT+d7p_SERq*SoQA8?2(q2dEO+a@A zkAU!>oLRgiJjddOhK3fQs;1;&bC+c!CuEx2>pfLd+Pc#p%gdvRT9}{D8sAxgyy_Gz zREM{Bblj1=9roD&alqrmG4B9&nwqfJv=CAZr5D-jQXKr~dBIoLfnaH{SbJw-cVhFU zxixCluyHCOc(o=wX#V4ixM^A$zheu+R~|Gk1#*6dt8*lE*Njq{RgEj*xocVGcy@>-EC!V-Vyq63m5GPxxH}Wf)B$^3GU~ZlVQ7BEih2Y zD{P`n1XiCD0Mum6f(3>>;vHApeZ0tckgCML&S?FQxO8A~hpGJ44!;UlyU#)MvZUY9Qd7uPEg*Nci*`V7 z5^VbeaaW;Rw70v9ZN?|LeHw)5s;zJ5%$^*g`LKLBGjNweD}DRxZq3cymp9wIVQ0$L zwL;WwO{&8F?Q5UR)6~?Ose%xLuQ!@@BJ})i29jW(N$=_-q_A2%DU6nk$W6tp{CoN+LRis=Umcc`N}2SMymLKp#6( zlK>A7FCTGqfH)74B})>SDE#~D*DF2K?K=%-BucWtq4=Of4f}D{tsfPYpd_&> z+nLnKZAM!Q9)BfVFtyOnyE79pH6@;%{5(b>p#+K8y1fIDT5ems3bV*xyiI0V{{eJzGaDgxHCFfbE3wKkFMO;C&5 zM~Kaf?aZ2k%Zhs>Y}g5?_S%{H_Wg$sO&l(=3SOv?>0?G{iDlPFv_=uB1$;f7o z!a-kc`aGI6&3*8^6H4j!UmmQI5`XrJl|As>Xv))VZu*xo((bO|*9#;mD%^ zFMql&*K=3=M+K*}g6XwZQ#KFZ3ke^qnK$x*vDJ`-f5ghf!0SfKZ~L!_p0 zWBPSiT)=aMXCdqLEVdR-x;RKO*G@=ZZMiwvN?nu6_2VtMC*xPwS%o_{I8M8iK-L?H2?(C#znTUWl z0Re3@<+ZgbxNr1Kp8;(yM$`k(XojD~xI!qX+%1i<`#@YdwB2VsPL?xcMSHXNBF-=! zq8n$Dn%yZONDN_}i54}j_X{3T$%b6+(zjr)FDSip`Wkx@Kap(|dFbDFlF4*6@_g+v zVBDz}*@tuMPly`~3j0Rh1uAC0@9al+k3opSpo8j`=%#UT<5>*gm#A$%3IEiXLbClL zBga=lpg3v95iGFhqT?LZ3xtEBHZ+^gv^hpwxJ}7?xiy#Yvu9_=*sG<;ljbPb3H99P z*FdXnAn{}vl5pzxWwF)bFw}6e&zObK@3BVPyz?Z(1({^q!p_u@b~Q1|2`KlR{Zec2 z_Xt9!dfu>uTonMN`|Kfo82@;sjhT!mC3DDVJwVnL9K$qQ4>`KmZ~a7uN1E4y5vg=? zkCBzth|}Q;7H~u-zoEdPVj|8@VG-kh-sr>#VrF0(Wq~VDB6YD(Cn)G}`3X?ijqrOW zK{@4ly74+!4+>jwgPf%`n51Nmt<_O{WX3vD2okxR?0*upX4;1qK}$*w{>Q`*K^=7^ ze8$o01_FWmI+eH`d)}*Sau~$3Z>J$uK$rz;?}?J(bq&-4SuqsiH)N1jud^0}0Jrxk zYk%>N-WV4hcMiK(K_hM07X(b7vKsAVR;JLeLS+5zZ*ZkqVPHxcCyt4@j^mO(DBc^| zZrQ9IK3UZ(-L91r>zOfZfTAJHD8WP6h70xnXM=7q&r3#0KU8v#V2F zE#|Xs8KRdsWpLA&1^MMGlgk{ncE0-mm ziF}-XqPPKD@-H`+UH~lD3V*z36y{HtVT6e0jWTg|6W9*gcd_(y-4B=XuGXVwk$dmCI>Z!Bxu(F z<%?GsTm7v)x0V`ual&(E>s|#i;Cg83)W4maWEKbhrIYH}4F6P4Do|Xze8m{eUP(11 z#+m^D+~dRwz=;L#aQ+C`|F27-F%BO#Klw@~YwM;kUkt^R7DuN}WyyrI*_wr~EpcmTaRhsV2rPS`&Q2}l zpC!5zoW=cP*W4mP(uPZ3v@KZi>bD2|M6YLL2UhX5J}rd8z+wtSN&4ngY5FD8YQpZl zAi{r?pg)mB!bIQ$NSiD`nW&Jgr<+*5)tRh(-1R5RO-*_M!IYzWkg7`F+S;4JUzNBa z2`cYj{-R)C3CAl=5IPx-9i)Kie?;ae>HCuby$itFB0(~gxr9U5ox&ONZO<>H6({a| z-%iI==@WC$V=FfgE-ty&5xB;o5kPR2NBtjt8V`}*~zkx zQ79o44DZZo*7k2F_FI_-XSQzUWwZ)0DQ~?_YdzD@un6#BJvN~hOKr7LVfm1DV-oyU zgpa9jJRsCm^ux^G@feSC|Bx#s-qTM>=8?n`4Cp*=w39J}&{X?Ha9twxm|&}OIti)0 zH6-u3^u zKiC)dVaTla@SeivE7%JZyBY&$fr+-g2e6|EKfDwq?>ioH7^f=0!g0&0kA=G&^QH zOu&8SG9>#vkL~tYOS;D_daOLLx3#F~m;ssKr>>JNwYO>wj0_^jmjmeBUjnHn`SuEG z%(LlrZ>tOA85wg%L!#w9Lfnt&$D;rs%dNn_EWXx%Z{%@0VB0u2bv)n3%&O;po1gAg z4trJ~YwP1)er76PpO1Dw&>KO%8xcj}HGU7$H_P%p&O;s23_?1pGm6oBav2gQH#T`a zhI|^yy;Ha{IkTW*1T_j-{{MNd*IBSPe@2)*$GJuGQ14W;NoENm?0`0RGjSyLnb0); zVEL_Xtkhqud7SazyZ$q3vqj47VD@Oy_`JaE=iXri%her1%u>~i% zt0)0vk#i>Md8H}SQuyLeub%G5KZ-fRlpFnvd&E9aH#Bd4E9b ztDsAC?KTL<{SM(7&hoP!13+dm_KifLvTn^t=+vLbY4m?tPzjWbp@+(k2e?tzJeEIf zKtBUDvyT7aD^sF*s|zGj!WIlv{i{BVVe~MxF~%=iY!^%jop6bsnzUsWU3|Y#%Ns+D z!e_K4(dG{`rQ`~fJpA5MphxZ3r|{8@A|EHvc*7?fI&0;}c%EPoA<5{0&k`4OYcT=Y z90+jG+5MZ)}2B4vb7lWjR6@g;@Ns1Kx9(cgw z36iY^%E+Y&>-?@9O|%;vNgI^%W1uAz;C;bg2mN)g{zJgo+6FGMwUA zn=2I!aF79jaa1`LLkwW?D>{&x(C>)JMM$K++#IuHU{=!i%F&QZMu!+ky#spF(8Aew z+?9i3Yy!;+?Om8qf8YYW8RRN3lo%pF&Q$o57F!b-2-jDOwqH>j*HBQyrkpjc7UtV$ zO*TnwWgURcW6H1ZK#dj^jt-UPc=*v(%Jz$i?{9Zs$IDTIhirJ=*Np*d(@%^Y-DczE z`W$_2GiE{@pcdC<3h&iM`(_{Vn>02O?H#d+H5)A$Y$&d|RQZ$@gt zGnK;{&sBzk=ip*y9A$-}#+I&+ZIyJvJgdT-mewr4E~sKgtexOsam6oPFT@Z*DZj-3 z4TSZ}=o|gwKTM%2A`&cMG6YBk*DiAjd6oca4x;a>Lm{p?4MM>BAm`mvte^@`P_1j74 z%vFIQ_|GgJ-Zq)HlO(gWNB&;h_TT;UJDKkkPOyyOPiex8N2RmF!rLy3()$mTP1 z7`5tR#E-*p*HCcjeRQN-R1_vKAbg$vKc z4!qTg6^NAs3)nC@;!i2BxsqUWp`rn5WGLuGSolilq#$3Uk3AMF5bcq-ld^&*hpgT? zDft*Yi>WANHcmH!ZBRcn3|Z^C(_vZwTX7#tHfRSf`xKxZ;-#sXXlDm532xywx@JNl z3)YoNRdIZtR~S)}e0jOn{fHb9-GzpNPN}9@Lud05uYNsL`;$OwZ~>R}<<`?A#KRUP z#NSpiinZDr9^=bF~5| z$39057_ao^3XoOR@nhS9y)e;HYR0Yt0|Tdg3;d*0BpJu+L2XEvo%i#Li;Hl=WSFC= zcgNzcGG0npUk4h50u2oi7xRMlM*OZ=lAQ9rxWEf6bXM-y*N7OTkPQ0%uux~~AhBfd z%@E=PJ8jJ<727F*pyfZg`gaQ}Uo!A2SMTiQ}y>d$6#y;poCuVqEX&WrF?hy1(d;Mb=Ct z*3fMz{M-`@JO}U>bx?ePYa!^%`o4d_k4?DC)w83iW@79md zY6=d5&gyz@CbFM~r)=RO8Z4hZre9_0EwJMP9n(G!7cto_N795gGHr@b{Ft^e_L=|_ z$_!uZ8I##f@M0ETFG{O^v%ctgkPFpB@hFj*R5Z?WekjvKW{kL1G>75T2;uwc*Tt&0 z-L;K^Dp=12DuSokH})c?yd08gWMzL_Rzl!QSjS;k%=C}Hm=R&nEBc$>_50QNwY3)6+%f-d{A6UWl;*zhiHyjp8ayMf zACnO&prZsnC46_clp=kXn0-Ynwdr6`>Dm3_pIy#!_ghL-T{CP3$_6k!CJ7o8X&o`m z+U8on3ehx+kB=14TyQ_zhIgMXh!r8E5W;hR3Z*=w8D4(nLkAQYnapsD`Aec4GxXzQ z1<39jc|POPeX{mCl=ix)eL5gVv>nt;hdrK~uZb7F5J=dg{cQDcRdh4BdbM^sXyAtb zHU`$w{CQH7dkPsl)s6aQ>KxIe;8TWp>cMIvX+}aKl7zwY>F(QIX8OAc`j8#3u(LMZ zhrWuis{#6l?Zl4yZ~S_jyE8{mSJ|=;lJo&j@W=D)Q23SX<2GMd0Mb(B&Iba#eP|vM zWuN*(2&6BJ!>;^}e=+O`%HEwe^xTag!k(~XUDFKWs3fd{MJ$~?{=WM8dFtKh@kMUbQB;jbE_0s)8~nYz6za+) zDc<|>7A}SM3fE;#*~i6(hXn7On~IdfpxfMRr0$DnZqY%hw3SZ|H+f;jX|Jy7Uek!= za;_f2`!(G|f-*HF$(IOu&yG@1V2Kum_8LMXBK8$`OP8%W>g`#gVs(@kId;~RJw3o8gY2L3jD&dZ}&)?rJ3 zh)S>%+-v_JOk7=C%g4t@&-vAzSOy^4RwSu`irHx7Tx$CKu++pYz-K#7_BulHxRu%C@dD|GE(*Pc-(4O( z%w~6Yepr>jlt@!!(IMEenD@vZ^S2KOpoI53rt4H7`8l?9mC`(^M}ZaKWWa|?#) z%3KYHJvxLv?&;P%?1=TW?NwVM0;ZQ_um5CAO&=V{hRIJEBAu4ZDN^;W(@5*f&kd^` zk*OltC%CR`7n0>p3EsMbu=-D*H_j+3w)H6~;HWG8C)8FWbwv&yj=u9wFZdJzK0cLj zonuwNhpx|FPydP@_wICMZobt9|EuXZHZ$Z(5^%UB-qSLRn{r#4l^+V5tbJizwVj@~e{z?WZY>M;-k`bB%Y@dSOCd(>1Rn=* z$ISx}Sv)WQQyfEHimEdjtz3@0lsQev#GPqwV@p(uzfsJ6yx2=yBHZ#`ST;J&++KyeKU#HO{FNIPeE&IUDv{dF;_PyKI^RX| z=2%rG^!#kQXgh_|oXBeGat>0hL2Orvs$=IQAZuix!N*0AZE{YZ14dsbAKlVJGu2WMx z_!P;WZl$lUw>?-3=H~wnJbx*BlkktAMs-t<3mu)z30K6nliSF5Id7Tn5jvn(@Cd!5 zyRUsZA1#tWiX}z2Gm~6dEkz%~9&g}cJxz6C#XU_sLrC`^4v<<_7q7w$)Or zhpmGHInm=3;{KRiwsWPNn;%c>rPt$>Sj+uI_GhCSi<`Kf7EZBn*@r#8btL%l3zDEp z3f*max=2%{`-Xoda}GmvbsrTdCz2m6oEh17o|&)Tik46(3p#xxe>`fN+l04l{ue;KZWM*gk<;!?H zbs(NDdhUd6A!%ySWVehT^1r_~hK(}Er9Xs7%q)5}5AYED_zA>zyZO2;ogoz#TI4D2 z?Ru}BiHEJ1vUNL!K4VucZmakA=hKL#ApQcK>CeCKn`%Q`*YZtgKQU4V%g%??+##Me z5FzW2V_|puT~}+wi9;W9%*A_%WNvexZVQH=_6HChtl4k#V!Sw2q}tD#mz_E7p=po} zY887H-@DbT;2mbHo}0x8Gsss}A_I5_Wf^0bcM2tD>iayW z1ks=(lr5-T;^|**i>Ql#R}aF%z~DC;;t7f+bApn&AF%(8-fbnoy{{j`Rh2+Y_|Ma} z)nlgXvc8$ZiH&48x-rk6TwP5%4;cd0)W|UYR!jC>t=E4jJ`p}kd?fj{rO(?p7@0lv zgY)y}&(hP*2&tgbqp$FQw6Xeb@9ys^6>TA&|N5wR-p)W?t`~ka}k9=5l}U@HDhR=`Jczj-w%M_MJJ-_Q-jT?2HTW;=o{8x)y0ohhW_g(jO8Q<}0 z#}${=jfS!D)dIgcqt-YGd7{azD^P?4dAjb)aI(zlAj#83L0Fz_*c$oc1+Kll{rWXT ztov$^gqD_eJ?Jbb?5@R1+VAmxfS~6oJ?uJ8GmAS*kp768hUWgSxmzeaENuAvKMjwI zI63&LV?E^mXgcetsM@IQ4~>L$cS(1bgh)yaUD6;9AuZBKNem$&ARsN>-8qyrLw9#~ zedl@K@8u5`EY_U0m^tUZ_kHc_ckRbB*k=gj<$~l{i;ee^#Q%&0FglOTAM78_-p&_~ zN<7%NWLwNtSp!WI2E5S=Qp@_%rrJts&_9RzmFG?zzl!Ya^}C(SmqEQYkGg|JTdA9Z z|Lk2c{*N2wK3nX7Yy980xo+6&*7J!-@0B^A^bzn}HikKKsI2p-m`9Zw*6suRk5t1; zM;VoPz0<5$($SkaDHz$P$YYdkRA*m`JFfQ+E^Hn5HYvyYPdr~i z6u*~08aiD~d^=fcy4r^q`gXllRn19si5d^Xp~(OcCE#$_sHHCH4)k#WZ}`sI_BQZw z`P>bewgC@SGN;LNp}L>Vc=r2@9qebK>*G205X!ecvx|)|<`451Er2P;W@1k^Yj)A+ zc9?$@fUh=Bj+;-A^!!$V{S;Vl^+-cC38|LOqACRiYi%>U=DR}pjc$%a-F7=z3o^u< zmVfKRBtB{R@OsLy!JM0$n`g>R{eh>&VX=laUF~G9^#NFC9RZ_@lhezYy5D&}LyYm4 zP88F-K|R1+udf+L1#Cr@mN}qp;9jq?9`cbQdOse|6#4SB((ZMIR{rh2u%PX#^o1o6 z%NT~w^NBkYnnxHS`}_CH5rMBHUk`mDIuwEu#sy z`l78{kmL20@tm2AhpC0#CdP(Ccj`|hXT=<~|DD0o{Z}?9uI&w5y-jyw2QZnjY5Ybd zJx}&U`1~e%?2EqLj^)dK?ymM0HlY^C3sw?^4*ku#wmp6^FdF+Zv9R*o2;ow-onRmR zh!?tM>FHCDdUhQt<=(fzb@6cQy}yD@rIu;0ecfl}TD+Dv=JecAr}y{4)qWa6{p$jZ`0b3vZdF`*4ECA4!R zA8=TCHQhfEL~e7vb)O>(gFn_mZsbRxhJKwyCsL)(_u&zOrJYFXjt#q#GCEpZ5HWypU4$)Q9mcI4A+BviExwibflr77u>2b_2#axf8FsX}U zS1MTL;rC9lH^eG7qY~!#Jv%evD!Lh-%QZzwaCr31io{^&Ub1QRdiG7G7*oCHlZm?9 zjYOLR%&Qw`YR>mv{_x#xa+_Oc&>jfOM?V73$l@L1pdF>8-R@YZiPZg#PW`jGRU!S1 zfTywb0p<~2guMLFsD6#@=(4POIl3%KCHB2+VTN%#-M*u^-SeT!vj4vZ=bQvqeM_oi zdF=_!kGSscp1sb)j)Mc*B1NP7{d7NOzh)$J69&cV{jTFPS}7iyXKhnf2qBen9(=-SiB;9HDrQ#}Q^SPJ7Cn<&Fa_fy%64dw&dHW8 z3CmIb{xX81XX;xkQnlfl`DD-gB{EQJi;`4Ou7qj6(G@oP3O15aJ?44<`OJs84K=Ob z-t%JS5HlZ`Ff4$a=UTcI6}PrZlDJ2IaVu8}(Unj6&bh9hj zK9(|Q{R5MG=)iS^Rsks><7=q$u8~{uc2#oJGqpD0Y7=!&El~isJp~W}upFJO1e0 zi&pmc@84Lk600X*P3p!L&YdVc+kmP6;-TAmU|kW0$J1Jt?0C_V3w!*I?Q;Gt9R}`l zH?fJLl9YdS+a_hzk6TU0XNcxJaF$M2>;AJ(U2!}P*sBXBO|%Ns5{p`ks;b&ks=g@ymLb@Tb{6hyJRLjfCfpEF1J zM@`l-&DS86QweV9_>@53%_Q_(8dvVFtxdL4gJTbXd#2sf2O}2y@uKJ4Qlo3(imV^q z2xHw6!Hl!Jz)|^MnsBmmZ&PY&=slg^pnBc};5(NMcN3Eoxi>N}NG$Hhi^J{GN3yID z`rtC!h9uO#>Cz|yABG$L9NobKDymke6I7myghtmQHg<{8nba)p$=x@srhd6Lh*~b^ zbpyEUY*mGXsi~>CjKp0xTJ*H|j0we@E{(?BW1)J+)P@j+zjj(sgPAF}m#3SDxj}ZW zcKWm*?j<3(!0Ebv>2|W&&s(p|LaxNUQvcLyx>EMe)^VEucEd(rv@50U z%kh%;%~2X(ld^QE`By*!)PQ7JS#JFOAl7WryXU$y+0>US zj{b%JgNLKk%kdvSB$G_i84Nh`RfSi?yauO4VNoyNe}|Azg(h_bH#IdC740wU76)6& z$y zOVWCSb;4O6uT_7nIW~5Bx_Sdt+9#X* z2dne&v|s+#uY^-b6%W-h4%V#HjGKlEz|S1YM_XeXXY+H>b2;qly9y~&i%E7q-qziu zdkf)RrMl_3>u6&H6MlJBVb&X--7qyiK0Y~_LzT=o$8sp~)$E{?xOTsA=ibne;^4sb zwKpaD0U{40R&N|Zu-e$S2KTPnv;HZ9f(}L26{fJ&jxHW-3SO6UGoBP~Z+o44!Yl`u__z$An6He6@D`#3ttU zY1EYht_PngnVW8Em#!{$ncG*6H&>X+Qspj55#0He`fcUYM!xC?KiY{6X!-$Ko;%LU z-G%WMT3L(BHxrx9+sfIcw!_Ulz1AD5X~Ai#tV`dOBA-vi<~Mq;AOk@iPpPJ=lJ@!`PMU?PLPIhrAwM{hnK)5%S)!(N z$xOd#F{_b@dmR#tmSl=Lu5T8?kW0B0Q7LP8Rs#jqrIti|dJ^?~_rF}Mojk94hk!q2 z`{=-36RVHsYA@;>6MhjYNKQ`+<`M?{s!}dgvP`p4mD;Xk7G8Ni$P^`MEd_4Whq2?6 zryKvUkTw8!im@mCW_ChNarG^u{L(CQ>MX%xLAMDpXAs1J8X1)NfA`|RBACiU!_gM)*zFt~*x zJe3D4hs8tNON!fQk?+|&R=FTmUYIGRBS%4aT3Zi?N=zUzI}S$4?=h^;v;`;;JEe0z zE50{C8pMj^_Al1f1Zsfuv}UGZ$U7+TspAOIZ*tqKjH#Cb+=egD|D34*13c%Ar2PH~ zp0u0-Z;4~$&9XNDiAPe!9Ieaq& z_b}*`M9(DwwaZSe(>2BExl%Ca?Na(EnnZKSNs`=s4}0gzc9 zJnd&2DqU`QchbF&8=b_5KKY)rGQZr0(yaJg`~x`t#plgbH?~?c03gC7uBk&?sCQ}y z&RqaIl-4&TjOEbq<&K3lBzArCr+S94*W(pX0Pm!G+EmSrery8L42U2`fau(-9!Z6k zDjz++yy$&BXRB@`AkU@*MwU6-qC;Y>frBxyw3BTnQmS<`TD^vnfb};rC0#NGRmN_O z6?1zyT~(YKtjz05`2NZFT^!*sz7eLh5>xQs^72gY$3-gfy3B^#gdacOIDVQNwlA&S z(=KXivH|S-^?)uI$3Vqd$~hY@+3Y!*BI!MJI0v+qW^dYGo|JyR36;4Clk&NWy^pX8 z)+E%~cG3HI*V*xS*>q60;Q8eS&$7<`(l$Gpv4`n%Y|66sHOx1J6A$DZi1b_i{C#4_?|oPZt$zb4zRt^T)*R zb^4P+N-Kvcdo@Wzm39;6NGVR{-B;lYj;?H>+E&%j4J?|sB~3*b(g2>z7QOPgEet9X zbJ_-)Q<+O+&XcObgUHOt`KI9PlIfGSkyWOr)TAoSi<&$uM|HIrI>G!_)^rh%Tt36x}wPVp=lDj zSW{p5?}>OmP8X_jKYL$j^9^E<`a5g9up`AbvE;???H6hlaEh8cn>bK~cy<2rpE!Sy zx&4($t**?mGxtX#)c%C4uAXua>mgq?ozG*wCw}AF{k^NrsC_C=nfYOYfu%xP8!=7i9XeVO)XMI? z{fLBwPY0Y{8B%uJHFJ?kuZs!u%8(wQyB|z8yt$ip4~Yt}%GE^ek(+~1s$-$1OB!<| zL<`~@^F>W$TJrTZMErY;tV3!P<72bSOXEvR)t}Dnaun2mc`hc*-_O%>c(~P2iB81* zvr;pxVo6pCEZq>v6A@xm@%17g)N&N!2xc?kf9d(T;yTAtQ0XaZFD8xVFY&91Rr)DPz9O~L zCmoej)?h_XTWHqasvu(K9I@8=vS^;e9F@XXJ=@mqFq-tXNdMdRLV4Sz3B%_q=T{C? zy>Y7DB>&P=25mS>0`Vdw>2o$_XGb#D!?PH+|7Gwxug|_`=1P(B_j}hpChJ7UxBkmA zCY|3EK>3Iw%}v_`{GT5~^EJEN3slo2PHsi3V^aRQFHRD9)YTH^Oq_2q=M@*xQQ=#W z;tSX;1{2tf?!TQNN0xl>a5`c6!BmiOwHNa=6lGbkXK`@NIeN6XX^&x4C~y`7JhBsH@|eollAWWd4xEaev=6@Ka&*mspu>GsVo?c8>=rfQjWoLX98W zhSUtcSy*$4msRqbV%3~qNdC9;R60+QQsj%xixYDJ+w*y@biOZ9I*;XHR(JYG)hD|l zl4(6S%^t_5sEG6UnWa3G>aERsOC>=={f?RKv!ZwM-!C@*(sd?9N+hH7rvP}!_N%)g zI{3%7B-41H3+2tfr8`n&+VTF#ReJz3PKjQif~_IkZRYxkd9@P;g=BAxydZ-RNG(N< zr8fDDBw1VsksSc!_v~TBge&BO_FpW*SBM|A;;z#4ShbT*pCfZjQ^Gj|v=_1e{3+tB zwm_%znd}~>a>T}CoqW&uxx`|d!H~4{!VYLUz&!5S%2lTO3~|yyi~Z(xRJpmhWNAdQ zpofqIS*wU%`nf@EvBcy+Tx}wnae0#WIk?#qH3++3_=bO1^5R`7qR&%Yg^OwLlZQ2xci=JOUiXGpaqGtX#4MD`UYOozeve*nDtOb&z7n zdq^~d>6PY~Ar5`l}frH^yf{AiHQ)nIWxUyfHtfG#IrsWc9LwK+dOht=2 z#Gv%dNB3=+KQM+DNwmo!I(Z2V@RIY@0_;}JV!7$#)W zzf)?&nmA}hmk4G;q9w!#Kx4^6B!D=?Pn?d$4gcnoW4z}th<n1{vzhB)}!*rlL7O z$w)RrWh!z+2-UTvplYJRs2s`|?10~n^+^kM;p6#;OCZ9~Fmrv;+*S=10W?+UfzW^n zXkLygz@8AH-*y7zqDdRhRzd*roy85{;WVCc*U%o^W1B+wJC!c0j6*EVfy(Nfbe4bQ zqqLt6LkN(V0GO188$S;{vBau;sr0vT`AW+tEIK2<**4TY_!)~?=gjjgqsHIt5i**K z5|h!%MX2af9OVb&GEpHY+7{Qq>7kM?(F6U8mDogacm}1GGr?vfn?>vT-x*M4;0E-8 zvp17T#_!;Xc>D_52f37bJAkqym=TX*j4zkvj=*9Ut{6#ZWb>XaUDqTXryq+e z_aRD7!r&F%Qq5^Fu5RD(;Wx4wBqNx9PL#U|2}e}z6F4^8ltEM3;OAA;IG(Kq_7?gZ z!09#0!~A6KCdVqJgAO=<6e$QQ84+lqf_SVc5w&&plA|i$Xjt_zk5wd=)e`aJbK=cC zeE06DUv&{-Kog8B(UQ!EMC3xD}QO0+boAV)B! z9ww_IWpi{2HoA40_7WQe{J4_XHf>5pd^NUidlmD63oEOdM;F#$1ip!MIo!+dXLSG81UTfx7ja_ri;Q>@vhz+u#SKbP_Wv;Rg1< zF`NuyQrhZ~VNqmNsN{_Zt~Il?(@;krb>3i!r-epWI3D2a}hWn8+!g%Bv< zf!@5q@0*vIn~9Z0C;rh=AxIprr~#IZBOS@bHeDChQZ^+1u@60Y7%ox$H8t}l&K(6KO45UsGx zq;0!wF{_qX(E^4r&N|8zK-Dt#$Q@7`zW%d#_9i>wGb9nLIz{xXGy%<6q;A5~uFYaB z>|739N}gF38MC0opnLB~MUZRt3;|*R3aY9IKt&MZ%F9Kb{T2B9WdbIh zxzS_R2TGtoqP0R6L60A@S`;AVz2RpSjioWNz=vAU1$ebajidWmaC} zjr}wV?su7|f2r|oBth?jY3QFDb z^K)9JBVq4>))pJk5lM*j0^@p6B`bH&*g zGV2z2F9Ui2)RJ{N?d-#owTPpr&fN(ps6Qa)Gh&MYPRGIH`AqFnJBrZ-4b5n#)$ir8 z;g)O6wvUnCZhUCMPA`%Ai^9HIZ`dfV+Ew%+CR?PCJ5?jEE#ob|<=94|pm0DenSV_g zu|XQ3TW*JgU+!8=uEFG6s+gPp@gZyN%J1bsfE4~buApb<*S)=>ygt)@#C|c4_5zc4 zq`E7rCV!bTtm!+C{;d{YC+?Kr%c(&7Og+aLchIDj{~<|sSJ>exfzy%9X*Y@+UE21) zubo)BcOX)xj~lit+E7TTudndAjpIM=BGfjF+`8{LyyYiqD6N6Z2IZ}U&oMENF^`Mq zqk4`~A`Q>;h5T{1#plflXTk`Umq0rd=r`>)mEl&|I}hC!S2ig9eMbRtFfmzjhEbtO zx7RV|Xcz|sr$2kN!ry(&XbzG@Lk}k2N z>rG3@1=j(~v=TB`&~%jqnSYr5o<9XeJBsjCyPsByL(oCTLHhm;OF}?;z?MmG# z6<)J(#$^9eWPu^z!pH+~`S32+WNxdm`uLd7)inv3{Pnkm%SWE9H{!s`tT`<7^o0pR z^60F=Q}d5&a;@^GkdC2c?vNs#9hcI2Gn<|OGQmEerDMMZMu%X7=!T@}8UD`;U~0kt z>tCiP2t1e*D$)2F&j3QD@QVKDZ;4^rCH8C)xSUlK@z+S%Hp7EW&w99Fx7^bpwR1^G z&1*^~6x2Nt_B?zBRb(E5tC%TL&JY%2Oj*U2hyXoc`zM7tNAW6<*q(b@+!9QKLGl5d z)wC*ohFX3TLoEM^cErRfVEBA+YDGM$qI(?plLIIxp$6XK1%!6$;>O|k8OmJ3CllnV z1o!U$)GCo)8+}>o^dz^#?r^50kH}_$)@t)XYj*}@3h+HBuZY#5xb(bDKpL9K-CKxi zJkB(a+b6N~N;vu+qp<4~3#tq#0ENo_t(Du$13NprYa=CI9={#e`*IIX8))|K?jxm1 zwt~aUp9N;R5OerEI_q+PZnfph?*ma{Yey5{F-SOz5JDxF=HLL~dAfUgzMOBPEi5d= zoyQ!10vp+solffL=v41;k_kKh{wLato|r3B@ru9(MYmW+I4{SiYugOdun(DjhllS8DD(K_5n>SV;&-S{wQFl-qF$2>VibgG8G% z$+}{C4#kv*+W)1+W?lTcFp@6r)b8m}sXx($GWMCRtYJOkM|W|qHWLK2DeHUb$6$>B zsQ6=Wq}4AVTKv6a4c(MYVhE^A_d6CUNNzx#fEAVf649)*&T-kNTebb$^<1q`g&cj# zL`1~pUO~D);6-~)B_tRAT&SATQlUwRqAqdPhU&x?R$6>I)-H940vhycb*41Znys*Vaj0J+)*uSV zH+LUO;!G28oJw0F&x)$*<=jx;A})^vJsmBr2JEu%w`ccA7-0qsyb&f1T?1^fV@1rm zWTQl~J|TLh5krQtL{+v$7IzLIvmibqyyiy~uWUfLTgi`4U*3ks z#sl(#J`BhN==Z%@uAk9?f~(NlGRehU<-1Q&741qjl@~o#09H>if z)c~J#iJ&H-cZPzjz9lVF3GckVje1kLz{PJ78!9v~8-(-Kz%U%^^ES(;QzVNLcS0Zu zYw32wbjc5PMB#DFa!rUj=nJZFY26V%ZUK^G-nw1cCS|s8B%&Cp&HXJe*ikWkmWq+Q zzD+gxH`ckjoXOpC+GV0HQSY)f$nCRk>a+4g1rPM&SttaE^yI=@@^~n{^I3MT1{YyB zcbEHJ6~6`KR?m|%zzT*2bSi8W-3K*yGTjHsY=0wkTu9gm$tQc({ZH}It`-l8X={>Y z?~$n-n;yr$1tFnMI4{i_cGG!Mi&iu`6m`d#0vvZ-Ak9N1NP5cAnq^MF+9H60JsDO`#7MLwSYB4v}FjB_8Go-U|G# z2VPFqH;HFbz`Ln^z7M4_zIj@}q5RDC14}zm!xwJYgB5^RS9kQ;VZr@x_3hQL@HTha zme)tI*ZwY(F-|QPBiG#~0l~`MTFN)%*fyKu-rvOZo^q{6XYm-g?g~6rV3FCMR^xTi z5rp|IhHo*|jGmt$C!gNY1la0IwRlbPV-l;f5n{Caf4X|-DMfhGYwrz#rq{X6ou@X@ zf#YkF=AL`i`?fOP1ADwYS$P_D_Z7+Sh6;de&S_(bp!Pp)gV)gqT$hbp>lFL>bYq*| zPHpYEiJhf>%djl9uA2V^1{`{3bm9GEm(foXaf*C_IV>0=Z|Dmt@sVSNba6j&=G&Ck zCZzzl7QMwAdb(Fibwmt8sVPbkoqB}IO1i^OKNKpV1Q6ROcgkL@8F$VL%^#SS4u#tL z$Hx;I_@DbLLe>4&e@-&bwDkmX1fLUDM`!Q;O_M`+pUO)?CXc3qyo=(;5)v& zcsk^PXEO;91%z|piGf&Fbi(lAVMWSqh_jwjV(K%9=i%xqx$VdVlZ1=o${h#;>AsQt zVx)(LG~0d^a!}S>=GuZ|F8~<|sN{h{krp{$EO9xWa#-c&Y6|c4Z>0%nA6^xLlhntuER9Axklva8{Ntb6EVM0YkQFkKdwyR}9?uubow4Pz47I=ZRA>sgWGo1B_;C;@+H(AM*?U;_yjH3P40Z0>##NWWd)52p zPtR8XT2|5(M%{Q+;$D!>|JjNaUCg^l1d%YQT;$>%%~Q|cIEDiqzwuYhT%vv>;wyI} zNTo}lI;RB>py~;|S03^a7)lAJT)~S7g-Weu54E38g_S`CvNP&-GW;%6yww-m{a^NN zGi}SMJBIBhmp#|scYkK1tU~=_oGk53nV7l!zsozP6TgnOHWuI)o%$O`{Vl52k^+(@ zAJg`AR6N7q&)>>TeP|6vsvedgWBSZ)`qI~Wvp6}Q%)NU9_Tu1l zAmQ_;TwNSrM&tI#>14jb{OLz^pU;l?3OsAB(h?@z{v@R?@%&YtF3R1$CQcb>umU}Q zle;+e(chx{2ZP7Q$22duJ5p_Hp&hE~q*CVVyKxJ3jte!uCugoPG*4?c?b*G6Ll8r{ z5}l4R$Q;N7Evv7eaTpeZegg`%i1%)_^<8?n0kyGa+amC?gSy&UzXPL{ri(QNj9%GK zCisl2)_ikoIek96?`Xszy@j&4UesZ0a89GPr}UG>dK>pFjqrCOYV$WwSyws01On%| ztru+_9TdB~cy5Mupo_X(wp;G3^<$?T{@t*M~Q%fLLqkoYDrZ-ktPU%>*$$WZ5TO@=1#u<>0%-b zttEVV_C$P@J*!WzX`tmk!8)|^a-1mjbeHbchY1uOfW;j6XN9>!!@n=iI`3q9J_o-d z_*Du|1yRAuKe}Hh&(^MVz3#F8?+ouh6uT5_@<*C2dX~scX{3Km(3V58%=+fpXDkd| zy>y|?t#^;1yR4YI?Y1uZq1kXAU>@hoBvuQ%!#viiGhb#Eq|V1LR$gQfKjR#Etov zWuxj@0>ae)gZcWk;K_2Or-|pamQt%FUo%)#}gMZ zQfpIgd7O%xcufH+RrPf)U4x12nSR?Ts%hSTayTKvMDxuMtwcKyEn?K$tt+V_4G$oy zRqdTSSMtxjLn#yIt;LIF_K*(*F=XPpnjFEjDxmo6r66t4XUiplnzctvuC|*%SP;~a zs&B#L1Px?G$G$yc;4{)_TQE_Ei^s3!PoK*v)olH^#{yAHJktvV5L4SK?m75z;}euQ z<7?ePf1Fi1mr2_u15IN|{(Lnm#;4_mqj^aAQd29^fBx^bvuG#4+Z^{LPPrfy+qB8P z3D-=XbnQ$5U<0}R;`+IyLuYm{dmdz?mg{iFx zd>#+L268at#6qaKzu&TPEv{Q?s1=StR~+^_v;ON@k7dkzEG}m!l^cEt90*{1B9gap z0F<~k9ISt@pRLJXYbwgh&{>j*y4vn|A+B6cP@D zs?DO}virGFKM&x&%-2*UcoRh>nr@ z>@#f>)ScOKw(P)!R)oN1a^aNRdeeGR%G!4#a17+ ztZkPx9*g=ca@<0YI{D$KZwcDvl1R0y|^n*=H2z z^}bF3Q{e zce6zHy$;XNUIXcJUoC5)kv(PxC=~Asdu=nE@5Dzn2FZl$-bOSn0{W5s2axf~-R#a= zDr&LI*>@_h34bK`kE1xXU$!Q{o^)97lz8c;S<=S{)Yh^2u+i>w$YA^1?;f3S*%@BA z^3o@qu~bp|D<;#|;@@|5YVY+Qk@p*8MZ|JnzNr~Co|~LVl-o`H9k+zZgRja3szA-V zOg&Hwk^sB#t-qGA(XZR%wm3bVfes6$2D3}kZCOVhc&jB!@0g>@lvQ*X5!&Zn+3#oV z=UabLB_S7NZ$nI9=I?B|xQHST3OHNuyY-NKP-xAGEn{a63**!)B5HUGJZp2?Hx%L+-8H|>XCQuy1zb%$qLPoQDcUTj$1 zO>5K1#x4UWdOpOsdkr%;zM&uuZSnvLW+PS5c2mUg-cYjS-Dx+D#JN>)JV-|u%b?-Q z^Wz;%C;v~aj?$5lsC7|PyJfk{#7}lSJuisc^shO?*B@5Jjv+3QZ!nN#K_W$i3UX~H z$q`HrwFgbeN3+*-K zY?Ci@0;Y^ACb6;e7Ftm;gsthGFXey0DCW$Ts+pV8T1bXhe(RHDv0gbBF=(tmjzQ{i z4M#m)EbFb#dpVPIUkV||-O27ZYQGcyUgBxGPuMCTxjyPAj~=_gFCJ5RfvR@yTQ4 z%N~E`-S96lreHT?1p~|~@Fk59iX5N|W;Mgqy;uxKFRD`P0gG(DzQp3<;ye)4Mnp6O zn73AgFv&j`7X=*yL|^UET%}|83}|7|=j#wAH;2?7@5)P~G>lqAL7C zf^L&t_kHAZ0)*5dOEt^CWYD=J4vzx}-e%B>DYjM*TvfB(X81d)W!t8`pdq}<&SftGDRL0nuhY4Zh?u^e=_dda(o=QQm@)s9XRB zzlk!ymvb_80Hm(#znkMPCEq}a|NKzXpp<9nA}zd$7*5dn4FjaehG-T6 zBD5k8a6-hIqSlZ?qOOU#bcK_=3{{8>1`xD;+Dn{sXrGqD+bsu3dBvc`2=k=5S<(Q@ z9?g##QO5cqfrcNHi>Q?l|M>kZ{xwb%qWfjAV(3lmg#LyIkS!Z=yZaZ)HIg$-ZFPpD z^OcrerD`HP0k7=GH>ETT3dOZL5;s}|be7ES<%U`7CL=nO{$VM9_#h zPb4uUU_P543!6t+oFEo@TA5;Wrq^#(nY6)iOIy>6waQ*_Tx%4s?yatDsCm^uH>ymB zW<00%Pbxzr$skaY7yI|vNWAZYFhk&tY<~cjtW|x7QdYN`P;+$VNHFgi#gG+VMR0xy zrnp;9t$is|au+KB`Q%YMH~!njP;5Bd4}I;mj6$J9`Ur6x86hhu&(dR7H_qUv){p zm#5+xR9abo>hlp!2#xP)pzDZ4uOWSfk0xpJp%vH0N!Z3SQ#HQCX!UR&ZLGgxdqVJg zG6+WRFRgfF0~>+^mv8e!uT2$}+>prcg;=gj4X>;!Z?j3(fHci7CWI=An4=eLlSF|( z&hLQ5tI^o8KeosueaXy-g)k&GIyzeRFnD@TyQzxr9U|pV7fR-zBU>&gDxjKhR2N;- zro-T`>px&IjSuz7IPDEWVQykNdU`tr0U(g_5oex<8q@~y)R)_@cbE3!_G}}uYt_DR zCWr<4np)y7b}-Psn{iLcqRiEiKbUo2G#-&!%)!&j(vMni$TYzd6cQ2=6jWm3RCoi; zdt6~EbZtDm+|#1@xHK_0Uj6NA5;#Fx6^^lMe{-AMg~O8q$;?@Ur_X@-V8op5ELiXI z$3u?G94}+#Mp`~Eqv5@x>%x`Mc1ifDmdCf}#^S?jC8JzdVgJhj`(9dA)xlkGjQ-ER zW8h#hk0E%a`x<-&Ru&W%hEO4)Zr=J08X~bE05K5GO8^fD)J&!NEYGGs=cjAkKy>Y9 zef!1Hh>*jFC?22PPBf{Dz@L^>-sJ+HOE;oN%bwd)GNW0jD=1*ZrPQ zbL=vn2~OK4pfPIaj1myvPiTOo+I1}A=1tvvM7=u{V=~odo8?zsvw0zMC0h#w$|w&qtSG@*n4$#Rpx;ZOi$*C z+;M5ji-_8@Z=YShfDoXH{VJj=i1Q)r+O4LZW_vjJY=a&0S3g2E7KoY>51)}&Xh+CJ zKzug;#Km5r?1R&`VQ8~3GdVf#bi&Q9pi%=v4#rEB^wpWQEuCi>sx@3yO z#O;93w_Vq*Ncmi36BJ-&`Wm+tRJ322+2T!Tf|mUTtYM>R$(r<7-BC4$Y1xXsV!#ML zd9ifz!&V(|C|wQTSINEbj2+eA0t1@i0u!=HP0fjc^rUr_ya<2!X{0x4t;G^u5~>F1 zLgp@7+}@Nj8em&K(|E5?W$1X_ytK}U?##`3#|0y!Q^*O?bCo1*`ModJudVH_tJ{C@ zn9&udbf5bCo)fN#_9y5=>7S~b*JQEMsITY_$ZQukZeU*c^nXenYUfuj8XFpDy)qsa zcRsE>h)YohfbTtzBk@w=^~AWslVOZNH{+d&i3y0c*BS})T}^RYIT!Vqv-*qd3% z$VT?_*lBz(@ghlpAso-w87mo5?#5tn1hRj--%}_y4IeP#;G)3fb%4xd11>s4RIE2j z#EeuYKFDWX&T?|x+f=`*cp5cIMmf+}iwFdCBFv{vNnZaqbc(kNpC?q#8HW5^8g$A( z;ehMH`Tkdj(a`e4krVaHGnLfCT8#hm_5=}l@Dt-8%1#hwXUHlz0vLj~;MnYNf4K)_ zglzoQk`+*m{F#ooPrRk6+?bF8l4d~w1>*sul-`IW%i_!O^d;cSR@kNH+ckxJF(D?& zBb4tKP~|7~zM9rE_1a1V0$E0upuo5RBPG~VH_8t5-K)-DBCqQm6uuyUlB|{Tf@71c z8KUOZYJ9J@%a=VC8~`vpN-o>6j>S@Y@|TM2{^xFVhGbyoEDKX8Y|PzcvgYNPNP*|g zFAe-?moRbJQ|L9c`J}D6>=>N|B+DOp4}=&%$S=Nzqk%xJk2A>tM2aWGBp+^O4EPyl z7qRuVloN4SaF7vGw_S#Mn!1irv3VAd)ubICLtE2 ztd=3<@T_%VJNgR+B-@oF2qES)oSK>%CUuXl=~~;{+xz)Oz3qO#xUA{EXo#!%a6PhJ z!0UVykkr&tMSwt$7%)6X*Mjz!Mg(4iO8w0!nQw6cn-O$FUdB^ExYW;Ea0hH_ws2i( z2}Eengr|VOq=0QLBm~&3XV|2}al0z1!c;&2XYH~bJ-h_&2uA1P7$OJ4Y=9&yo`9~k z3T=?0xcBfcGoF6YWH1H{HIII+qN=35^@5sK1E23ALfCO$001*CMx~?xz@A)GG&&&= zjKKiv0P>_GNjQ&>Jy`Of8!fl%D&3*jrGNka5;|&;;{K|pWERyV*Y7MGi@RlQo%f`& zbgrxOaMOM}p)x&PYaC!H4mEI8S^PgQz>y+rGN;jX7bbU2y}=*vPJ-Ve&R|;HaJjB# zPny{Z4 zbw{&x4r2NL9Qfa`T;mskZnNJ<0?8yd?NWDM3bSq{r1c+$EkDflobLLy<8}$C>V;Ya znWRMH!hfJ-N3vSIw}{7B?<`${SE^0AaJ)kN5JmObl9RrI0F!;8<^w2?;ZWbhbNEmi zkm0_^W$}8IG&=6*UI3y=MHc&_P2_Oys%P7Ct&{(OI1|$okv=}h0GT4TG{%6aUesr8 ztxzE# zEt6c=*LtGy_lxwp4Tu&ugPzm<`vyPe}jVP7aZ$gzzgcgt*LRlf3G!?E+)iH z)q)Jl0tSgjgWC2;YKrX|{a;w6Z@#XQifEM!~+L|WhFkfQ_JYZZq zujSFrL7T*S(&+x{zsP0n%+1XW4de4bCEKk)Qo?$rx0srMg2lGcklx$d*Ee zIT{XG*S%s1juPIZv;B2{>k&PpAcG*c6t@yXzOtGUJt+uc80AIfvJ9+G{RLeh_dC46El8O7|}7-*&|ux7b}xkT8rZLLdh~ zpv6hU26XUA0R$>3du%zxg#FGu8W_$(QSt~s{flSke|jVJ^zU7}{U?dPjC&&HPAi7) zcfT9cO&l=Lr6Dq6I^R%{kV#e83Mc-m6f%l=P&qvBb;s~l1ZpU%k&D{hin`jExODEW zbx+D5W_^CxF2#I4m-S@K$8}c4qmB`#Q; z`Zi8~f)<4;UuF=<}Jr5zm?;V`76Md#WCBXsx>+FP=t@hyg zUIAU6ywg%ZA(4UbNyYZNK4Fiy$_A5tbhXloSgDeqH^Ne+>U0?pay5n0|B12&)`zlo zr!IGr65rxX3!>gsyH1ocN%<)z2z>#zx69D0-jb~mP;r193nH4ojU^>H84a(7enn^C zDUhLzc*|dp7ORQo4B0(s%jta>H;DYo5Q~c z`M|Lk{bhz}X3Sjnad$4@tA0PcxPMx^(Dmf@g4hxx^UGc0S@+9r;_D6nV5Q*s9d>ndKTVcQbwl`RwrNRW05xOH0G-Y{f z-?%^~Gl9la??QIPPQ9q*9X}P{YicE)-aWjWoO)POD4V9%o6>Upz^53>{{yHFy5QOYX3`<=KTN$q ztfdj+;l*=kknQ*1S_gEeAMuR#84S7XrlgaCT`;vFEIgNUIv9^vwZ7oP5b2EH=iB4j<-o}=A@6E;pq5<5dvLTw;Xal$9M)dt9#-y79WE$z>$)1q>n57R+B5kpPKkvCrIM71 z?}VorKqMA^`;;#>kR11*Z()$Wq+fybw10s_yIC>}jY?g$kmSWRKHDP&(x9@y774|N zI^EoYr?Iw{*@AojnDuQvqq6VCjn>*zVJEw~FUCBgr;U1m`q%@fcbTblzskGZS#H@V z{vc~>_@$fI3Tnd9slA#WWSF+1M<~hzwgH&zKHU^#1hehwIQ*Qn>)HfOI zZ}t$Kfa?vONc)uGyRk|}AGBArkvc>5W{4&F=e?G1<)0@C9#j3r z3*y5Hq2Z8a!iHLUaep4L%zHhR$WeZ9VQixW=2q$-^gD zIIOC&>E&1uE(C%T_Q?-+E>7}$w&E8%rWBE^n4S-?%1L*iomj?$ElndrXLk&dLaZ12+iO7>%3>sA0UYrm``x*D-Z`5MHiBmko!6} z7pa=Kv>l|$O$!nd6DuK&qL!|gPq~bbesNlQER=tt>j=T}oFNq)pXl!D=udpQP$=B+ zRy*3Sx!sclVX;%LN}Zu$J+IJvv2(h!vGHQ=b&VnugfU3Q_5k7z#`MJeP@HtN*2lS` z*pECAR1SJ{XUF9r0OY~dl$MgQ?6I#xC7&quzs0IS|A-u~PwG!^%v9w|CC$FuBC}oo zGHn?u>Q!3LND=pkavq8GvqrE5!D#~CBTd@K`_7X9d%H-d_Cb?b?sN$-73Pq)A8~S7 z&xoP|efX6A5iE9;KkhF1`gDbtFllc0@WNG0j|X<-C?b~Bzj)mKen6~Jjn#)lyDFc% z&@`Bhb;j6&4ft(;%yHhiJx~^|m{#V3liX|nXqJI1!MdD+$qj_i3JZqHQ zn9|W?20_1~@d8dsTxLR46sSBrVnmJEo)RXfr=``G z9wd{vf@uqg%#5||)%1j57`^ubScbT3)&^u5Kz!3<#2?nXJKaz6(5XHfHUA;DfDg5U z-lgijm7qR^*~G&`D1<{u*&n;V5~pBMyIrxg^r24>fy*@9n12f*rWoAM=Fz`~&!}X0 zNifMg7X-(&%fYInu{aCt*i=K31x1GMi=)6;&vo9k2m^YL%|l4_^vJJhnk2*r!;?(N zb$rk1aD@~>!it+x?{ZqGQwy}_>}trGA&v_)nF8b2{+y8S6g-N>*hLnv8zgwb($6D! z+Qk6-MnO7`BvnjL6}}wB&KRig3-H@A_$;d^iMOBIJ#VnsGqImxV}sw#Lr+{rpkt?y z{|!=RmF4C8di(ZWTtmio2afdvABnQI=3U|t!?eTRSuwgM*B5L{Z(4_a`sXPr>G3D% zEsSa2Dl8CsD8ZxJ@q@lKPc2i8L>n_1o&JH)s_nj(ligpR2SolcG8xi=SkM7APB=d2 z-Y21E4LO0?zZ5%u`KmUzH_P&~vC~^3D+I4IZ5O145q~1$fhcOP#S5g0N(HHUg;6lx zsap3wXl(V$;DH?z`S}+Cmt$5p3}*)?Klivqf>UGXqo8JQQ+> zujRF}as}yLM}#THe@g&tBs35s_Nq1y0*}=qmm6aA#ru8l2QG7{eYUgHeo34qgf6DC$Ai56wYxkyehHz6*_@~_8Nn$U>ZHuZF@F5>gvkUMok~jb@AtKD%?Sdd0u0i*Im<_D?PDa&Q#qL6cku=qsfp83bwYkQYm46 zJd}v)=Zc;rURW3hkwvaXJI7<&GqwC?aUtfm+#*6>2Dm(d1K6tFJvlFkLRBe@Zb2nZcoXz+$j$P-cBjqM;k$K1P-lupy!AcYs8kbc6 z{yp*fedjlCJ9VW$`or$}C)#Mzr{T|A^$}~pqLOX8BiAd37q_h&I);G3ZdM!ln_a!ui2VcR$!0h3GNr%Mb z@t5Zb=G#K4GM}jxkT_2e~Z$u2q^({WUmjVuo!9f&6f*7Ap39?p2 z%6FIA>G&u9*7hl$H?R=dqng=6=FE0~RUMqCJ=J(<8>z!o)xUapQTHY$21btfMuI?o zK2wW57P;lz$`BkO~ z3dz^ojoaPd|Ad3%sggQ;S%9ehH8KGAy`rs>DlU`G7*{twt1q}P!%|*2Jm$1u0JZ7% z_MElEpEE}ys;AgYOUY*nyh^ra!qmZ5ig2dv{?$cgF(w>hdQ9qpXc^x1t)1a7bW9(n zPGp0F(BUK;=}RF}RO%?}4ufy;sLkBfQ3N|{iw;Y4JlRRS1ZI+900CqAl9v`?;rPV~ zeTOxS63OeM%0gys=CbtXhi<>DIT>lN^t8H)Yt^gxzzF|EP!DV7Y4yVNa3LT4?$XqO zXmsi?@bbv1HySb}&{GhMy6F?%)z}pgTZ}cm_!(*R@#BWfYFemRjYpB?VWyIje|YI# zLaW{}A63Y4uh}2m4X95n0tF=4c}3b^QYz`~UEM7by>)%-eDUX@CFm%8(dySk8hEwN z%oKsP;?eq$|EEBb)J-8a7Ov;!3+aPnFNVcH-ji(KN!`)Hcied(rTXykBX2v8p?hOL z*MNV?y9Hei*4s;zt>CIfJjj^P4$yULE`6NTzndGSGtb=xW7U99j7pbWAtgh-ebr+KD-@A}83gFepgHlNAPlBq zd$T~E=HJ$|{JuSWNMd#0*`6>omcIcz-p$+1=iQ7 z5b_<-&M#Iq2uetC3XQDpd~3}WPoNc*wEtl6ayM~UVA{c3fhhx(HMCTHs0`P9F$jA9 zxXqx&So^gq422o?OjHeths%UyKeF1xgTq_0Ws{kG*n=HjGycIr{b;Fi*%3+BcstbjgtWccfw_1T~(K z80fMQl&KF6sb9NWDl0o1sOgrH9+2%uTN`r6Sa+5wL~%LVyBFSm);Z1eyLw~Nsaqrq zZ&SA|y1WV1oV@y}=YD=k(-Jh`1UwfrUuMU9tPhYZ*2m>GbB#93JN z82upHVXPMfgw{Ty_aIDXPk4?i@@8GKTUN2&vCF~zvUI;TslNP(cr!{;S9fQ|VNTGt zozQ3Mx#$`(NsGYUk7wbir=<~tPfP3GKE+}ZgxHsT6R9Kg?5b=wDauk^`fAvW#wzhe zo8icShB-~-2`kpFXtoa5km#mPjs*OR813vsY=;rLps)^}Y1#3LHQT!I1_m~mHzw1a zb=!jzZ9h-4!Ofr`fsaM|515t?oo4h%gJv99$k5?#&vkvmNH9s(YPi#j(;+CSf3rqy zrV$Te!Cf?!l>HC-mv~&*uv2mCP(C%wZ{yPTTEtmy>^nt+Fb#X12EP zpBGyMSZnh<`{^~#TC?j!lCc!DaDpZx4+<#6^27=HJ)!P=mur@nKUPs53^HzcDp8nt zR}ud6dv6>d=oMq6hVm~59h=HNj&O*vCko`d&kER5H?!!{P_efpdG7nM>!aVi@L`^R z;a}lkC(XJ{r1jK~R~}Eo28pcEYlRtx0mKYE{nuA{Cc;3)A?#d3M3oH>;@cy`pnJdR z`rKMDJq^jyDPnZV4QZw5eMs&u2oOmHl9TDPXI%9@^ntdD{9eRS~m85X3<&1-z9KWMW?;PN;scH|+(Sy;iQ2uX)6LF88GS5zp_Wfq&;}+qG^FZi^{b(Y9Af z7ORIVT$f|yFBX^yPfVn|F+2?~s6v-y%Nc%Ft<8!1-KH0ObLHrDPpoxlZ##k4H<-i% z|3W;kAr3eN#R6ALIzl&PW|*PBkpa9R2DhLb2H{x*qPkCTEO)PqI^?{mGU7iaC1}2Z z=NQuPr7#P#vU9Q0$>PFA=#`$Or*x38d8T6%LfR(L4>w-jmOP{nmBj;b17B7iB#-0w4{(|WlVVdC6!n|pwbS`;QXxAj?tc+*2$O`5*gf=34!H+aaJ#4^J>eEJ z4=L;SxU{f?-F!=W^l^`npZTa$=HUBr10D;Ml)*5jX~w6-j15XubNzT>ZE4bW*gL36 zXG}SCAPlwgK#O1vOiY4Hbu)ffn2&Kj2yTbj;4=06rOFr#Vf66{Lz5{PR&29(*lh>b z6e^^~WXh9Nn7>0|Ko;4L*xa_%qz%#b?=r(M;Th-yM-igc7opW3Gx|c5Ric#HB?I5n zo^HqRMv#!ts)TXdS{>!NB|Y7~z@WD{vPDj1WmJk zg1VcaX+G^#fljJsT#d>D2&&QyT0k3}P&ym91r_Y-1vQ3Dkqu-GUCf!^KC@i474W`I z{DHA;`!qahHja(mko+uky$X*dOac>+WzQGqd#ytTc3|ZDE}SOZY?WBrI(mcmF+SGQ zqLX*SnI~Jd68`(T|GwC@OHOVYo9us|&HwjL{}?(}g>3LU-jwn}M*^uc60;3dd2Ubc z9RzVz&0SlW-YTkS?}+f*gtO0PY2CI6bF6W-GG5lHr23eGZ(${KRf|YA@p9*3vhIhp zN>f!B1Dq8qYT_Ap=5XbqwR;aPr9UvbO$Btntd!Zw!pPCCc1K*zs7bI1VWKl0sZw4=%vqa+*Sa&TwEyNiUgnM27T7!cbiBvBk9 zCR9iXEr_gsOH!j(@5E^Otqc7%!iH!+fN5mz@T2S-smOZ}>DKm$^*yRKPqP*XN^cAu zq{e~5mKe~$AB|8J`oP80>;b&c`0skvXec=y2O_Jz6Tc-kIUBPa zk{9}P`Dx#v3QN{yiwq9LV|3y^*G_GdGj`Laa0fGBp-}rF91`%UrNnam$VH0iUR*U#aLgq1NSt_-kt?^sn*3@)D5 zXjG9H`wE*pu&P#@3Gd!N=#Kt$<7!lf_pdT zUFsX;d}%fk&fAXb_D84yoyyl&Hgh^dUi%O{MY(_9xvX_$R66FVM}TDUKfluej7{Jf zbL;9M4ZXMi7Jwr@q$tTB@7umBEG$fii{I?_TV7tNw~IPFGJ*96SRi!KBw^CXWZU4*hp+KQx<4~kGy2q| z$3>wrvt?p375nH?u~Rg{lMRIfJHVY@_k8ndI_Z@lr#;N$gD$XQU=s72j76m%$l+3m!J}vqwoE7Jdrsnz4m3S~r zY+;&8%jm<0Pe?K!h^a8$=#38&4vP}O@ZZ1?hZMn%0cSdE#qLV|o^D7N$#u3{8xp-Z+s2=}SsV zes02*eHiz`A~z?8iuZGq!h#!kxRAWGq{fFv_m>D{dRRC<6N?LlIx+^;RYB(y*3Ktu z0M9^y#>cOe{id7in3q^Kwl?)bLLe&{7@EM;u&DmzZF@B0H=mMEyzZ9=(X;TcryRsR)3~uPWAA+0l?cWVkcSxFJPMWMp+s-Tiuoi<->`igZgIQYJ|KWK&!7}#Z~ z{=c2FVUJ7;o?((-U2W1C&gzYG58Rt=kTysG+Svxf%PGr7H{fTALZgq|qXS^2HFRxQ zIX9e^(eI{2;V=-elZ4d9 zdQ+1D1c(^pqs=|Jyu#v?f%$#SP}g!eQ=Lh%Vu?>sM`^r9$L~~{UoK}Q-T*6xz;P^% zaZkL{tCzYqe;j5rw3e2aA0EO`7e7w+QYI%C3p%dcJq&aWl$E)Uo#~ijb1Q_Vo4?li z`Qbd8R(tz4Rui1te#O3MiZWn1tc>ECE-}6Q3C4l(g0@>b?GlXt#-2ZR5}+1yK3Yrh zTSRq**VSEJxrz#U!VR$v!^plak3aekT0>_MFfK3OX|((j4Av3yeF=Wd!S`fCE8^D< zoa@y)g3UgEZn$l@d{y|?X{_JlnNb(|ARH6!Ysz!W=edw*hzH44(;MA`C|+Pd==smP zZZ}{jcdP3YEm?DOJu&{K+>v#CY4;~|jRx}cW{1M6j1sUs5m_HynA`g6ib?$N<%(JI}JnpyI7d+sP>{hozY;<`IX?KH7}=n_tGX%6%aSH+P0mw21Ar zOn*?k(4<9QJ^%&L%|{siMk zQ?7Mt5IJc6?%4iCH)AX^VuIyAysF)YJliVs=4r3k&eypF5UWfyt)NYMbq*Cd(2sx> zT<4@NX@HTowS*w01;x_^n_{#;a-E2P)A*WN+RoLSxF%gb5UIVCG-wuP$k(m5F>+Dx zH+n<$hV?&I!IGL(9ufW3A?J68t#Nr?m>=)uK{s0v!)&kj70NNWg4|`W zF<5-Ipcl#(c*K8MUk*f5+H1Y+E7KEav$4O1f9=lfw+2M8eM$J_ZqaO8{-qq~gS0=9 zvnf|CZyu(jfd#U(&0y+;i}xAoV8+SoyHU3m2s*tgwY^I(`u6s>Z4t2dbe25y>-eMG zUGwjXAhSPlXn6Xl=jt5DESuvoY8`eh%mqYTMz@^iF5KRu@1)io%s(w)vn0oIA9U4!4o*~B_D?;zb{gwT zmfxF*YIPm;5LpSzP&V{J}gz>NAmzWmE7Bgbf^ z^)lgV5ei10;-e9$pPP<}4I>|DP0L4Lo!Q^uMIA@%1Ega{7&anXgLloKQUeJ=?3g6| zbi2aH!pA?d=4k%PitVHw^ts$hvg1Ik;@m3(qSr4&DKzR)_n7#TB{_UmoWg>-S+54t zhG|Ycd5&<9xsG+;Xo41yL=H-FCFJ|nvDvUmANoqb zrnUZgowB5aC3@bc3O!1O8De@Ry(m5l@(u;3yu2?uT=aktjY2$xBpC6;4o8AXFp)Kf zsc0JkMjyGWjinGgA*0cS!vM%&YX=yLe9=e_=?mq*9W!()p^sQ^@d7v_H_DJBIdQlP zi@P-fzF|Apdu0(-76K}oBh5=o=$Z@X#{Pa-xp`JzUN=vWlt0nw46(GABU3pnN z`7kykYxlnFeM90=I-B?NST6Qr`+p9#zrC}l%Rhg8=&f?2d3xE;(K+3)Gt<~`HV6)n zq6T}5&men4@+-e!dA^74=Rmenf-G&T&Tj+D=s_jTZP}1t)39xubUO3NSG9(Qrl_gk z0pxyB%jT1H*Or?O_ZWn^Sn>noe;qd3C7KI2)*ap^Ubadt7FA{D=eu_ONda=2onlhv zm7W-N^p85@{vEn^8>-3CF_&6gt|$8)vw5)R7a_HllOWn;sImU`({bN|+F}N}U?lGI zDZRYW`ZdnISKI#l1x}_-zq+4vF_0_;qvRl!SFc9Vhui|HZl8`f8b?RWtB%4=`x9Q6 z|MP5dbFmja94`u?7Zn12ec;b6a5j>Zw6~X$tnkh-YNNHVeRk0Gy`RhN;|&O@@6g>_ z_NTwNX1KAs1^ZmM_jw~AEyae|_t!3u;ze{+R8|`Mi>%MKgonF7fBu|2Qn5I z60d6iwqdt1e+G#UDOy8+)44K!BfK&(4v?((i8xuq_PjsfzpU0ERQHO37-3RLG&K}{ zDCl=MG%m_WAqA1Wf%MtQp@#yv5WN8w^d}-e?=_RE1TPucnF-lg!ATnn0#`5^3|ap{ zO%jYgw20{fDd_mOzab=H6`Ps22Y0YV!f}Fp)~%H7S{G=;h(zBEh{o*@UJK@k>f=Kw zdCWhrd~8$H-yu8k(xN@XkQj_axDtaAZcSH9=jqzLQi+-V3HYs}pwg(W#8;P_U&e$K zA3_*kFwu--@8&<(i+YWXST8=MZhYM=}_?HG~1Obp-2<%vYaKl35gqGkT;i2-HN zLdAS8DuhhF;Ychxrs30gBTR@9@f>Oj$AmD*Ar`>uE%t+Wcp!<_BnY!9(s=cmu_-CS zCY2cEa^F)dsHr0F2LarL`3kRj2bVxT64j!khQxxfS2}IydULJnU}52GGSUzG;bpW8 z-_OcgAc+s*VT??Q7zFFc$ApkjAZapJ*l*tBHpO5QB61WwIL=44*gOpJDaXr}%@COb z`vf!O$k4!K!GZh}>+q^g$IJQI&1{(n1K3lD43KirRDu$#X1}kx+we6GbN)U|pRA0P zecAJ_A)-_MdUnJa{uIL#cajq=!j4a7_L(uLMPFZ$FHb=(MS|3eF1a3+YutELpv`hy z?XqMX&~OLlOm3TjmXih+uRp59jwty+e-sU9` z|1N4-cYlLuj4nI>F*i3r^=y9H1+nh6!qX!ok&y|JNlA0l({&&&!JyUO_yNVI*cYFN zv)y05Ojutx4yICn8$0m#FXJU4J_46KTF~}{t8&`L@w&A2i}-@J9FupmzP)naY`qJe zzo}vb%HF}@^16Y5yGnne)Zy}Yli&Ty@qw@x2__Srzk`E+rNsdP9LyPvh;| z8u!k3O1-^6KTRWG#RyT{&v0LUH`mqI{{Iak0?Yvr0a3r%LQWd zPaGPjemNb|&0RW=H0Q2leoPu08<`;u_JgMY$LqjrP>rS`a|#QAgV(-2HNE?0jvw|bH%afNXc0VH{x5b9DH!s2tY>>Fjia8!) zolbz@6!A!<9af}!+zuwYZ%3?>0i1JqVvz*FFnTLS1R6-AqhO;6rW1aJ!3M*!si_s- zj1BQeq}0`6X0XWl{%xF#Un%qn=~Iq;fWQTDB4>a%r__6P>Fys|$`?FO% zI5_PW@m>c1&o3qj8xLe`Cu{0}lwmBr`zfwtBJuS5L)a0X!$?cA>Z`jqaHt5@^=l zu2uo}De1?Lfg(P2LHxCt)1MUKC=PTWz=~P?h)l?pSoFe*r#0`}&fVz3i3bvhlJl9y z;3OWg=$NIAgzfD|_Kl4>IXU}2q?n#TVOe$gj|)EVAgEVp4UR&4T7VAZxe^V}RlWq{ zUciHhf3%R$l*(gg!|K-aGlGv46cnhXjX4HkX4K2Hfthl^8=109e-J{$&&{6>_Djo& z1|Vao*E|0P*e)iMlmhwbTIFBH4n2B83BX7^A|f2GIp$3ahKDEbih#01l`t9JrJH+q zsdGUWrr}o}PwX&7FE&zCX9x9Xdo4Q1+c(_1;+GoB`|^sNX>~e$U+${4mcT%KQMmci zS*KoTpu?P-L-%Nb!cXq{4=2l&?%Sq_2{ohrXzx2MXxt2zQq~oX35^K~9`&#vZ}QjS zy1}1u6}O(3IJD-nHa4u%3+?55d9_&7asnRFLk=&q2lGO>1iV`rHD7tKEDSg^qq%c3 zc_U+fJbX-Utt=w>b*ee!^S|g9R`O3#>Y{$l?1QisTmo!}jLDZ*6Ls!(^TcgkfmtT# zb%X198NsaUt~W883O&%)T~i182!B4YP*G5z5k9^mWtPE?z`Un)4^tuEfbQExC%oT8G{DJht@hEx7Ox^KV z*duRN%XTrCzv1TRoi}eLjEsCG>=a!QNRVw!5r7P$8gmx?`P=VSas+ZcUTyFo)-$vXscmtB|}NlJJ7s)`0r=AD#y-1rbkONGD<6I=Xkp| zz)U9*n@PoCpPsQ3eC&U4cv#fki3wq24zE-5O9x;;x)XQ6mqY)`t&5(hx|y|6AmA8Q zsU@=XPD?Adml%jOfAO16y+DQVXWv!IRT4*)r#lpdp(Yd+^pBDjYKg^w#Qd?KA3L~? zwZg4Hx|9xH9xvet>pVYfMP8$zn9S;<<2E4UdFkDi7=1hZv%j{Ok-xvy%T2jFVt2dY zr?7>_B$!!XL!3L$eL^Zu?)2ZBS4>&7iqSDmn||Huby$T#p4OE@?+76?ttZW|F-hPU z5*E}dHdY<+6)OX@84Dv@xS2J!ZB6||& zdp(NW*e|a>wa$0YH{N;^znF+&tuW6bHJz@qrkp#ww(W~)@{#sr9fUk>2qB{Hn4WAi zS(g}1Y`-RguJ=_u_AamI{`BoK5Uz

f?DzR+(yIu9xUmJ$hF4iW3QEH$A-gQaQ8V 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

IE7zV{Wc?S5qBP*%T!iV97*)pNeV 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 Hu0mjfBVu0= literal 0 HcmV?d00001 diff --git a/docs/sources/installation/images/windows-installer.png b/docs/sources/installation/images/windows-installer.png new file mode 100644 index 0000000000000000000000000000000000000000..6305dcc6d9f5c7173ddc16a2a0880e3ff6149bcf GIT binary patch literal 81366 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ȗdvo))s<@ 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 **Note:** > Docker is supported on Mac OS X 10.6 "Snow Leopard" or newer. -Docker has two key components: the Docker daemon and the `docker` binary -which acts as a client. The client passes instructions to the daemon -which builds, runs and manages your Docker containers. As Docker uses -some Linux-specific kernel features you can't use it directly on OS X. -Instead we run the Docker daemon inside a lightweight virtual machine on your local -OS X host. We can then use a native client `docker` binary to communicate -with the Docker daemon inside our virtual machine. To make this process -easier we've designed a helper application called -[boot2docker](https://github.com/boot2docker/boot2docker) to install -that virtual machine and run our Docker daemon. +The Docker Engine uses Linux-specific kernel features, so we run it on OS X +using a lightweight virtual machine. You can use the OS X Docker client to +control the virtualized engine to build, run and manage Docker containers. -[boot2docker](https://github.com/boot2docker/boot2docker) uses -VirtualBox to create the virtual machine so we'll need to install that -first. +To make this process easier we designed a helper application called +[boot2docker](https://github.com/boot2docker/boot2docker) to install the +virtual machine and run the Docker daemon. -## Installing VirtualBox +## Installation -Docker on OS X needs VirtualBox to run. To begin with, head over to -[VirtualBox Download Page](https://www.virtualbox.org/wiki/Downloads) -and get the tool for `OS X hosts x86/amd64`. +1. Download the latest release of the [Docker for OSX Installer]( + https://github.com/boot2docker/osx-installer/releases) +2. Run the installer, which will install VirtualBox and the Boot2Docker management + tool. + ![](/installation/images/osx-installer.png) +3. Open a terminal and run: -Once the download is complete, open the disk image, run `VirtualBox.pkg` -and install VirtualBox. +``` + boot2docker init + boot2docker start + export DOCKER_HOST=tcp://localhost:4243 +``` -> **Note**: -> Do not simply copy the package without running the -> installer. +`boot2docker init` will ask you to enter an ssh key passphrase - the simplest +(but least secure) is to just hit [Enter]. This passphrase is used by the +`boot2docker ssh` command. -## Installing boot2docker manually -### Downloading the boot2docker script +Once you have an initialized virtual machine, you can `boot2docker stop` and +`boot2docker start` it. -[boot2docker](https://github.com/boot2docker/boot2docker) provides a -handy script to manage the VM running the Docker daemon. It also takes -care of the installation of that VM. +## Upgrading -Open up a new terminal window and run the following commands to get -boot2docker: +To upgrade: - # Enter the installation directory - $ mkdir -p ~/bin - $ cd ~/bin +1. Download the latest release of the [Docker for OSX Installer]( + https://github.com/boot2docker/osx-installer/releases) +2. Run the installer, which will update VirtualBox and the Boot2Docker management + tool. +3. To upgrade your existing virtual machine, open a terminal and run: - # Get the file - $ curl https://raw.githubusercontent.com/boot2docker/boot2docker/master/boot2docker > boot2docker +``` + boot2docker stop + boot2docker download + boot2docker start +``` - # Mark it executable - $ chmod +x boot2docker -### Installing the Docker OS X Client +## Running Docker -The Docker daemon is accessed using the `docker` binary. +From your terminal, you can try the “hello world” example. Run: -Run the following commands to get it downloaded and set up: + $ docker run ubuntu echo hello world - # Get the docker binary - $ DIR=$(mktemp -d ${TMPDIR:-/tmp}/dockerdl.XXXXXXX) && \ - curl -f -o $DIR/ld.tgz https://get.docker.io/builds/Darwin/x86_64/docker-latest.tgz && \ - gunzip $DIR/ld.tgz && \ - tar xvf $DIR/ld.tar -C $DIR/ && \ - cp $DIR/usr/local/bin/docker ./docker +This will download the ubuntu image and print hello world. - # Copy the executable file - $ sudo mkdir -p /usr/local/bin - $ sudo cp docker /usr/local/bin/ +# Further details -### Configure the Docker OS X Client +The Boot2Docker management tool provides some commands: -The Docker client, `docker`, uses an environment variable `DOCKER_HOST` -to specify the location of the Docker daemon to connect to. Specify your -local boot2docker virtual machine as the value of that variable. +``` +$ ./boot2docker +Usage: ./boot2docker [] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|delete|download|version} [] +``` - $ export DOCKER_HOST=tcp://127.0.0.1:4243 - -## Installing boot2docker with Homebrew - -If you are using Homebrew on your machine, simply run the following -command to install `boot2docker`: - - $ brew install boot2docker - -Run the following command to install the Docker client: - - $ brew install docker - -And that's it! Let's check out how to use it. - -# How To Use Docker On Mac OS X - -## Running the Docker daemon via boot2docker - -Firstly we need to initialize our boot2docker virtual machine. Run the -`boot2docker` command. - - $ boot2docker init - -This will setup our initial virtual machine. - -Next we need to start the Docker daemon. - - $ boot2docker up - -There are a variety of others commands available using the `boot2docker` -script. You can see these like so: - - $ boot2docker - Usage ./boot2docker {init|start|up|pause|stop|restart|status|info|delete|ssh|download} - -## The Docker client - -Once the virtual machine with the Docker daemon is up, you can use the `docker` -binary just like any other application. - - $ docker version - Client version: 0.10.0 - Client API version: 1.10 - Server version: 0.10.0 - Server API version: 1.10 - Last stable version: 0.10.0 - -## Using Docker port forwarding with boot2docker - -In order to forward network ports from Docker with boot2docker we need to -manually forward the port range Docker uses inside VirtualBox. To do -this we take the port range that Docker uses by default with the `-P` -option, ports 49000-49900, and run the following command. - -> **Note:** -> The boot2docker virtual machine must be powered off for this -> to work. - - for i in {49000..49900}; do - VBoxManage modifyvm "boot2docker-vm" --natpf1 "tcp-port$i,tcp,,$i,,$i"; - VBoxManage modifyvm "boot2docker-vm" --natpf1 "udp-port$i,udp,,$i,,$i"; - done - -## Connecting to the VM via SSH - -If you feel the need to connect to the VM, you can simply run: - - $ boot2docker ssh - - # User: docker - # Pwd: tcuser - -If SSH complains about keys then run: - - $ ssh-keygen -R '[localhost]:2022' - -## Upgrading to a newer release of boot2docker - -To upgrade an initialized boot2docker virtual machine, you can use the -following 3 commands. Your virtual machine's disk will not be changed, -so you won't lose your images and containers: - - $ boot2docker stop - $ boot2docker download - $ boot2docker start - -# Learn More - -## boot2docker - -See the GitHub page for -[boot2docker](https://github.com/boot2docker/boot2docker). - -# Next steps - -You can now continue with the [*Hello -World*](/examples/hello_world/#hello-world) example. +For further information or to report issues, please see the [Boot2Docker site](http://boot2docker.io). diff --git a/docs/sources/installation/windows.md b/docs/sources/installation/windows.md index 189be0074..2980cad14 100644 --- a/docs/sources/installation/windows.md +++ b/docs/sources/installation/windows.md @@ -1,56 +1,57 @@ page_title: Installation on Windows -page_description: Please note this project is currently under heavy development. It should not be used in production. +page_description: Docker installation on Microsoft Windows page_keywords: Docker, Docker documentation, Windows, requirements, virtualbox, boot2docker # Windows -Docker can run on Windows using a virtualization platform like -VirtualBox. A Linux distribution is run inside a virtual machine and -that's where Docker will run. - -## Installation - > **Note**: > Docker is still under heavy development! We don't recommend using it in > production yet, but we're getting closer with each release. Please see > our blog post, [Getting to Docker 1.0]( > http://blog.docker.io/2013/08/getting-to-docker-1-0/) -1. Install virtualbox from [https://www.virtualbox.org]( - https://www.virtualbox.org) - or follow this [tutorial]( - http://www.slideshare.net/julienbarbier42/install-virtualbox-on-windows-7). -2. Download the latest boot2docker.iso from - [https://github.com/boot2docker/boot2docker/releases]( - https://github.com/boot2docker/boot2docker/releases). -3. Start VirtualBox. -4. Create a new Virtual machine with the following settings: +Docker Engine runs on Windows using a lightweight virtual machine. There +is no native Windows Docker client yet, so everything is done inside the virtual +machine. - - Name: boot2docker - - Type: Linux - - Version: Linux 2.6 (64 bit) - - Memory size: 1024 MB - - Hard drive: Do not add a virtual hard drive +To make this process easier we designed a helper application called +[boot2docker](https://github.com/boot2docker/boot2docker) to install the +virtual machine and run the Docker daemon. -5. Open the settings of the virtual machine: - 5.1. go to Storage - 5.2. click the empty slot below Controller: IDE - 5.3. click the disc icon on the right of IDE Secondary Master - 5.4. click Choose a virtual CD/DVD disk file +## Installation -6. Browse to the path where you`ve saved the boot2docker.iso, select - the boot2docker.iso and click open. +1. Download the latest release of the [Docker for Windows Installer](https://github.com/boot2docker/windows-installer/releases) +2. Run the installer, which will install VirtualBox, MSYS-git, the boot2docker Linux ISO and the + Boot2Docker management tool. + ![](/installation/images/windows-installer.png) +3. Run the `Boot2Docker Start` shell script from your Desktop or Program Files > Docker. + The Start script will ask you to enter an ssh key passphrase - the simplest + (but least secure) is to just hit [Enter]. + ![](/installation/images/windows-boot2docker-start.png) -7. Click OK on the Settings dialog to save the changes and close the - window. +The `Boot2Docker Start` script will connect you to a shell session in the virtual +Machine. If needed, it will initialise a new VM and start it. -8. Start the virtual machine by clicking the green start button. +## Upgrading + +To upgrade: + +1. Download the latest release of the [Docker for Windows Installer]( + https://github.com/boot2docker/windows-installer/releases) +2. Run the installer, which will update the Boot2Docker management tool. +3. To upgrade your existing virtual machine, open a terminal and run: + +``` + boot2docker stop + boot2docker download + boot2docker start +``` -9. The boot2docker virtual machine should boot now. ## Running Docker -boot2docker will log you in automatically so you can start using Docker +Boot2Docker will log you in automatically so you can start using Docker right away. Let's try the “hello world” example. Run @@ -59,34 +60,14 @@ Let's try the “hello world” example. Run This will download the small busybox image and print hello world. -## Persistent storage +# Further Details -1. Add a virtual hard drive to the VM created in Installation -2. Start the VM -3. Create an empty partition on the attached virtual hard drive +The Boot2Docker management tool provides some commands: - ```sh - sudo fdisk /dev/sda - n (new partition) - p (primary partition) - 1 (partition 1) - w (write changes to disk) - ``` +``` +$ ./boot2docker +Usage: ./boot2docker [] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|delete|download|version} [] +``` -4. Format the partition using ext4 - ```sh - mkfs.ext4 -L boot2docker-data /dev/sda1 - ``` - -5. Reboot - - ```sh - sudo reboot - ``` - -6. boot2docker should now auto mount the partition and persist data there. (/var/lib/docker linking to /mnt/sda1/var/lib/docker) - - ```sh - ls -l /var/lib - ``` +For further information or to report issues, please see the [Boot2Docker site](http://boot2docker.io) From 1de52caedca11c083e92039e2ecd8671944dfc47 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 28 May 2014 00:17:11 +0000 Subject: [PATCH 274/400] update container's state after we close the waitLock Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- daemon/container.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index 647f4c187..9d396c281 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -468,6 +468,20 @@ func (container *Container) monitor(callback execdriver.StartCallback) error { utils.Errorf("Error running container: %s", err) } + // Cleanup + container.cleanup() + + // Re-create a brand new stdin pipe once the container exited + if container.Config.OpenStdin { + container.stdin, container.stdinPipe = io.Pipe() + } + + if container.daemon != nil && container.daemon.srv != nil { + container.daemon.srv.LogEvent("die", container.ID, container.daemon.repositories.ImageName(container.Image)) + } + + close(container.waitLock) + if container.daemon != nil && container.daemon.srv != nil && container.daemon.srv.IsRunning() { container.State.SetStopped(exitCode) @@ -483,20 +497,6 @@ func (container *Container) monitor(callback execdriver.StartCallback) error { } } - // Cleanup - container.cleanup() - - // Re-create a brand new stdin pipe once the container exited - if container.Config.OpenStdin { - container.stdin, container.stdinPipe = io.Pipe() - } - - if container.daemon != nil && container.daemon.srv != nil { - container.daemon.srv.LogEvent("die", container.ID, container.daemon.repositories.ImageName(container.Image)) - } - - close(container.waitLock) - return err } From 147ad3480da6c9e966b1db0d9d607dc6b1483327 Mon Sep 17 00:00:00 2001 From: SvenDowideit Date: Wed, 28 May 2014 10:56:37 +1000 Subject: [PATCH 275/400] gofmt does not result in the same thing as go fmt -s file.go, which is what travis tests for Docker-DCO-1.1-Signed-off-by: SvenDowideit (github: SvenDowideit) --- CONTRIBUTING.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f59e49d46..cb0d33347 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -77,13 +77,8 @@ well as a clean documentation build. See ``docs/README.md`` for more information on building the docs and how docs get released. Write clean code. Universally formatted code promotes ease of writing, reading, -and maintenance. Always run `go fmt` before committing your changes. Most -editors have plugins that do this automatically, and there's also a git -pre-commit hook: - -``` -curl -o .git/hooks/pre-commit https://raw.githubusercontent.com/edsrzf/gofmt-git-hook/master/fmt-check && chmod +x .git/hooks/pre-commit -``` +and maintenance. Always run `gofmt -s -w file.go` on each changed file before +committing your changes. Most editors have plugins that do this automatically. Pull requests descriptions should be as clear as possible and include a reference to all the issues that they address. From b64535166f4e930121bf9cb624bf2d044c97e472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Petazzoni?= Date: Tue, 27 May 2014 18:57:25 -0700 Subject: [PATCH 276/400] Replace 'private' with 'self-hosted' in a couple of places MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker-DCO-1.1-Signed-off-by: Jérôme Petazzoni (github: jpetazzo) --- docs/sources/reference/commandline/cli.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 2a1be8908..e0c5eb106 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -212,7 +212,7 @@ repository is used as the context. The Git repository is cloned with its submodules (git clone –recursive). A fresh git clone occurs in a temporary directory on your local host, and then this is sent to the Docker daemon as the context. This way, your local user credentials and -vpn's etc can be used to access private repositories +vpn's etc can be used to access private repositories. See also: @@ -647,7 +647,7 @@ Restores both images and tags. -p, --password="" Password -u, --username="" Username -If you want to login to a private registry you can +If you want to login to a self-hosted registry you can specify this by adding the server name. example: @@ -728,8 +728,8 @@ use `docker pull`: Push an image or a repository to the registry -Use `docker push` to share your images on public or -private registries. +Use `docker push` to share your images to the [Docker.io](https://index.docker.io) +registry or to a self-hosted one. ## restart From 69640123826cf73d3d83182cb81e5de4ad0cc3a7 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 27 May 2014 19:03:57 -0700 Subject: [PATCH 277/400] Return devmapper errors with additional text Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/graphdriver/devmapper/devmapper.go | 54 +++++++++++------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/daemon/graphdriver/devmapper/devmapper.go b/daemon/graphdriver/devmapper/devmapper.go index 315f0c0db..d78ae1ef8 100644 --- a/daemon/graphdriver/devmapper/devmapper.go +++ b/daemon/graphdriver/devmapper/devmapper.go @@ -327,21 +327,21 @@ func createPool(poolName string, dataFile, metadataFile *os.File) error { size, err := GetBlockDeviceSize(dataFile) if err != nil { - return fmt.Errorf("Can't get data size") + return fmt.Errorf("Can't get data size %s", err) } params := metadataFile.Name() + " " + dataFile.Name() + " 128 32768 1 skip_block_zeroing" if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil { - return fmt.Errorf("Can't add target") + return fmt.Errorf("Can't add target %s", err) } var cookie uint = 0 if err := task.SetCookie(&cookie, 0); err != nil { - return fmt.Errorf("Can't set cookie") + return fmt.Errorf("Can't set cookie %s", err) } if err := task.Run(); err != nil { - return fmt.Errorf("Error running DeviceCreate (createPool)") + return fmt.Errorf("Error running DeviceCreate (createPool) %s", err) } UdevWait(cookie) @@ -357,16 +357,16 @@ func reloadPool(poolName string, dataFile, metadataFile *os.File) error { size, err := GetBlockDeviceSize(dataFile) if err != nil { - return fmt.Errorf("Can't get data size") + return fmt.Errorf("Can't get data size %s", err) } params := metadataFile.Name() + " " + dataFile.Name() + " 128 32768" if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil { - return fmt.Errorf("Can't add target") + return fmt.Errorf("Can't add target %s", err) } if err := task.Run(); err != nil { - return fmt.Errorf("Error running DeviceCreate") + return fmt.Errorf("Error running DeviceCreate %s", err) } return nil @@ -426,15 +426,15 @@ func setTransactionId(poolName string, oldId uint64, newId uint64) error { } if err := task.SetSector(0); err != nil { - return fmt.Errorf("Can't set sector") + return fmt.Errorf("Can't set sector %s", err) } if err := task.SetMessage(fmt.Sprintf("set_transaction_id %d %d", oldId, newId)); err != nil { - return fmt.Errorf("Can't set message") + return fmt.Errorf("Can't set message %s", err) } if err := task.Run(); err != nil { - return fmt.Errorf("Error running setTransactionId") + return fmt.Errorf("Error running setTransactionId %s", err) } return nil } @@ -445,7 +445,7 @@ func suspendDevice(name string) error { return err } if err := task.Run(); err != nil { - return fmt.Errorf("Error running DeviceSuspend: %s", err) + return fmt.Errorf("Error running DeviceSuspend %s", err) } return nil } @@ -458,11 +458,11 @@ func resumeDevice(name string) error { var cookie uint = 0 if err := task.SetCookie(&cookie, 0); err != nil { - return fmt.Errorf("Can't set cookie") + return fmt.Errorf("Can't set cookie %s", err) } if err := task.Run(); err != nil { - return fmt.Errorf("Error running DeviceResume") + return fmt.Errorf("Error running DeviceResume %s", err) } UdevWait(cookie) @@ -480,11 +480,11 @@ func createDevice(poolName string, deviceId *int) error { } if err := task.SetSector(0); err != nil { - return fmt.Errorf("Can't set sector") + return fmt.Errorf("Can't set sector %s", err) } if err := task.SetMessage(fmt.Sprintf("create_thin %d", *deviceId)); err != nil { - return fmt.Errorf("Can't set message") + return fmt.Errorf("Can't set message %s", err) } dmSawExist = false @@ -494,7 +494,7 @@ func createDevice(poolName string, deviceId *int) error { *deviceId++ continue } - return fmt.Errorf("Error running createDevice") + return fmt.Errorf("Error running createDevice %s", err) } break } @@ -508,15 +508,15 @@ func deleteDevice(poolName string, deviceId int) error { } if err := task.SetSector(0); err != nil { - return fmt.Errorf("Can't set sector") + return fmt.Errorf("Can't set sector %s", err) } if err := task.SetMessage(fmt.Sprintf("delete %d", deviceId)); err != nil { - return fmt.Errorf("Can't set message") + return fmt.Errorf("Can't set message %s", err) } if err := task.Run(); err != nil { - return fmt.Errorf("Error running deleteDevice") + return fmt.Errorf("Error running deleteDevice %s", err) } return nil } @@ -533,7 +533,7 @@ func removeDevice(name string) error { if dmSawBusy { return ErrBusy } - return fmt.Errorf("Error running removeDevice") + return fmt.Errorf("Error running removeDevice %s", err) } return nil } @@ -546,19 +546,19 @@ func activateDevice(poolName string, name string, deviceId int, size uint64) err params := fmt.Sprintf("%s %d", poolName, deviceId) if err := task.AddTarget(0, size/512, "thin", params); err != nil { - return fmt.Errorf("Can't add target") + return fmt.Errorf("Can't add target %s", err) } if err := task.SetAddNode(AddNodeOnCreate); err != nil { - return fmt.Errorf("Can't add node") + return fmt.Errorf("Can't add node %s", err) } var cookie uint = 0 if err := task.SetCookie(&cookie, 0); err != nil { - return fmt.Errorf("Can't set cookie") + return fmt.Errorf("Can't set cookie %s", err) } if err := task.Run(); err != nil { - return fmt.Errorf("Error running DeviceCreate (activateDevice)") + return fmt.Errorf("Error running DeviceCreate (activateDevice) %s", err) } UdevWait(cookie) @@ -589,14 +589,14 @@ func createSnapDevice(poolName string, deviceId *int, baseName string, baseDevic if doSuspend { resumeDevice(baseName) } - return fmt.Errorf("Can't set sector") + return fmt.Errorf("Can't set sector %s", err) } if err := task.SetMessage(fmt.Sprintf("create_snap %d %d", *deviceId, baseDeviceId)); err != nil { if doSuspend { resumeDevice(baseName) } - return fmt.Errorf("Can't set message") + return fmt.Errorf("Can't set message %s", err) } dmSawExist = false @@ -610,7 +610,7 @@ func createSnapDevice(poolName string, deviceId *int, baseName string, baseDevic if doSuspend { resumeDevice(baseName) } - return fmt.Errorf("Error running DeviceCreate (createSnapDevice)") + return fmt.Errorf("Error running DeviceCreate (createSnapDevice) %s", err) } break From 02f4ae6c56474b1f4e747916812b38134d503349 Mon Sep 17 00:00:00 2001 From: Derek Date: Thu, 22 May 2014 23:58:56 -0700 Subject: [PATCH 278/400] Use Timeout Conn wrapper to set read deadline for downloading layer Docker-DCO-1.1-Signed-off-by: Derek (github: crquan) --- registry/registry.go | 11 +++++++++++ server/server.go | 44 +++++++++++++++++++++++++++++++++----------- utils/timeoutconn.go | 26 ++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 utils/timeoutconn.go diff --git a/registry/registry.go b/registry/registry.go index 2e3e7e03a..3d0a3ed2d 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -726,7 +726,17 @@ type Registry struct { } func NewRegistry(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, indexEndpoint string) (r *Registry, err error) { + httpDial := func(proto string, addr string) (net.Conn, error) { + conn, err := net.Dial(proto, addr) + if err != nil { + return nil, err + } + conn = utils.NewTimeoutConn(conn, time.Duration(1)*time.Minute) + return conn, nil + } + httpTransport := &http.Transport{ + Dial: httpDial, DisableKeepAlives: true, Proxy: http.ProxyFromEnvironment, } @@ -738,6 +748,7 @@ func NewRegistry(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, inde }, indexEndpoint: indexEndpoint, } + r.client.Jar, err = cookiejar.New(nil) if err != nil { return nil, err diff --git a/server/server.go b/server/server.go index 6d398807b..0deda0d95 100644 --- a/server/server.go +++ b/server/server.go @@ -27,6 +27,7 @@ import ( "io" "io/ioutil" "log" + "net" "net/http" "net/url" "os" @@ -1134,17 +1135,38 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin } } - // Get the layer - out.Write(sf.FormatProgress(utils.TruncateID(id), "Pulling fs layer", nil)) - layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token) - if err != nil { - out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil)) - return err - } - defer layer.Close() - if err := srv.daemon.Graph().Register(imgJSON, utils.ProgressReader(layer, imgSize, out, sf, false, utils.TruncateID(id), "Downloading"), img); err != nil { - out.Write(sf.FormatProgress(utils.TruncateID(id), "Error downloading dependent layers", nil)) - return err + for j := 1; j <= retries; j++ { + // Get the layer + status := "Pulling fs layer" + if j > 1 { + status = fmt.Sprintf("Pulling fs layer [retries: %d]", j) + } + out.Write(sf.FormatProgress(utils.TruncateID(id), status, nil)) + layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token) + if uerr, ok := err.(*url.Error); ok { + err = uerr.Err + } + if terr, ok := err.(net.Error); ok && terr.Timeout() && j < retries { + time.Sleep(time.Duration(j) * 500 * time.Millisecond) + continue + } else if err != nil { + out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil)) + return err + } + defer layer.Close() + + err = srv.daemon.Graph().Register(imgJSON, + utils.ProgressReader(layer, imgSize, out, sf, false, utils.TruncateID(id), "Downloading"), + img) + if terr, ok := err.(net.Error); ok && terr.Timeout() && j < retries { + time.Sleep(time.Duration(j) * 500 * time.Millisecond) + continue + } else if err != nil { + out.Write(sf.FormatProgress(utils.TruncateID(id), "Error downloading dependent layers", nil)) + return err + } else { + break + } } } out.Write(sf.FormatProgress(utils.TruncateID(id), "Download complete", nil)) diff --git a/utils/timeoutconn.go b/utils/timeoutconn.go new file mode 100644 index 000000000..a3231c7ee --- /dev/null +++ b/utils/timeoutconn.go @@ -0,0 +1,26 @@ +package utils + +import ( + "net" + "time" +) + +func NewTimeoutConn(conn net.Conn, timeout time.Duration) net.Conn { + return &TimeoutConn{conn, timeout} +} + +// A net.Conn that sets a deadline for every Read or Write operation +type TimeoutConn struct { + net.Conn + timeout time.Duration +} + +func (c *TimeoutConn) Read(b []byte) (int, error) { + if c.timeout > 0 { + err := c.Conn.SetReadDeadline(time.Now().Add(c.timeout)) + if err != nil { + return 0, err + } + } + return c.Conn.Read(b) +} From 31f0a61a3dfabe363d08d4ff55e8a1efb29d84eb Mon Sep 17 00:00:00 2001 From: Alexandr Morozov Date: Wed, 28 May 2014 13:55:10 +0400 Subject: [PATCH 279/400] Remove collections package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It doesn't needed anymore аfter port and ip allocators refactoring Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- pkg/collections/orderedintset.go | 95 --------------------------- pkg/collections/orderedintset_test.go | 71 -------------------- 2 files changed, 166 deletions(-) delete mode 100644 pkg/collections/orderedintset.go delete mode 100644 pkg/collections/orderedintset_test.go diff --git a/pkg/collections/orderedintset.go b/pkg/collections/orderedintset.go deleted file mode 100644 index 7442f2e93..000000000 --- a/pkg/collections/orderedintset.go +++ /dev/null @@ -1,95 +0,0 @@ -package collections - -import ( - "sort" - "sync" -) - -// OrderedIntSet is a thread-safe sorted set and a stack. -type OrderedIntSet struct { - sync.Mutex - set []int -} - -// NewOrderedSet returns an initialized OrderedSet -func NewOrderedIntSet() *OrderedIntSet { - return &OrderedIntSet{} -} - -// Push takes an int and adds it to the set. If the elem aready exists, it has no effect. -func (s *OrderedIntSet) Push(elem int) { - s.Lock() - if len(s.set) == 0 { - s.set = append(s.set, elem) - s.Unlock() - return - } - - // Make sure the list is always sorted - i := sort.SearchInts(s.set, elem) - if i < len(s.set) && s.set[i] == elem { - s.Unlock() - return - } - s.set = append(s.set[:i], append([]int{elem}, s.set[i:]...)...) - s.Unlock() -} - -// Pop is an alias to PopFront() -func (s *OrderedIntSet) Pop() int { - return s.PopFront() -} - -// Pop returns the first element from the list and removes it. -// If the list is empty, it returns 0 -func (s *OrderedIntSet) PopFront() int { - s.Lock() - if len(s.set) == 0 { - s.Unlock() - return 0 - } - ret := s.set[0] - s.set = s.set[1:] - s.Unlock() - return ret -} - -// PullBack retrieve the last element of the list. -// The element is not removed. -// If the list is empty, an empty element is returned. -func (s *OrderedIntSet) PullBack() int { - s.Lock() - defer s.Unlock() - if len(s.set) == 0 { - return 0 - } - return s.set[len(s.set)-1] -} - -// Exists checks if the given element present in the list. -func (s *OrderedIntSet) Exists(elem int) bool { - s.Lock() - if len(s.set) == 0 { - s.Unlock() - return false - } - i := sort.SearchInts(s.set, elem) - res := i < len(s.set) && s.set[i] == elem - s.Unlock() - return res -} - -// Remove removes an element from the list. -// If the element is not found, it has no effect. -func (s *OrderedIntSet) Remove(elem int) { - s.Lock() - if len(s.set) == 0 { - s.Unlock() - return - } - i := sort.SearchInts(s.set, elem) - if i < len(s.set) && s.set[i] == elem { - s.set = append(s.set[:i], s.set[i+1:]...) - } - s.Unlock() -} diff --git a/pkg/collections/orderedintset_test.go b/pkg/collections/orderedintset_test.go deleted file mode 100644 index 0ac4ca545..000000000 --- a/pkg/collections/orderedintset_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package collections - -import ( - "math/rand" - "testing" -) - -func BenchmarkPush(b *testing.B) { - var testSet []int - for i := 0; i < 1000; i++ { - testSet = append(testSet, rand.Int()) - } - s := NewOrderedIntSet() - b.ResetTimer() - for i := 0; i < b.N; i++ { - for _, elem := range testSet { - s.Push(elem) - } - } -} - -func BenchmarkPop(b *testing.B) { - var testSet []int - for i := 0; i < 1000; i++ { - testSet = append(testSet, rand.Int()) - } - s := NewOrderedIntSet() - for _, elem := range testSet { - s.Push(elem) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - for j := 0; j < 1000; j++ { - s.Pop() - } - } -} - -func BenchmarkExist(b *testing.B) { - var testSet []int - for i := 0; i < 1000; i++ { - testSet = append(testSet, rand.Intn(2000)) - } - s := NewOrderedIntSet() - for _, elem := range testSet { - s.Push(elem) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - for j := 0; j < 1000; j++ { - s.Exists(j) - } - } -} - -func BenchmarkRemove(b *testing.B) { - var testSet []int - for i := 0; i < 1000; i++ { - testSet = append(testSet, rand.Intn(2000)) - } - s := NewOrderedIntSet() - for _, elem := range testSet { - s.Push(elem) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - for j := 0; j < 1000; j++ { - s.Remove(j) - } - } -} From fd58524f81031eec112b5e9bd52bfaa186fc9c20 Mon Sep 17 00:00:00 2001 From: Bernerd Schaefer Date: Wed, 28 May 2014 16:40:36 +0200 Subject: [PATCH 280/400] Add system.SetKeepCaps and system.ClearKeepCaps Docker-DCO-1.1-Signed-off-by: Bernerd Schaefer (github: bernerdschaefer) --- pkg/system/calls_linux.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pkg/system/calls_linux.go b/pkg/system/calls_linux.go index faead0114..6986051e1 100644 --- a/pkg/system/calls_linux.go +++ b/pkg/system/calls_linux.go @@ -135,6 +135,22 @@ func GetParentDeathSignal() (int, error) { return sig, nil } +func SetKeepCaps() error { + if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_KEEPCAPS, 1, 0); err != 0 { + return err + } + + return nil +} + +func ClearKeepCaps() error { + if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_KEEPCAPS, 0, 0); err != 0 { + return err + } + + return nil +} + func Setctty() error { if _, _, err := syscall.RawSyscall(syscall.SYS_IOCTL, 0, uintptr(syscall.TIOCSCTTY), 0); err != 0 { return err From 0563453b918b47c1f9d1e05b8650d2c8bf7ac3af Mon Sep 17 00:00:00 2001 From: Bernerd Schaefer Date: Wed, 28 May 2014 16:41:48 +0200 Subject: [PATCH 281/400] SETUID/SETGID not required for changing user It is no longer necessary to pass "SETUID" or "SETGID" capabilities to the container when a "user" is specified in the config. Docker-DCO-1.1-Signed-off-by: Bernerd Schaefer (github: bernerdschaefer) --- pkg/libcontainer/nsinit/init.go | 24 ++++++++++++++++--- .../security/capabilities/capabilities.go | 19 +++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 8139865a0..0f1f48418 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -172,15 +172,33 @@ func setupNetwork(container *libcontainer.Container, context libcontainer.Contex // and working dir, and closes any leaky file descriptors // before execing the command inside the namespace func FinalizeNamespace(container *libcontainer.Container) error { - if err := capabilities.DropCapabilities(container); err != nil { - return fmt.Errorf("drop capabilities %s", err) - } if err := system.CloseFdsFrom(3); err != nil { return fmt.Errorf("close open file descriptors %s", err) } + + // drop capabilities in bounding set before changing user + if err := capabilities.DropBoundingSet(container); err != nil { + return fmt.Errorf("drop bounding set %s", err) + } + + // preserve existing capabilities while we change users + if err := system.SetKeepCaps(); err != nil { + return fmt.Errorf("set keep caps %s", err) + } + if err := SetupUser(container.User); err != nil { return fmt.Errorf("setup user %s", err) } + + if err := system.ClearKeepCaps(); err != nil { + return fmt.Errorf("clear keep caps %s", err) + } + + // drop all other capabilities + if err := capabilities.DropCapabilities(container); err != nil { + return fmt.Errorf("drop capabilities %s", err) + } + if container.WorkingDir != "" { if err := system.Chdir(container.WorkingDir); err != nil { return fmt.Errorf("chdir to %s %s", container.WorkingDir, err) diff --git a/pkg/libcontainer/security/capabilities/capabilities.go b/pkg/libcontainer/security/capabilities/capabilities.go index ba72070f5..64ea961a1 100644 --- a/pkg/libcontainer/security/capabilities/capabilities.go +++ b/pkg/libcontainer/security/capabilities/capabilities.go @@ -9,6 +9,25 @@ import ( const allCapabilityTypes = capability.CAPS | capability.BOUNDS +// DropBoundingSet drops the capability bounding set to those specified in the +// container configuration. +func DropBoundingSet(container *libcontainer.Container) error { + c, err := capability.NewPid(os.Getpid()) + if err != nil { + return err + } + + keep := getEnabledCapabilities(container) + c.Clear(capability.BOUNDS) + c.Set(capability.BOUNDS, keep...) + + if err := c.Apply(capability.BOUNDS); err != nil { + return err + } + + return nil +} + // DropCapabilities drops all capabilities for the current process expect those specified in the container configuration. func DropCapabilities(container *libcontainer.Container) error { c, err := capability.NewPid(os.Getpid()) From 50417f6abc6d6de7cf32e956d538fd9103c5f9b4 Mon Sep 17 00:00:00 2001 From: FLGMwt Date: Wed, 28 May 2014 11:59:29 -0500 Subject: [PATCH 282/400] Change 'Uploading context' wording Docker-DCO-1.1-Signed-off-by: Ryan Stelly (github: FLGMwt) --- api/client/commands.go | 4 ++-- contrib/man/md/Dockerfile.5.md | 2 +- contrib/man/md/docker-build.1.md | 2 +- docs/sources/reference/builder.md | 2 +- docs/sources/reference/commandline/cli.md | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index 67c5aec2d..4d191baf5 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -172,9 +172,9 @@ func (cli *DockerCli) CmdBuild(args ...string) error { // FIXME: ProgressReader shouldn't be this annoying to use if context != nil { sf := utils.NewStreamFormatter(false) - body = utils.ProgressReader(context, 0, cli.err, sf, true, "", "Uploading context") + body = utils.ProgressReader(context, 0, cli.err, sf, true, "", "Sending build context to Docker daemon") } - // Upload the build context + // Send the build context v := &url.Values{} //Check if the given image name can be resolved diff --git a/contrib/man/md/Dockerfile.5.md b/contrib/man/md/Dockerfile.5.md index 06970eb87..2e964c9c5 100644 --- a/contrib/man/md/Dockerfile.5.md +++ b/contrib/man/md/Dockerfile.5.md @@ -34,7 +34,7 @@ A Dockerfile is similar to a Makefile. The path to the source repository defines where to find the context of the build. The build is run by the docker daemon, not the CLI. The whole context must be transferred to the daemon. The Docker CLI reports - "Uploading context" when the context is sent to the daemon. + "Sending build context to Docker daemon" when the context is sent to the daemon. **sudo docker build -t repository/tag .** -- specifies a repository and tag at which to save the new image if the build diff --git a/contrib/man/md/docker-build.1.md b/contrib/man/md/docker-build.1.md index b3e9a2842..455a21d50 100644 --- a/contrib/man/md/docker-build.1.md +++ b/contrib/man/md/docker-build.1.md @@ -17,7 +17,7 @@ be used by **ADD** commands found within the Dockerfile. Warning, this will send a lot of data to the Docker daemon depending on the contents of the current directory. The build is run by the Docker daemon, not by the CLI, so the whole context must be transferred to the daemon. -The Docker CLI reports "Uploading context" when the context is sent to +The Docker CLI reports "Sending build context to Docker daemon" when the context is sent to the daemon. When a single Dockerfile is given as the URL, then no context is set. diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index 6a4ae4ad2..27af21d54 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -23,7 +23,7 @@ Then call `docker build` with the path of you source repository as argument The path to the source repository defines where to find the *context* of the build. The build is run by the Docker daemon, not by the CLI, so the whole context must be transferred to the daemon. The Docker CLI reports -"Uploading context" when the context is sent to the daemon. +"Sending build context to Docker daemon" when the context is sent to the daemon. You can specify a repository and tag at which to save the new image if the build succeeds: diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index e0c5eb106..f6a1b04b0 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -259,7 +259,7 @@ happens at the client side (where you're running The transfer of context from the local machine to the Docker daemon is what the `docker` client means when you see the -"Uploading context" message. +"Sending build context" message. If you wish to keep the intermediate containers after the build is complete, you must use `--rm=false`. This does not From bf7f360dcac38037d5c4f9e2e90d01adc240ed2b Mon Sep 17 00:00:00 2001 From: William Thurston Date: Sat, 17 May 2014 07:06:29 +0000 Subject: [PATCH 283/400] Fixes #5749 libcontainer support for arbitrary route table entries Docker-DCO-1.1-Signed-off-by: William Thurston (github: jhspaybar) --- daemon/execdriver/lxc/init.go | 2 +- pkg/libcontainer/container.go | 24 ++++++ pkg/libcontainer/container.json | 10 +++ pkg/libcontainer/container_test.go | 5 ++ pkg/libcontainer/network/network.go | 4 +- pkg/libcontainer/network/veth.go | 22 +++--- pkg/libcontainer/nsinit/init.go | 13 ++++ pkg/netlink/netlink_linux.go | 113 +++++++++++++++++++++++----- pkg/netlink/netlink_unsupported.go | 5 +- 9 files changed, 166 insertions(+), 32 deletions(-) diff --git a/daemon/execdriver/lxc/init.go b/daemon/execdriver/lxc/init.go index e21e71764..5e77190e5 100644 --- a/daemon/execdriver/lxc/init.go +++ b/daemon/execdriver/lxc/init.go @@ -88,7 +88,7 @@ func setupNetworking(args *execdriver.InitArgs) error { return fmt.Errorf("Unable to set up networking, %s is not a valid gateway IP", args.Gateway) } - if err := netlink.AddDefaultGw(gw); err != nil { + if err := netlink.AddDefaultGw(gw.String(), "eth0"); err != nil { return fmt.Errorf("Unable to set up networking: %v", err) } } diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index 6734bfd59..27a4235de 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -46,6 +46,9 @@ type Container struct { // Networks specifies the container's network setup to be created Networks []*Network `json:"networks,omitempty"` + // Routes can be specified to create entries in the route table as the container is started + Routes []*Route `json:"routes,omitempty"` + // Cgroups specifies specific cgroup settings for the various subsystems that the container is // placed into to limit the resources the container has available Cgroups *cgroups.Cgroup `json:"cgroups,omitempty"` @@ -91,3 +94,24 @@ type Network struct { // container's interfaces if a pair is created, specifically in the case of type veth Mtu int `json:"mtu,omitempty"` } + +// Routes can be specified to create entries in the route table as the container is started +// +// All of destination, source, and gateway should be either IPv4 or IPv6. +// One of the three options must be present, and ommitted entries will use their +// IP family default for the route table. For IPv4 for example, setting the +// gateway to 1.2.3.4 and the interface to eth0 will set up a standard +// destination of 0.0.0.0(or *) when viewed in the route table. +type Route struct { + // Sets the destination and mask, should be a CIDR. Accepts IPv4 and IPv6 + Destination string `json:"destination,omitempty"` + + // Sets the source and mask, should be a CIDR. Accepts IPv4 and IPv6 + Source string `json:"source,omitempty"` + + // Sets the gateway. Accepts IPv4 and IPv6 + Gateway string `json:"gateway,omitempty"` + + // The device to set this route up for, for example: eth0 + InterfaceName string `json:"interface_name,omitempty"` +} diff --git a/pkg/libcontainer/container.json b/pkg/libcontainer/container.json index 7156260bc..3487b4717 100644 --- a/pkg/libcontainer/container.json +++ b/pkg/libcontainer/container.json @@ -24,6 +24,16 @@ "mtu": 1500 } ], + "routes": [ + { + "gateway": "172.17.42.1", + "interface_name": "eth0" + }, + { + "destination": "192.168.0.0/24", + "interface_name": "eth0" + } + ], "capabilities": [ "MKNOD" ], diff --git a/pkg/libcontainer/container_test.go b/pkg/libcontainer/container_test.go index f6e991edf..23b15d2cf 100644 --- a/pkg/libcontainer/container_test.go +++ b/pkg/libcontainer/container_test.go @@ -39,6 +39,11 @@ func TestContainerJsonFormat(t *testing.T) { t.Fail() } + if len(container.Routes) != 2 { + t.Log("should have found 2 routes") + t.Fail() + } + if !container.Namespaces["NEWNET"] { t.Log("namespaces should contain NEWNET") t.Fail() diff --git a/pkg/libcontainer/network/network.go b/pkg/libcontainer/network/network.go index f8dee4527..85a28dc37 100644 --- a/pkg/libcontainer/network/network.go +++ b/pkg/libcontainer/network/network.go @@ -53,8 +53,8 @@ func SetInterfaceMaster(name, master string) error { return netlink.AddToBridge(iface, masterIface) } -func SetDefaultGateway(ip string) error { - return netlink.AddDefaultGw(net.ParseIP(ip)) +func SetDefaultGateway(ip, ifaceName string) error { + return netlink.AddDefaultGw(ip, ifaceName) } func SetInterfaceIp(name string, rawIp string) error { diff --git a/pkg/libcontainer/network/veth.go b/pkg/libcontainer/network/veth.go index 3df0cd61e..d3be221c6 100644 --- a/pkg/libcontainer/network/veth.go +++ b/pkg/libcontainer/network/veth.go @@ -12,6 +12,8 @@ import ( type Veth struct { } +const defaultDevice = "eth0" + func (v *Veth) Create(n *libcontainer.Network, nspid int, context libcontainer.Context) error { var ( bridge string @@ -56,21 +58,21 @@ func (v *Veth) Initialize(config *libcontainer.Network, context libcontainer.Con if err := InterfaceDown(vethChild); err != nil { return fmt.Errorf("interface down %s %s", vethChild, err) } - if err := ChangeInterfaceName(vethChild, "eth0"); err != nil { - return fmt.Errorf("change %s to eth0 %s", vethChild, err) + if err := ChangeInterfaceName(vethChild, defaultDevice); err != nil { + return fmt.Errorf("change %s to %s %s", vethChild, defaultDevice, err) } - if err := SetInterfaceIp("eth0", config.Address); err != nil { - return fmt.Errorf("set eth0 ip %s", err) + if err := SetInterfaceIp(defaultDevice, config.Address); err != nil { + return fmt.Errorf("set %s ip %s", defaultDevice, err) } - if err := SetMtu("eth0", config.Mtu); err != nil { - return fmt.Errorf("set eth0 mtu to %d %s", config.Mtu, err) + if err := SetMtu(defaultDevice, config.Mtu); err != nil { + return fmt.Errorf("set %s mtu to %d %s", defaultDevice, config.Mtu, err) } - if err := InterfaceUp("eth0"); err != nil { - return fmt.Errorf("eth0 up %s", err) + if err := InterfaceUp(defaultDevice); err != nil { + return fmt.Errorf("%s up %s", defaultDevice, err) } if config.Gateway != "" { - if err := SetDefaultGateway(config.Gateway); err != nil { - return fmt.Errorf("set gateway to %s %s", config.Gateway, err) + if err := SetDefaultGateway(config.Gateway, defaultDevice); err != nil { + return fmt.Errorf("set gateway to %s on device %s failed with %s", config.Gateway, defaultDevice, err) } } return nil diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 301210676..95d07a496 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -18,6 +18,7 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer/security/capabilities" "github.com/dotcloud/docker/pkg/libcontainer/security/restrict" "github.com/dotcloud/docker/pkg/libcontainer/utils" + "github.com/dotcloud/docker/pkg/netlink" "github.com/dotcloud/docker/pkg/system" "github.com/dotcloud/docker/pkg/user" ) @@ -60,6 +61,9 @@ func Init(container *libcontainer.Container, uncleanRootfs, consolePath string, if err := setupNetwork(container, context); err != nil { return fmt.Errorf("setup networking %s", err) } + if err := setupRoute(container); err != nil { + return fmt.Errorf("setup route %s", err) + } label.Init() @@ -168,6 +172,15 @@ func setupNetwork(container *libcontainer.Container, context libcontainer.Contex return nil } +func setupRoute(container *libcontainer.Container) error { + for _, config := range container.Routes { + if err := netlink.AddRoute(config.Destination, config.Source, config.Gateway, config.InterfaceName); err != nil { + return err + } + } + return nil +} + // FinalizeNamespace drops the caps, sets the correct user // and working dir, and closes any leaky file descriptors // before execing the command inside the namespace diff --git a/pkg/netlink/netlink_linux.go b/pkg/netlink/netlink_linux.go index 6de293d42..21d459362 100644 --- a/pkg/netlink/netlink_linux.go +++ b/pkg/netlink/netlink_linux.go @@ -131,10 +131,9 @@ type RtMsg struct { syscall.RtMsg } -func newRtMsg(family int) *RtMsg { +func newRtMsg() *RtMsg { return &RtMsg{ RtMsg: syscall.RtMsg{ - Family: uint8(family), Table: syscall.RT_TABLE_MAIN, Scope: syscall.RT_SCOPE_UNIVERSE, Protocol: syscall.RTPROT_BOOT, @@ -367,40 +366,118 @@ done: return nil } -// Add a new default gateway. Identical to: -// ip route add default via $ip -func AddDefaultGw(ip net.IP) error { +// Add a new route table entry. +func AddRoute(destination, source, gateway, device string) error { + if destination == "" && source == "" && gateway == "" { + return fmt.Errorf("one of destination, source or gateway must not be blank") + } + s, err := getNetlinkSocket() if err != nil { return err } defer s.Close() - family := getIpFamily(ip) - wb := newNetlinkRequest(syscall.RTM_NEWROUTE, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK) + msg := newRtMsg() + currentFamily := -1 + var rtAttrs []*RtAttr - msg := newRtMsg(family) - wb.AddData(msg) - - var ipData []byte - if family == syscall.AF_INET { - ipData = ip.To4() - } else { - ipData = ip.To16() + if destination != "" { + destIP, destNet, err := net.ParseCIDR(destination) + if err != nil { + return fmt.Errorf("destination CIDR %s couldn't be parsed", destination) + } + destFamily := getIpFamily(destIP) + currentFamily = destFamily + destLen, bits := destNet.Mask.Size() + if destLen == 0 && bits == 0 { + return fmt.Errorf("destination CIDR %s generated a non-canonical Mask", destination) + } + msg.Family = uint8(destFamily) + msg.Dst_len = uint8(destLen) + var destData []byte + if destFamily == syscall.AF_INET { + destData = destIP.To4() + } else { + destData = destIP.To16() + } + rtAttrs = append(rtAttrs, newRtAttr(syscall.RTA_DST, destData)) } - gateway := newRtAttr(syscall.RTA_GATEWAY, ipData) + if source != "" { + srcIP, srcNet, err := net.ParseCIDR(source) + if err != nil { + return fmt.Errorf("source CIDR %s couldn't be parsed", source) + } + srcFamily := getIpFamily(srcIP) + if currentFamily != -1 && currentFamily != srcFamily { + return fmt.Errorf("source and destination ip were not the same IP family") + } + currentFamily = srcFamily + srcLen, bits := srcNet.Mask.Size() + if srcLen == 0 && bits == 0 { + return fmt.Errorf("source CIDR %s generated a non-canonical Mask", source) + } + msg.Family = uint8(srcFamily) + msg.Src_len = uint8(srcLen) + var srcData []byte + if srcFamily == syscall.AF_INET { + srcData = srcIP.To4() + } else { + srcData = srcIP.To16() + } + rtAttrs = append(rtAttrs, newRtAttr(syscall.RTA_SRC, srcData)) + } - wb.AddData(gateway) + if gateway != "" { + gwIP := net.ParseIP(gateway) + if gwIP == nil { + return fmt.Errorf("gateway IP %s couldn't be parsed", gateway) + } + gwFamily := getIpFamily(gwIP) + if currentFamily != -1 && currentFamily != gwFamily { + return fmt.Errorf("gateway, source, and destination ip were not the same IP family") + } + msg.Family = uint8(gwFamily) + var gwData []byte + if gwFamily == syscall.AF_INET { + gwData = gwIP.To4() + } else { + gwData = gwIP.To16() + } + rtAttrs = append(rtAttrs, newRtAttr(syscall.RTA_GATEWAY, gwData)) + } + + wb.AddData(msg) + for _, attr := range rtAttrs { + wb.AddData(attr) + } + + var ( + native = nativeEndian() + b = make([]byte, 4) + ) + iface, err := net.InterfaceByName(device) + if err != nil { + return err + } + native.PutUint32(b, uint32(iface.Index)) + + wb.AddData(newRtAttr(syscall.RTA_OIF, b)) if err := s.Send(wb); err != nil { return err } - return s.HandleAck(wb.Seq) } +// Add a new default gateway. Identical to: +// ip route add default via $ip +func AddDefaultGw(ip, device string) error { + return AddRoute("", "", ip, device) +} + // Bring up a particular network interface func NetworkLinkUp(iface *net.Interface) error { s, err := getNetlinkSocket() diff --git a/pkg/netlink/netlink_unsupported.go b/pkg/netlink/netlink_unsupported.go index 8a5531b9e..135934566 100644 --- a/pkg/netlink/netlink_unsupported.go +++ b/pkg/netlink/netlink_unsupported.go @@ -27,9 +27,12 @@ func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error { return ErrNotImplemented } -func AddDefaultGw(ip net.IP) error { +func AddRoute(destination, source, gateway, device string) error { return ErrNotImplemented +} +func AddDefaultGw(ip, device string) error { + return ErrNotImplemented } func NetworkSetMTU(iface *net.Interface, mtu int) error { From 35d08bdd01e3c63414d7998efc0813803e2ba5d9 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 28 May 2014 20:27:03 +0200 Subject: [PATCH 284/400] Revert "Remove the bind mount for dev/console which override the mknod/label" This reverts commit ae85dd54582e94d36b146ab1688844ed58cc8df3. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- pkg/libcontainer/console/console.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/libcontainer/console/console.go b/pkg/libcontainer/console/console.go index 62edbfde8..5f06aea22 100644 --- a/pkg/libcontainer/console/console.go +++ b/pkg/libcontainer/console/console.go @@ -40,6 +40,9 @@ func Setup(rootfs, consolePath, mountLabel string) error { if err := label.SetFileLabel(consolePath, mountLabel); err != nil { return fmt.Errorf("set file label %s %s", dest, err) } + if err := system.Mount(consolePath, dest, "bind", syscall.MS_BIND, ""); err != nil { + return fmt.Errorf("bind %s to %s %s", consolePath, dest, err) + } return nil } From 7f5cd76824b500418ed168dfcfeb73de8badcb51 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 28 May 2014 20:44:24 +0200 Subject: [PATCH 285/400] libcontainer: Don't create a device node on /dev/console to bind mount on There is no need for this, the device node by itself doesn't work, since its not on a devpts fs, and we can just a regular file to bind mount over. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- pkg/libcontainer/console/console.go | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/pkg/libcontainer/console/console.go b/pkg/libcontainer/console/console.go index 5f06aea22..79a480418 100644 --- a/pkg/libcontainer/console/console.go +++ b/pkg/libcontainer/console/console.go @@ -17,29 +17,26 @@ func Setup(rootfs, consolePath, mountLabel string) error { oldMask := system.Umask(0000) defer system.Umask(oldMask) - stat, err := os.Stat(consolePath) - if err != nil { - return fmt.Errorf("stat console %s %s", consolePath, err) - } - var ( - st = stat.Sys().(*syscall.Stat_t) - dest = filepath.Join(rootfs, "dev/console") - ) - if err := os.Remove(dest); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("remove %s %s", dest, err) - } if err := os.Chmod(consolePath, 0600); err != nil { return err } if err := os.Chown(consolePath, 0, 0); err != nil { return err } - if err := system.Mknod(dest, (st.Mode&^07777)|0600, int(st.Rdev)); err != nil { - return fmt.Errorf("mknod %s %s", dest, err) - } if err := label.SetFileLabel(consolePath, mountLabel); err != nil { - return fmt.Errorf("set file label %s %s", dest, err) + return fmt.Errorf("set file label %s %s", consolePath, err) } + + dest := filepath.Join(rootfs, "dev/console") + + f, err := os.Create(dest) + if err != nil && !os.IsExist(err) { + return fmt.Errorf("create %s %s", dest, err) + } + if f != nil { + f.Close() + } + if err := system.Mount(consolePath, dest, "bind", syscall.MS_BIND, ""); err != nil { return fmt.Errorf("bind %s to %s %s", consolePath, dest, err) } From 180c2a678510a93a442f1bcdb6a47287ec88ccd6 Mon Sep 17 00:00:00 2001 From: unclejack Date: Wed, 28 May 2014 20:53:16 +0300 Subject: [PATCH 286/400] add support for COPY to docker build This adds a COPY command to docker build which works like ADD, but is only for local files and it doesn't extract files. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- docs/sources/reference/builder.md | 40 ++++ .../TestCopy/DirContentToExistDir/Dockerfile | 10 + .../DirContentToExistDir/test_dir/test_file | 0 .../TestCopy/DirContentToRoot/Dockerfile | 8 + .../DirContentToRoot/test_dir/test_file | 0 .../TestCopy/DisallowRemote/Dockerfile | 2 + .../build_tests/TestCopy/EtcToRoot/Dockerfile | 2 + .../TestCopy/SingleFileToExistDir/Dockerfile | 10 + .../TestCopy/SingleFileToExistDir/test_file | 0 .../SingleFileToNonExistDir/Dockerfile | 9 + .../SingleFileToNonExistDir/test_file | 0 .../TestCopy/SingleFileToRoot/Dockerfile | 9 + .../TestCopy/SingleFileToWorkdir/Dockerfile | 2 + .../TestCopy/WholeDirToRoot/Dockerfile | 11 ++ integration-cli/docker_cli_build_test.go | 175 ++++++++++++++++++ server/buildfile.go | 36 ++-- 16 files changed, 301 insertions(+), 13 deletions(-) create mode 100644 integration-cli/build_tests/TestCopy/DirContentToExistDir/Dockerfile create mode 100644 integration-cli/build_tests/TestCopy/DirContentToExistDir/test_dir/test_file create mode 100644 integration-cli/build_tests/TestCopy/DirContentToRoot/Dockerfile create mode 100644 integration-cli/build_tests/TestCopy/DirContentToRoot/test_dir/test_file create mode 100644 integration-cli/build_tests/TestCopy/DisallowRemote/Dockerfile create mode 100644 integration-cli/build_tests/TestCopy/EtcToRoot/Dockerfile create mode 100644 integration-cli/build_tests/TestCopy/SingleFileToExistDir/Dockerfile create mode 100644 integration-cli/build_tests/TestCopy/SingleFileToExistDir/test_file create mode 100644 integration-cli/build_tests/TestCopy/SingleFileToNonExistDir/Dockerfile create mode 100644 integration-cli/build_tests/TestCopy/SingleFileToNonExistDir/test_file create mode 100644 integration-cli/build_tests/TestCopy/SingleFileToRoot/Dockerfile create mode 100644 integration-cli/build_tests/TestCopy/SingleFileToWorkdir/Dockerfile create mode 100644 integration-cli/build_tests/TestCopy/WholeDirToRoot/Dockerfile diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index 6a4ae4ad2..235d92e1b 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -290,6 +290,46 @@ The copy obeys the following rules: - If `` doesn't exist, it is created along with all missing directories in its path. +## COPY + + COPY + +The `COPY` instruction will copy new files from `` and add them to the +container's filesystem at path ``. + +`` must be the path to a file or directory relative to the source directory +being built (also called the *context* of the build). + +`` is the absolute path to which the source will be copied inside the +destination container. + +All new files and directories are created with a uid and gid of 0. + +> **Note**: +> If you build using STDIN (`docker build - < somefile`), there is no +> build context, so `COPY` can't be used. + +The copy obeys the following rules: + +- The `` path must be inside the *context* of the build; + you cannot `COPY ../something /something`, because the first step of a + `docker build` is to send the context directory (and subdirectories) to the + docker daemon. + +- If `` is a directory, the entire directory is copied, including + filesystem metadata. + +- If `` is any other kind of file, it is copied individually along with + its metadata. In this case, if `` ends with a trailing slash `/`, it + will be considered a directory and the contents of `` will be written + at `/base()`. + +- If `` does not end with a trailing slash, it will be considered a + regular file and the contents of `` will be written at ``. + +- If `` doesn't exist, it is created along with all missing directories + in its path. + ## ENTRYPOINT ENTRYPOINT has two forms: diff --git a/integration-cli/build_tests/TestCopy/DirContentToExistDir/Dockerfile b/integration-cli/build_tests/TestCopy/DirContentToExistDir/Dockerfile new file mode 100644 index 000000000..d63e8538b --- /dev/null +++ b/integration-cli/build_tests/TestCopy/DirContentToExistDir/Dockerfile @@ -0,0 +1,10 @@ +FROM busybox +RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd +RUN echo 'dockerio:x:1001:' >> /etc/group +RUN mkdir /exists +RUN touch /exists/exists_file +RUN chown -R dockerio.dockerio /exists +COPY test_dir/ /exists/ +RUN [ $(ls -l / | grep exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] +RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' ] +RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ] diff --git a/integration-cli/build_tests/TestCopy/DirContentToExistDir/test_dir/test_file b/integration-cli/build_tests/TestCopy/DirContentToExistDir/test_dir/test_file new file mode 100644 index 000000000..e69de29bb diff --git a/integration-cli/build_tests/TestCopy/DirContentToRoot/Dockerfile b/integration-cli/build_tests/TestCopy/DirContentToRoot/Dockerfile new file mode 100644 index 000000000..45df77e56 --- /dev/null +++ b/integration-cli/build_tests/TestCopy/DirContentToRoot/Dockerfile @@ -0,0 +1,8 @@ +FROM busybox +RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd +RUN echo 'dockerio:x:1001:' >> /etc/group +RUN touch /exists +RUN chown dockerio.dockerio exists +COPY test_dir / +RUN [ $(ls -l /test_file | awk '{print $3":"$4}') = 'root:root' ] +RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] diff --git a/integration-cli/build_tests/TestCopy/DirContentToRoot/test_dir/test_file b/integration-cli/build_tests/TestCopy/DirContentToRoot/test_dir/test_file new file mode 100644 index 000000000..e69de29bb diff --git a/integration-cli/build_tests/TestCopy/DisallowRemote/Dockerfile b/integration-cli/build_tests/TestCopy/DisallowRemote/Dockerfile new file mode 100644 index 000000000..e6bc0c0dd --- /dev/null +++ b/integration-cli/build_tests/TestCopy/DisallowRemote/Dockerfile @@ -0,0 +1,2 @@ +FROM busybox +COPY https://index.docker.io/robots.txt / diff --git a/integration-cli/build_tests/TestCopy/EtcToRoot/Dockerfile b/integration-cli/build_tests/TestCopy/EtcToRoot/Dockerfile new file mode 100644 index 000000000..b4f319f80 --- /dev/null +++ b/integration-cli/build_tests/TestCopy/EtcToRoot/Dockerfile @@ -0,0 +1,2 @@ +FROM scratch +COPY . / diff --git a/integration-cli/build_tests/TestCopy/SingleFileToExistDir/Dockerfile b/integration-cli/build_tests/TestCopy/SingleFileToExistDir/Dockerfile new file mode 100644 index 000000000..3edfe661d --- /dev/null +++ b/integration-cli/build_tests/TestCopy/SingleFileToExistDir/Dockerfile @@ -0,0 +1,10 @@ +FROM busybox +RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd +RUN echo 'dockerio:x:1001:' >> /etc/group +RUN mkdir /exists +RUN touch /exists/exists_file +RUN chown -R dockerio.dockerio /exists +COPY test_file /exists/ +RUN [ $(ls -l / | grep exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] +RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ] +RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' ] diff --git a/integration-cli/build_tests/TestCopy/SingleFileToExistDir/test_file b/integration-cli/build_tests/TestCopy/SingleFileToExistDir/test_file new file mode 100644 index 000000000..e69de29bb diff --git a/integration-cli/build_tests/TestCopy/SingleFileToNonExistDir/Dockerfile b/integration-cli/build_tests/TestCopy/SingleFileToNonExistDir/Dockerfile new file mode 100644 index 000000000..33b65a62c --- /dev/null +++ b/integration-cli/build_tests/TestCopy/SingleFileToNonExistDir/Dockerfile @@ -0,0 +1,9 @@ +FROM busybox +RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd +RUN echo 'dockerio:x:1001:' >> /etc/group +RUN touch /exists +RUN chown dockerio.dockerio /exists +COPY test_file /test_dir/ +RUN [ $(ls -l / | grep test_dir | awk '{print $3":"$4}') = 'root:root' ] +RUN [ $(ls -l /test_dir/test_file | awk '{print $3":"$4}') = 'root:root' ] +RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] diff --git a/integration-cli/build_tests/TestCopy/SingleFileToNonExistDir/test_file b/integration-cli/build_tests/TestCopy/SingleFileToNonExistDir/test_file new file mode 100644 index 000000000..e69de29bb diff --git a/integration-cli/build_tests/TestCopy/SingleFileToRoot/Dockerfile b/integration-cli/build_tests/TestCopy/SingleFileToRoot/Dockerfile new file mode 100644 index 000000000..38fd09026 --- /dev/null +++ b/integration-cli/build_tests/TestCopy/SingleFileToRoot/Dockerfile @@ -0,0 +1,9 @@ +FROM busybox +RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd +RUN echo 'dockerio:x:1001:' >> /etc/group +RUN touch /exists +RUN chown dockerio.dockerio /exists +COPY test_file / +RUN [ $(ls -l /test_file | awk '{print $3":"$4}') = 'root:root' ] +RUN [ $(ls -l /test_file | awk '{print $1}') = '-rw-r--r--' ] +RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] diff --git a/integration-cli/build_tests/TestCopy/SingleFileToWorkdir/Dockerfile b/integration-cli/build_tests/TestCopy/SingleFileToWorkdir/Dockerfile new file mode 100644 index 000000000..ba2d797e3 --- /dev/null +++ b/integration-cli/build_tests/TestCopy/SingleFileToWorkdir/Dockerfile @@ -0,0 +1,2 @@ +FROM busybox +COPY test_file . diff --git a/integration-cli/build_tests/TestCopy/WholeDirToRoot/Dockerfile b/integration-cli/build_tests/TestCopy/WholeDirToRoot/Dockerfile new file mode 100644 index 000000000..91be29fe7 --- /dev/null +++ b/integration-cli/build_tests/TestCopy/WholeDirToRoot/Dockerfile @@ -0,0 +1,11 @@ +FROM busybox +RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd +RUN echo 'dockerio:x:1001:' >> /etc/group +RUN touch /exists +RUN chown dockerio.dockerio exists +COPY test_dir /test_dir +RUN [ $(ls -l / | grep test_dir | awk '{print $3":"$4}') = 'root:root' ] +RUN [ $(ls -l / | grep test_dir | awk '{print $1}') = 'drwxr-xr-x' ] +RUN [ $(ls -l /test_dir/test_file | awk '{print $3":"$4}') = 'root:root' ] +RUN [ $(ls -l /test_dir/test_file | awk '{print $1}') = '-rw-r--r--' ] +RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ] diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index d804ba796..f2bce99cf 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -237,6 +237,181 @@ func TestAddEtcToRoot(t *testing.T) { logDone("build - add etc directory to root") } +func TestCopySingleFileToRoot(t *testing.T) { + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestCopy", "SingleFileToRoot") + f, err := os.OpenFile(filepath.Join(buildDirectory, "test_file"), os.O_CREATE, 0644) + if err != nil { + t.Fatal(err) + } + f.Close() + buildCmd := exec.Command(dockerBinary, "build", "-t", "testcopyimg", ".") + buildCmd.Dir = buildDirectory + out, exitCode, err := runCommandWithOutput(buildCmd) + errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) + + if err != nil || exitCode != 0 { + t.Fatal("failed to build the image") + } + + deleteImages("testcopyimg") + + logDone("build - copy single file to root") +} + +// Issue #3960: "ADD src ." hangs - adapted for COPY +func TestCopySingleFileToWorkdir(t *testing.T) { + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestCopy", "SingleFileToWorkdir") + f, err := os.OpenFile(filepath.Join(buildDirectory, "test_file"), os.O_CREATE, 0644) + if err != nil { + t.Fatal(err) + } + f.Close() + buildCmd := exec.Command(dockerBinary, "build", "-t", "testcopyimg", ".") + buildCmd.Dir = buildDirectory + done := make(chan error) + go func() { + out, exitCode, err := runCommandWithOutput(buildCmd) + if err != nil || exitCode != 0 { + done <- fmt.Errorf("build failed to complete: %s %v", out, err) + return + } + done <- nil + }() + select { + case <-time.After(5 * time.Second): + if err := buildCmd.Process.Kill(); err != nil { + fmt.Printf("could not kill build (pid=%d): %v\n", buildCmd.Process.Pid, err) + } + t.Fatal("build timed out") + case err := <-done: + if err != nil { + t.Fatal(err) + } + } + + deleteImages("testcopyimg") + + logDone("build - copy single file to workdir") +} + +func TestCopySingleFileToExistDir(t *testing.T) { + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestCopy") + buildCmd := exec.Command(dockerBinary, "build", "-t", "testcopyimg", "SingleFileToExistDir") + buildCmd.Dir = buildDirectory + out, exitCode, err := runCommandWithOutput(buildCmd) + errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) + + if err != nil || exitCode != 0 { + t.Fatal("failed to build the image") + } + + deleteImages("testcopyimg") + + logDone("build - add single file to existing dir") +} + +func TestCopySingleFileToNonExistDir(t *testing.T) { + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestCopy") + buildCmd := exec.Command(dockerBinary, "build", "-t", "testcopyimg", "SingleFileToNonExistDir") + buildCmd.Dir = buildDirectory + out, exitCode, err := runCommandWithOutput(buildCmd) + errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) + + if err != nil || exitCode != 0 { + t.Fatal("failed to build the image") + } + + deleteImages("testcopyimg") + + logDone("build - copy single file to non-existing dir") +} + +func TestCopyDirContentToRoot(t *testing.T) { + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestCopy") + buildCmd := exec.Command(dockerBinary, "build", "-t", "testcopyimg", "DirContentToRoot") + buildCmd.Dir = buildDirectory + out, exitCode, err := runCommandWithOutput(buildCmd) + errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) + + if err != nil || exitCode != 0 { + t.Fatal("failed to build the image") + } + + deleteImages("testcopyimg") + + logDone("build - copy directory contents to root") +} + +func TestCopyDirContentToExistDir(t *testing.T) { + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestCopy") + buildCmd := exec.Command(dockerBinary, "build", "-t", "testcopyimg", "DirContentToExistDir") + buildCmd.Dir = buildDirectory + out, exitCode, err := runCommandWithOutput(buildCmd) + errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) + + if err != nil || exitCode != 0 { + t.Fatal("failed to build the image") + } + + deleteImages("testcopyimg") + + logDone("build - copy directory contents to existing dir") +} + +func TestCopyWholeDirToRoot(t *testing.T) { + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestCopy", "WholeDirToRoot") + test_dir := filepath.Join(buildDirectory, "test_dir") + if err := os.MkdirAll(test_dir, 0755); err != nil { + t.Fatal(err) + } + f, err := os.OpenFile(filepath.Join(test_dir, "test_file"), os.O_CREATE, 0644) + if err != nil { + t.Fatal(err) + } + f.Close() + buildCmd := exec.Command(dockerBinary, "build", "-t", "testcopyimg", ".") + buildCmd.Dir = buildDirectory + out, exitCode, err := runCommandWithOutput(buildCmd) + errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) + + if err != nil || exitCode != 0 { + t.Fatal("failed to build the image") + } + + deleteImages("testcopyimg") + + logDone("build - copy whole directory to root") +} + +func TestCopyEtcToRoot(t *testing.T) { + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestCopy") + buildCmd := exec.Command(dockerBinary, "build", "-t", "testcopyimg", "EtcToRoot") + buildCmd.Dir = buildDirectory + out, exitCode, err := runCommandWithOutput(buildCmd) + errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) + + if err != nil || exitCode != 0 { + t.Fatal("failed to build the image") + } + + deleteImages("testcopyimg") + logDone("build - copy etc directory to root") +} + +func TestCopyDisallowRemote(t *testing.T) { + buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestCopy") + buildCmd := exec.Command(dockerBinary, "build", "-t", "testcopyimg", "DisallowRemote") + buildCmd.Dir = buildDirectory + out, exitCode, err := runCommandWithOutput(buildCmd) + + if err == nil || exitCode == 0 { + t.Fatalf("building the image should've failed; output: %s", out) + } + + deleteImages("testcopyimg") + logDone("build - copy - disallow copy from remote") +} + // Issue #5270 - ensure we throw a better error than "unexpected EOF" // when we can't access files in the context. func TestBuildWithInaccessibleFilesInContext(t *testing.T) { diff --git a/server/buildfile.go b/server/buildfile.go index d20666444..26fc49890 100644 --- a/server/buildfile.go +++ b/server/buildfile.go @@ -340,7 +340,7 @@ func (b *buildFile) CmdInsert(args string) error { } func (b *buildFile) CmdCopy(args string) error { - return fmt.Errorf("COPY has been deprecated. Please use ADD instead") + return b.runContextCommand(args, false, false, "COPY") } func (b *buildFile) CmdWorkdir(workdir string) error { @@ -399,7 +399,7 @@ func (b *buildFile) checkPathForAddition(orig string) error { return nil } -func (b *buildFile) addContext(container *daemon.Container, orig, dest string, remote bool) error { +func (b *buildFile) addContext(container *daemon.Container, orig, dest string, decompress bool) error { var ( err error destExists = true @@ -439,8 +439,8 @@ func (b *buildFile) addContext(container *daemon.Container, orig, dest string, r return copyAsDirectory(origPath, destPath, destExists) } - // If we are adding a remote file, do not try to untar it - if !remote { + // If we are adding a remote file (or we've been told not to decompress), do not try to untar it + if decompress { // First try to unpack the source as an archive // to support the untar feature we need to clean up the path a little bit // because tar is very forgiving. First we need to strip off the archive's @@ -473,13 +473,13 @@ func (b *buildFile) addContext(container *daemon.Container, orig, dest string, r return fixPermissions(resPath, 0, 0) } -func (b *buildFile) CmdAdd(args string) error { +func (b *buildFile) runContextCommand(args string, allowRemote bool, allowDecompression bool, cmdName string) error { if b.context == nil { - return fmt.Errorf("No context given. Impossible to use ADD") + return fmt.Errorf("No context given. Impossible to use %s", cmdName) } tmp := strings.SplitN(args, " ", 2) if len(tmp) != 2 { - return fmt.Errorf("Invalid ADD format") + return fmt.Errorf("Invalid %s format", cmdName) } orig, err := b.ReplaceEnvMatches(strings.Trim(tmp[0], " \t")) @@ -493,7 +493,7 @@ func (b *buildFile) CmdAdd(args string) error { } cmd := b.config.Cmd - b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)} + b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, orig, dest)} defer func(cmd []string) { b.config.Cmd = cmd }(cmd) b.config.Image = b.image @@ -502,11 +502,14 @@ func (b *buildFile) CmdAdd(args string) error { destPath = dest remoteHash string isRemote bool + decompress = true ) - if utils.IsURL(orig) { + isRemote = utils.IsURL(orig) + if isRemote && !allowRemote { + return fmt.Errorf("Source can't be an URL for %s", cmdName) + } else if utils.IsURL(orig) { // Initiate the download - isRemote = true resp, err := utils.Download(orig) if err != nil { return err @@ -608,7 +611,7 @@ func (b *buildFile) CmdAdd(args string) error { hash = "file:" + h } } - b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", hash, dest)} + b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, hash, dest)} hit, err := b.probeCache() if err != nil { return err @@ -631,16 +634,23 @@ func (b *buildFile) CmdAdd(args string) error { } defer container.Unmount() - if err := b.addContext(container, origPath, destPath, isRemote); err != nil { + if !allowDecompression || isRemote { + decompress = false + } + if err := b.addContext(container, origPath, destPath, decompress); err != nil { return err } - if err := b.commit(container.ID, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil { + if err := b.commit(container.ID, cmd, fmt.Sprintf("%s %s in %s", cmdName, orig, dest)); err != nil { return err } return nil } +func (b *buildFile) CmdAdd(args string) error { + return b.runContextCommand(args, true, true, "ADD") +} + func (b *buildFile) create() (*daemon.Container, error) { if b.image == "" { return nil, fmt.Errorf("Please provide a source image with `from` prior to run") From 5d04b9deafc5ce173364a24881f8e950c9430be0 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 28 May 2014 18:09:05 -0700 Subject: [PATCH 287/400] Handle EBUSY on remount Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- .../security/restrict/restrict.go | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/pkg/libcontainer/security/restrict/restrict.go b/pkg/libcontainer/security/restrict/restrict.go index a22a1aa73..2dadc4fff 100644 --- a/pkg/libcontainer/security/restrict/restrict.go +++ b/pkg/libcontainer/security/restrict/restrict.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "syscall" + "time" "github.com/dotcloud/docker/pkg/system" ) @@ -13,20 +14,25 @@ import ( const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV func mountReadonly(path string) error { - if err := system.Mount("", path, "", syscall.MS_REMOUNT|syscall.MS_RDONLY, ""); err != nil { - if err == syscall.EINVAL { - // Probably not a mountpoint, use bind-mount - if err := system.Mount(path, path, "", syscall.MS_BIND, ""); err != nil { + for i := 0; i < 5; i++ { + if err := system.Mount("", path, "", syscall.MS_REMOUNT|syscall.MS_RDONLY, ""); err != nil { + switch err { + case syscall.EINVAL: + // Probably not a mountpoint, use bind-mount + if err := system.Mount(path, path, "", syscall.MS_BIND, ""); err != nil { + return err + } + return system.Mount(path, path, "", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC|defaultMountFlags, "") + case syscall.EBUSY: + time.Sleep(100 * time.Millisecond) + continue + default: return err } - if err := system.Mount(path, path, "", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC|defaultMountFlags, ""); err != nil { - return err - } - } else { - return err } + return nil } - return nil + return fmt.Errorf("unable to mount %s as readonly max retries reached", path) } // This has to be called while the container still has CAP_SYS_ADMIN (to be able to perform mounts). @@ -38,6 +44,7 @@ func Restrict(mounts ...string) error { return fmt.Errorf("unable to remount %s readonly: %s", dest, err) } } + if err := system.Mount("/dev/null", "/proc/kcore", "", syscall.MS_BIND, ""); err != nil && !os.IsNotExist(err) { return fmt.Errorf("unable to bind-mount /dev/null over /proc/kcore: %s", err) } From a0505edc9a890b3d4cdf5cf1850273c250c3dc41 Mon Sep 17 00:00:00 2001 From: Robin Speekenbrink Date: Thu, 8 May 2014 15:11:17 +0200 Subject: [PATCH 288/400] updated documentation for mentioning images vs containers Docker-DCO-1.1-Signed-off-by: Robin Speekenbrink (github: fruitl00p) rebased by Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- api/client/commands.go | 4 ++-- contrib/completion/fish/docker.fish | 2 +- contrib/man/md/docker-build.1.md | 2 +- contrib/man/md/docker.1.md | 2 +- contrib/man/old-man/docker-build.1 | 2 +- contrib/man/old-man/docker.1 | 2 +- docs/sources/examples/cfengine_process_management.md | 4 ++-- docs/sources/examples/using_supervisord.md | 4 ++-- docs/sources/introduction/working-with-docker.md | 2 +- docs/sources/reference/api/README.md | 2 +- docs/sources/reference/commandline/cli.md | 2 +- docs/sources/use/basics.md | 4 ++-- hack/RELEASE-CHECKLIST.md | 2 +- hack/make.sh | 2 +- 14 files changed, 18 insertions(+), 18 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index 67c5aec2d..608059584 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -47,7 +47,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[unix://%s]: tcp://host:port to bind/connect to or unix://path/to/socket to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", api.DEFAULTUNIXSOCKET) for _, command := range [][]string{ {"attach", "Attach to a running container"}, - {"build", "Build a container from a Dockerfile"}, + {"build", "Build an image from a Dockerfile"}, {"commit", "Create a new image from a container's changes"}, {"cp", "Copy files/folders from the containers filesystem to the host path"}, {"diff", "Inspect changes on a container's filesystem"}, @@ -105,7 +105,7 @@ func (cli *DockerCli) CmdInsert(args ...string) error { } func (cli *DockerCli) CmdBuild(args ...string) error { - cmd := cli.Subcmd("build", "[OPTIONS] PATH | URL | -", "Build a new container image from the source code at PATH") + cmd := cli.Subcmd("build", "[OPTIONS] PATH | URL | -", "Build a new image from the source code at PATH") tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) to be applied to the resulting image in case of success") suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers") noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image") diff --git a/contrib/completion/fish/docker.fish b/contrib/completion/fish/docker.fish index e3bb72aeb..7ea478d05 100644 --- a/contrib/completion/fish/docker.fish +++ b/contrib/completion/fish/docker.fish @@ -71,7 +71,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from attach' -l sig-proxy -d complete -c docker -A -f -n '__fish_seen_subcommand_from attach' -a '(__fish_print_docker_containers running)' -d "Container" # build -complete -c docker -f -n '__fish_docker_no_subcommand' -a build -d 'Build a container from a Dockerfile' +complete -c docker -f -n '__fish_docker_no_subcommand' -a build -d 'Build an image from a Dockerfile' complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l no-cache -d 'Do not use cache when building the image' complete -c docker -A -f -n '__fish_seen_subcommand_from build' -s q -l quiet -d 'Suppress the verbose output generated by the containers' complete -c docker -A -f -n '__fish_seen_subcommand_from build' -l rm -d 'Remove intermediate containers after a successful build' diff --git a/contrib/man/md/docker-build.1.md b/contrib/man/md/docker-build.1.md index b3e9a2842..b01bb9f8b 100644 --- a/contrib/man/md/docker-build.1.md +++ b/contrib/man/md/docker-build.1.md @@ -2,7 +2,7 @@ % William Henry % APRIL 2014 # NAME -docker-build - Build a container image from a Dockerfile source at PATH +docker-build - Build an image from a Dockerfile source at PATH # SYNOPSIS **docker build** [**--no-cache**[=*false*]] [**-q**|**--quiet**[=*false*]] diff --git a/contrib/man/md/docker.1.md b/contrib/man/md/docker.1.md index 0071a71c9..c73b083e6 100644 --- a/contrib/man/md/docker.1.md +++ b/contrib/man/md/docker.1.md @@ -81,7 +81,7 @@ port=[4243] or path =[/var/run/docker.sock] is omitted, default values are used. Attach to a running container **docker-build(1)** - Build a container from a Dockerfile + Build an image from a Dockerfile **docker-commit(1)** Create a new image from a container's changes diff --git a/contrib/man/old-man/docker-build.1 b/contrib/man/old-man/docker-build.1 index 6546b7be2..2d189eb0e 100644 --- a/contrib/man/old-man/docker-build.1 +++ b/contrib/man/old-man/docker-build.1 @@ -3,7 +3,7 @@ .\" .TH "DOCKER" "1" "MARCH 2014" "0.1" "Docker" .SH NAME -docker-build \- Build a container image from a Dockerfile source at PATH +docker-build \- Build an image from a Dockerfile source at PATH .SH SYNOPSIS .B docker build [\fB--no-cache\fR[=\fIfalse\fR] diff --git a/contrib/man/old-man/docker.1 b/contrib/man/old-man/docker.1 index 4a36e5baf..5a5586512 100644 --- a/contrib/man/old-man/docker.1 +++ b/contrib/man/old-man/docker.1 @@ -69,7 +69,7 @@ Print version information and quit Attach to a running container .TP .B build -Build a container from a Dockerfile +Build an image from a Dockerfile .TP .B commit Create a new image from a container's changes diff --git a/docs/sources/examples/cfengine_process_management.md b/docs/sources/examples/cfengine_process_management.md index 0c7b6a8a1..ee5ba238a 100644 --- a/docs/sources/examples/cfengine_process_management.md +++ b/docs/sources/examples/cfengine_process_management.md @@ -58,7 +58,7 @@ There are three steps: containerized CFEngine installation. 3. Start your application processes as part of the `docker run` command. -### Building the container image +### Building the image The first two steps can be done as part of a Dockerfile, as follows. @@ -87,7 +87,7 @@ The first two steps can be done as part of a Dockerfile, as follows. ENTRYPOINT ["/var/cfengine/bin/docker_processes_run.sh"] By saving this file as Dockerfile to a working directory, you can then build -your container with the docker build command, e.g. +your image with the docker build command, e.g. `docker build -t managed_image`. ### Testing the container diff --git a/docs/sources/examples/using_supervisord.md b/docs/sources/examples/using_supervisord.md index 6fc47b0c0..0f1094532 100644 --- a/docs/sources/examples/using_supervisord.md +++ b/docs/sources/examples/using_supervisord.md @@ -95,9 +95,9 @@ Here We've exposed ports 22 and 80 on the container and we're running the `/usr/bin/supervisord` binary when the container launches. -## Building our container +## Building our image -We can now build our new container. +We can now build our new image. $ sudo docker build -t /supervisord . diff --git a/docs/sources/introduction/working-with-docker.md b/docs/sources/introduction/working-with-docker.md index 1abee1ce3..aefdc586c 100644 --- a/docs/sources/introduction/working-with-docker.md +++ b/docs/sources/introduction/working-with-docker.md @@ -83,7 +83,7 @@ You will see a list of all currently available commands. Commands: attach Attach to a running container - build Build a container from a Dockerfile + build Build an image from a Dockerfile commit Create a new image from a container's changes . . . diff --git a/docs/sources/reference/api/README.md b/docs/sources/reference/api/README.md index a7b8ae1b4..ec1cbcb2c 100644 --- a/docs/sources/reference/api/README.md +++ b/docs/sources/reference/api/README.md @@ -2,7 +2,7 @@ This directory holds the authoritative specifications of APIs defined and implem * The remote API by which a docker node can be queried over HTTP * The registry API by which a docker node can download and upload - container images for storage and sharing + images for storage and sharing * The index search API by which a docker node can search the public index for images to download * The docker.io OAuth and accounts API which 3rd party services can diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index e0c5eb106..8325bc794 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -190,7 +190,7 @@ To kill the container, use `docker kill`. Usage: docker build [OPTIONS] PATH | URL | - - Build a new container image from the source code at PATH + Build a new image from the source code at PATH --force-rm=false Always remove intermediate containers, even after unsuccessful builds --no-cache=false Do not use cache when building the image diff --git a/docs/sources/use/basics.md b/docs/sources/use/basics.md index e4422034d..1b8c0a7b1 100644 --- a/docs/sources/use/basics.md +++ b/docs/sources/use/basics.md @@ -157,7 +157,7 @@ TCP and a Unix socket ## Committing (saving) a container state -Save your containers state to a container image, so the state can be +Save your containers state to an image, so the state can be re-used. When you commit your container only the differences between the image @@ -171,7 +171,7 @@ will be stored (as a diff). See which images you already have using the # List your containers $ sudo docker images -You now have a image state from which you can create new instances. +You now have an image state from which you can create new instances. Read more about [*Share Images via Repositories*]( ../workingwithrepository/#working-with-the-repository) or diff --git a/hack/RELEASE-CHECKLIST.md b/hack/RELEASE-CHECKLIST.md index 31f0adb75..583328b46 100644 --- a/hack/RELEASE-CHECKLIST.md +++ b/hack/RELEASE-CHECKLIST.md @@ -70,7 +70,7 @@ EXAMPLES: #### Builder -+ 'docker build -t FOO .' applies the tag FOO to the newly built container ++ 'docker build -t FOO .' applies the tag FOO to the newly built image #### Remote API diff --git a/hack/make.sh b/hack/make.sh index 8636756c8..e7dc09cfb 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -18,7 +18,7 @@ set -e # - The right way to call this script is to invoke "make" from # your checkout of the Docker repository. # the Makefile will do a "docker build -t docker ." and then -# "docker run hack/make.sh" in the resulting container image. +# "docker run hack/make.sh" in the resulting image. # set -o pipefail From 456ba11d89b52f946d293489b52508a2e270f1a7 Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Tue, 27 May 2014 15:51:05 -0700 Subject: [PATCH 289/400] docs/installation/google: update to use container-vm images Docker-DCO-1.1-Signed-off-by: Johan Euphrosine (github: proppy) rebased by Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/installation/google.md | 57 +++++++++++++---------------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/docs/sources/installation/google.md b/docs/sources/installation/google.md index bb8a96147..fe3d0619c 100644 --- a/docs/sources/installation/google.md +++ b/docs/sources/installation/google.md @@ -4,52 +4,45 @@ page_keywords: Docker, Docker documentation, installation, google, Google Comput # Google Cloud Platform -1. Go to [Google Cloud Console](https://cloud.google.com/console) and - create a new Cloud Project with [Compute Engine - enabled](https://developers.google.com/compute/docs/signup). -2. Download and configure the [Google Cloud SDK]( - https://developers.google.com/cloud/sdk/) to use your project - with the following commands: +## QuickStart with Container-optimized Google Compute Engine images + +1. Go to [Google Cloud Console][1] and create a new Cloud Project with + [Compute Engine enabled][2] + +2. Download and configure the [Google Cloud SDK][3] to use your + project with the following commands: ``` $ curl https://dl.google.com/dl/cloudsdk/release/install_google_cloud_sdk.bash | bash $ gcloud auth login Enter a cloud project id (or leave blank to not set): - ``` - -3. Start a new instance, select a zone close to you and the desired - instance size: - - ``` - $ gcutil addinstance docker-playground --image=backports-debian-7 - 1: europe-west1-a ... - 4: us-central1-b - >>> - 1: machineTypes/n1-standard-1 - ... - 12: machineTypes/g1-small - >>> ``` -4. Connect to the instance using SSH: +3. Start a new instance using the latest [Container-optimized image][4]: + (select a zone close to you and the desired instance size) ``` - $ gcutil ssh docker-playground - $ docker-playground:~$ + $ gcloud compute instances create docker-playground \ + --image projects/google-containers/global/images/container-vm-v20140522 \ + --zone us-central1-a \ + --machine-type f1-micro ``` -5. Install the latest Docker release and configure it to start when the - instance boots: +4. Connect to the instance using SSH: ``` - $ docker-playground:~$ curl get.docker.io | bash - $ docker-playground:~$ sudo update-rc.d docker defaults + $ gcloud compute ssh --zone us-central1-a docker-playground + ``` + ``` + docker-playground:~$ sudo docker run busybox echo 'docker on GCE \o/' + docker on GCE \o/ ``` -6. Start a new container: +Read more about [deploying Containers on Google Cloud Platform][5]. - ``` - $ docker-playground:~$ sudo docker run busybox echo 'docker on GCE \o/' - $ docker on GCE \o/ - ``` +[1]: https://cloud.google.com/console +[2]: https://developers.google.com/compute/docs/signup +[3]: https://developers.google.com/cloud/sdk +[4]: https://developers.google.com/compute/docs/containers#container-optimized_google_compute_engine_images +[5]: https://developers.google.com/compute/docs/containers From 5e3b643ce6f43d02fc7fe88eba41d583044a2efd Mon Sep 17 00:00:00 2001 From: Giuseppe Mazzotta Date: Thu, 29 May 2014 15:57:29 +0200 Subject: [PATCH 290/400] * do not consider iptables' output an error in case of xtables lock Docker-DCO-1.1-Signed-off-by: Giuseppe Mazzotta (github: gdm85) --- pkg/iptables/iptables.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/iptables/iptables.go b/pkg/iptables/iptables.go index b44c45223..e3f4d74b9 100644 --- a/pkg/iptables/iptables.go +++ b/pkg/iptables/iptables.go @@ -166,5 +166,10 @@ func Raw(args ...string) ([]byte, error) { return nil, fmt.Errorf("iptables failed: iptables %v: %s (%s)", strings.Join(args, " "), output, err) } + // ignore iptables' message about xtables lock + if strings.Contains(string(output), "waiting for it to exit") { + output = []byte("") + } + return output, err } From 75754e69f6cce80c34ebc72817ada0a807fd635a Mon Sep 17 00:00:00 2001 From: Johannes 'fish' Ziemke Date: Thu, 29 May 2014 15:50:52 +0200 Subject: [PATCH 291/400] Add ErrPrerequisites to improve misleading errors There are two cases where we can't use a graphdriver: 1) the graphdriver itself isn't supported by the system 2) the graphdriver is supported by some configuration/prerequisites are missing This introduces a new error for the 2) case and uses it when trying to run docker with btrfs backend on a non-btrfs filesystem. Docker-DCO-1.1-Signed-off-by: Johannes 'fish' Ziemke (github: discordianfish) --- daemon/graphdriver/btrfs/btrfs.go | 8 ++++++-- daemon/graphdriver/driver.go | 7 ++++--- daemon/graphdriver/graphtest/graphtest.go | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/daemon/graphdriver/btrfs/btrfs.go b/daemon/graphdriver/btrfs/btrfs.go index 614dc1ff0..06c3fd066 100644 --- a/daemon/graphdriver/btrfs/btrfs.go +++ b/daemon/graphdriver/btrfs/btrfs.go @@ -18,6 +18,10 @@ import ( "unsafe" ) +const ( + btrfsSuperMagic = 0x9123683E +) + func init() { graphdriver.Register("btrfs", Init) } @@ -30,8 +34,8 @@ func Init(home string) (graphdriver.Driver, error) { return nil, err } - if buf.Type != 0x9123683E { - return nil, graphdriver.ErrNotSupported + if buf.Type != btrfsSuperMagic { + return nil, graphdriver.ErrPrerequisites } return &Driver{ diff --git a/daemon/graphdriver/driver.go b/daemon/graphdriver/driver.go index 96f8d3ab3..a3039e030 100644 --- a/daemon/graphdriver/driver.go +++ b/daemon/graphdriver/driver.go @@ -44,7 +44,8 @@ var ( "vfs", } - ErrNotSupported = errors.New("driver not supported") + ErrNotSupported = errors.New("driver not supported") + ErrPrerequisites = errors.New("Prerequisites for driver not satisfied (wrong filesystem?)") ) func init() { @@ -78,7 +79,7 @@ func New(root string) (driver Driver, err error) { for _, name := range priority { driver, err = GetDriver(name, root) if err != nil { - if err == ErrNotSupported { + if err == ErrNotSupported || err == ErrPrerequisites { continue } return nil, err @@ -89,7 +90,7 @@ func New(root string) (driver Driver, err error) { // Check all registered drivers if no priority driver is found for _, initFunc := range drivers { if driver, err = initFunc(root); err != nil { - if err == ErrNotSupported { + if err == ErrNotSupported || err == ErrPrerequisites { continue } return nil, err diff --git a/daemon/graphdriver/graphtest/graphtest.go b/daemon/graphdriver/graphtest/graphtest.go index f8ccb2ef3..56ea8f7d4 100644 --- a/daemon/graphdriver/graphtest/graphtest.go +++ b/daemon/graphdriver/graphtest/graphtest.go @@ -31,7 +31,7 @@ func newDriver(t *testing.T, name string) *Driver { d, err := graphdriver.GetDriver(name, root) if err != nil { - if err == graphdriver.ErrNotSupported { + if err == graphdriver.ErrNotSupported || err == graphdriver.ErrPrerequisites { t.Skip("Driver %s not supported", name) } t.Fatal(err) From 72e6e5ff7edc9c054e154897a4c547d89c082293 Mon Sep 17 00:00:00 2001 From: Vishnu Kannan Date: Wed, 28 May 2014 00:01:08 +0000 Subject: [PATCH 292/400] Added a new method cgroups.GetStats() which will return a cgroups.Stats object which will contain all the available cgroup Stats. Remove old Stats interface in libcontainers cgroups package. Changed Stats to use unit64 instead of int64 to prevent integer overflow issues. Updated unit tests. Docker-DCO-1.1-Signed-off-by: Vishnu Kannan (github: vishh) --- pkg/libcontainer/cgroups/fs/apply_raw.go | 17 ++- pkg/libcontainer/cgroups/fs/blkio.go | 129 ++++++++++-------- pkg/libcontainer/cgroups/fs/blkio_test.go | 69 +++++----- pkg/libcontainer/cgroups/fs/cpu.go | 24 +++- pkg/libcontainer/cgroups/fs/cpu_test.go | 33 +++-- pkg/libcontainer/cgroups/fs/cpuacct.go | 40 +++--- pkg/libcontainer/cgroups/fs/cpuset.go | 6 +- pkg/libcontainer/cgroups/fs/devices.go | 6 +- pkg/libcontainer/cgroups/fs/freezer.go | 49 +++---- pkg/libcontainer/cgroups/fs/memory.go | 33 +++-- pkg/libcontainer/cgroups/fs/memory_test.go | 20 +-- pkg/libcontainer/cgroups/fs/perf_event.go | 4 +- .../cgroups/fs/stats_test_util.go | 73 ++++++++++ pkg/libcontainer/cgroups/fs/test_util.go | 15 -- pkg/libcontainer/cgroups/fs/utils.go | 10 +- pkg/libcontainer/cgroups/stats.go | 32 +++-- 16 files changed, 329 insertions(+), 231 deletions(-) create mode 100644 pkg/libcontainer/cgroups/fs/stats_test_util.go diff --git a/pkg/libcontainer/cgroups/fs/apply_raw.go b/pkg/libcontainer/cgroups/fs/apply_raw.go index be500781e..291d1e84b 100644 --- a/pkg/libcontainer/cgroups/fs/apply_raw.go +++ b/pkg/libcontainer/cgroups/fs/apply_raw.go @@ -26,7 +26,7 @@ var ( type subsystem interface { Set(*data) error Remove(*data) error - Stats(*data) (map[string]int64, error) + GetStats(*data, *cgroups.Stats) error } type data struct { @@ -74,7 +74,8 @@ func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) { return d, nil } -func GetStats(c *cgroups.Cgroup, subsystem string, pid int) (map[string]int64, error) { +func GetStats(c *cgroups.Cgroup) (*cgroups.Stats, error) { + stats := cgroups.NewStats() cgroupRoot, err := cgroups.FindCgroupMountpoint("cpu") if err != nil { return nil, err @@ -94,13 +95,15 @@ func GetStats(c *cgroups.Cgroup, subsystem string, pid int) (map[string]int64, e root: cgroupRoot, cgroup: cgroup, c: c, - pid: pid, } - sys, exists := subsystems[subsystem] - if !exists { - return nil, fmt.Errorf("subsystem %s does not exist", subsystem) + + for _, sys := range subsystems { + if err := sys.GetStats(d, stats); err != nil { + return nil, err + } } - return sys.Stats(d) + + return stats, nil } func GetPids(c *cgroups.Cgroup) ([]int, error) { diff --git a/pkg/libcontainer/cgroups/fs/blkio.go b/pkg/libcontainer/cgroups/fs/blkio.go index 5cbef69f5..0c7a4e7b3 100644 --- a/pkg/libcontainer/cgroups/fs/blkio.go +++ b/pkg/libcontainer/cgroups/fs/blkio.go @@ -3,7 +3,6 @@ package fs import ( "bufio" "fmt" - "io/ioutil" "os" "path/filepath" "strconv" @@ -57,65 +56,87 @@ examples: 8:0 Total 0 Total 0 */ -func (s *blkioGroup) Stats(d *data) (map[string]int64, error) { - var ( - paramData = make(map[string]int64) - params = []string{ - "io_service_bytes_recursive", - "io_serviced_recursive", - "io_queued_recursive", - } - ) - path, err := d.path("blkio") - if err != nil { - return nil, err - } - - k, v, err := s.getSectors(path) - if err != nil { - return nil, err - } - paramData[fmt.Sprintf("blkio.sectors_recursive:%s", k)] = v - - for _, param := range params { - f, err := os.Open(filepath.Join(path, fmt.Sprintf("blkio.%s", param))) - if err != nil { - return nil, err - } - defer f.Close() - - sc := bufio.NewScanner(f) - for sc.Scan() { - // format: dev type amount - fields := strings.Fields(sc.Text()) - switch len(fields) { - case 3: - v, err := strconv.ParseInt(fields[2], 10, 64) - if err != nil { - return nil, err - } - paramData[fmt.Sprintf("%s:%s:%s", param, fields[0], fields[1])] = v - case 2: - // this is the total line, skip - default: - return nil, ErrNotValidFormat - } - } - } - return paramData, nil +func splitBlkioStatLine(r rune) bool { + return r == ' ' || r == ':' } -func (s *blkioGroup) getSectors(path string) (string, int64, error) { - f, err := os.Open(filepath.Join(path, "blkio.sectors_recursive")) +func getBlkioStat(path string) ([]cgroups.BlkioStatEntry, error) { + var blkioStats []cgroups.BlkioStatEntry + f, err := os.Open(path) if err != nil { - return "", 0, err + return nil, err } defer f.Close() - data, err := ioutil.ReadAll(f) - if err != nil { - return "", 0, err + sc := bufio.NewScanner(f) + for sc.Scan() { + // format: dev type amount + fields := strings.FieldsFunc(sc.Text(), splitBlkioStatLine) + if len(fields) < 3 { + if len(fields) == 2 && fields[0] == "Total" { + // skip total line + continue + } else { + return nil, fmt.Errorf("Invalid line found while parsing %s: %s", path, sc.Text()) + } + } + + v, err := strconv.ParseUint(fields[0], 10, 64) + if err != nil { + return nil, err + } + major := v + + v, err = strconv.ParseUint(fields[1], 10, 64) + if err != nil { + return nil, err + } + minor := v + + op := "" + valueField := 2 + if len(fields) == 4 { + op = fields[2] + valueField = 3 + } + v, err = strconv.ParseUint(fields[valueField], 10, 64) + if err != nil { + return nil, err + } + blkioStats = append(blkioStats, cgroups.BlkioStatEntry{Major: major, Minor: minor, Op: op, Value: v}) } - return getCgroupParamKeyValue(string(data)) + + return blkioStats, nil +} + +func (s *blkioGroup) GetStats(d *data, stats *cgroups.Stats) error { + var blkioStats []cgroups.BlkioStatEntry + var err error + path, err := d.path("blkio") + if err != nil { + return err + } + + if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.sectors_recursive")); err != nil { + return err + } + stats.BlkioStats.SectorsRecursive = blkioStats + + if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_service_bytes_recursive")); err != nil { + return err + } + stats.BlkioStats.IoServiceBytesRecursive = blkioStats + + if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_serviced_recursive")); err != nil { + return err + } + stats.BlkioStats.IoServicedRecursive = blkioStats + + if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_queued_recursive")); err != nil { + return err + } + stats.BlkioStats.IoQueuedRecursive = blkioStats + + return nil } diff --git a/pkg/libcontainer/cgroups/fs/blkio_test.go b/pkg/libcontainer/cgroups/fs/blkio_test.go index d0244ad71..d91a6479a 100644 --- a/pkg/libcontainer/cgroups/fs/blkio_test.go +++ b/pkg/libcontainer/cgroups/fs/blkio_test.go @@ -2,14 +2,16 @@ package fs import ( "testing" + + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) const ( sectorsRecursiveContents = `8:0 1024` serviceBytesRecursiveContents = `8:0 Read 100 -8:0 Write 400 -8:0 Sync 200 -8:0 Async 300 +8:0 Write 200 +8:0 Sync 300 +8:0 Async 500 8:0 Total 500 Total 500` servicedRecursiveContents = `8:0 Read 10 @@ -26,6 +28,12 @@ Total 50` Total 5` ) +var actualStats = *cgroups.NewStats() + +func appendBlkioStatEntry(blkioStatEntries *[]cgroups.BlkioStatEntry, major, minor, value uint64, op string) { + *blkioStatEntries = append(*blkioStatEntries, cgroups.BlkioStatEntry{Major: major, Minor: minor, Value: value, Op: op}) +} + func TestBlkioStats(t *testing.T) { helper := NewCgroupTestUtil("blkio", t) defer helper.cleanup() @@ -37,37 +45,34 @@ func TestBlkioStats(t *testing.T) { }) blkio := &blkioGroup{} - stats, err := blkio.Stats(helper.CgroupData) + err := blkio.GetStats(helper.CgroupData, &actualStats) if err != nil { t.Fatal(err) } // Verify expected stats. - expectedStats := map[string]int64{ - "blkio.sectors_recursive:8:0": 1024, + expectedStats := cgroups.BlkioStats{} + appendBlkioStatEntry(&expectedStats.SectorsRecursive, 8, 0, 1024, "") - // Serviced bytes. - "io_service_bytes_recursive:8:0:Read": 100, - "io_service_bytes_recursive:8:0:Write": 400, - "io_service_bytes_recursive:8:0:Sync": 200, - "io_service_bytes_recursive:8:0:Async": 300, - "io_service_bytes_recursive:8:0:Total": 500, + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 100, "Read") + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 200, "Write") + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 300, "Sync") + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 500, "Async") + appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 500, "Total") - // Serviced requests. - "io_serviced_recursive:8:0:Read": 10, - "io_serviced_recursive:8:0:Write": 40, - "io_serviced_recursive:8:0:Sync": 20, - "io_serviced_recursive:8:0:Async": 30, - "io_serviced_recursive:8:0:Total": 50, + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 10, "Read") + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 40, "Write") + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 20, "Sync") + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 30, "Async") + appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 50, "Total") - // Queued requests. - "io_queued_recursive:8:0:Read": 1, - "io_queued_recursive:8:0:Write": 4, - "io_queued_recursive:8:0:Sync": 2, - "io_queued_recursive:8:0:Async": 3, - "io_queued_recursive:8:0:Total": 5, - } - expectStats(t, expectedStats, stats) + appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 1, "Read") + appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 4, "Write") + appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 2, "Sync") + appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 3, "Async") + appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 5, "Total") + + expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats) } func TestBlkioStatsNoSectorsFile(t *testing.T) { @@ -80,7 +85,7 @@ func TestBlkioStatsNoSectorsFile(t *testing.T) { }) blkio := &blkioGroup{} - _, err := blkio.Stats(helper.CgroupData) + err := blkio.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected to fail, but did not") } @@ -96,7 +101,7 @@ func TestBlkioStatsNoServiceBytesFile(t *testing.T) { }) blkio := &blkioGroup{} - _, err := blkio.Stats(helper.CgroupData) + err := blkio.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected to fail, but did not") } @@ -112,7 +117,7 @@ func TestBlkioStatsNoServicedFile(t *testing.T) { }) blkio := &blkioGroup{} - _, err := blkio.Stats(helper.CgroupData) + err := blkio.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected to fail, but did not") } @@ -128,7 +133,7 @@ func TestBlkioStatsNoQueuedFile(t *testing.T) { }) blkio := &blkioGroup{} - _, err := blkio.Stats(helper.CgroupData) + err := blkio.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected to fail, but did not") } @@ -145,7 +150,7 @@ func TestBlkioStatsUnexpectedNumberOfFields(t *testing.T) { }) blkio := &blkioGroup{} - _, err := blkio.Stats(helper.CgroupData) + err := blkio.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected to fail, but did not") } @@ -162,7 +167,7 @@ func TestBlkioStatsUnexpectedFieldType(t *testing.T) { }) blkio := &blkioGroup{} - _, err := blkio.Stats(helper.CgroupData) + err := blkio.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected to fail, but did not") } diff --git a/pkg/libcontainer/cgroups/fs/cpu.go b/pkg/libcontainer/cgroups/fs/cpu.go index ad3078b3b..1c266f4a1 100644 --- a/pkg/libcontainer/cgroups/fs/cpu.go +++ b/pkg/libcontainer/cgroups/fs/cpu.go @@ -5,6 +5,8 @@ import ( "os" "path/filepath" "strconv" + + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) type cpuGroup struct { @@ -39,16 +41,15 @@ func (s *cpuGroup) Remove(d *data) error { return removePath(d.path("cpu")) } -func (s *cpuGroup) Stats(d *data) (map[string]int64, error) { - paramData := make(map[string]int64) +func (s *cpuGroup) GetStats(d *data, stats *cgroups.Stats) error { path, err := d.path("cpu") if err != nil { - return nil, err + return err } f, err := os.Open(filepath.Join(path, "cpu.stat")) if err != nil { - return nil, err + return err } defer f.Close() @@ -56,9 +57,18 @@ func (s *cpuGroup) Stats(d *data) (map[string]int64, error) { for sc.Scan() { t, v, err := getCgroupParamKeyValue(sc.Text()) if err != nil { - return nil, err + return err + } + switch t { + case "nr_periods": + stats.CpuStats.ThrottlingData.Periods = v + + case "nr_throttled": + stats.CpuStats.ThrottlingData.ThrottledPeriods = v + + case "throttled_time": + stats.CpuStats.ThrottlingData.ThrottledTime = v } - paramData[t] = v } - return paramData, nil + return nil } diff --git a/pkg/libcontainer/cgroups/fs/cpu_test.go b/pkg/libcontainer/cgroups/fs/cpu_test.go index cacf2f4ce..ad0674083 100644 --- a/pkg/libcontainer/cgroups/fs/cpu_test.go +++ b/pkg/libcontainer/cgroups/fs/cpu_test.go @@ -1,31 +1,40 @@ package fs import ( + "fmt" "testing" + + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) func TestCpuStats(t *testing.T) { helper := NewCgroupTestUtil("cpu", t) defer helper.cleanup() - cpuStatContent := `nr_periods 2000 - nr_throttled 200 - throttled_time 42424242424` + + const ( + kNrPeriods = 2000 + kNrThrottled = 200 + kThrottledTime = uint64(18446744073709551615) + ) + + cpuStatContent := fmt.Sprintf("nr_periods %d\n nr_throttled %d\n throttled_time %d\n", + kNrPeriods, kNrThrottled, kThrottledTime) helper.writeFileContents(map[string]string{ "cpu.stat": cpuStatContent, }) cpu := &cpuGroup{} - stats, err := cpu.Stats(helper.CgroupData) + err := cpu.GetStats(helper.CgroupData, &actualStats) if err != nil { t.Fatal(err) } - expected_stats := map[string]int64{ - "nr_periods": 2000, - "nr_throttled": 200, - "throttled_time": 42424242424, - } - expectStats(t, expected_stats, stats) + expectedStats := cgroups.ThrottlingData{ + Periods: kNrPeriods, + ThrottledPeriods: kNrThrottled, + ThrottledTime: kThrottledTime} + + expectThrottlingDataEquals(t, expectedStats, actualStats.CpuStats.ThrottlingData) } func TestNoCpuStatFile(t *testing.T) { @@ -33,7 +42,7 @@ func TestNoCpuStatFile(t *testing.T) { defer helper.cleanup() cpu := &cpuGroup{} - _, err := cpu.Stats(helper.CgroupData) + err := cpu.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected to fail, but did not.") } @@ -50,7 +59,7 @@ func TestInvalidCpuStat(t *testing.T) { }) cpu := &cpuGroup{} - _, err := cpu.Stats(helper.CgroupData) + err := cpu.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected failed stat parsing.") } diff --git a/pkg/libcontainer/cgroups/fs/cpuacct.go b/pkg/libcontainer/cgroups/fs/cpuacct.go index c52049f3e..36a50b8d4 100644 --- a/pkg/libcontainer/cgroups/fs/cpuacct.go +++ b/pkg/libcontainer/cgroups/fs/cpuacct.go @@ -15,8 +15,8 @@ import ( ) var ( - cpuCount = int64(runtime.NumCPU()) - clockTicks = int64(system.GetClockTicks()) + cpuCount = uint64(runtime.NumCPU()) + clockTicks = uint64(system.GetClockTicks()) ) type cpuacctGroup struct { @@ -34,34 +34,33 @@ func (s *cpuacctGroup) Remove(d *data) error { return removePath(d.path("cpuacct")) } -func (s *cpuacctGroup) Stats(d *data) (map[string]int64, error) { +func (s *cpuacctGroup) GetStats(d *data, stats *cgroups.Stats) error { var ( - startCpu, lastCpu, startSystem, lastSystem, startUsage, lastUsage int64 - percentage int64 - paramData = make(map[string]int64) + startCpu, lastCpu, startSystem, lastSystem, startUsage, lastUsage uint64 + percentage uint64 ) path, err := d.path("cpuacct") if startCpu, err = s.getCpuUsage(d, path); err != nil { - return nil, err + return err } if startSystem, err = s.getSystemCpuUsage(d); err != nil { - return nil, err + return err } startUsageTime := time.Now() if startUsage, err = getCgroupParamInt(path, "cpuacct.usage"); err != nil { - return nil, err + return err } // sample for 100ms time.Sleep(100 * time.Millisecond) if lastCpu, err = s.getCpuUsage(d, path); err != nil { - return nil, err + return err } if lastSystem, err = s.getSystemCpuUsage(d); err != nil { - return nil, err + return err } usageSampleDuration := time.Since(startUsageTime) if lastUsage, err = getCgroupParamInt(path, "cpuacct.usage"); err != nil { - return nil, err + return err } var ( @@ -74,15 +73,14 @@ func (s *cpuacctGroup) Stats(d *data) (map[string]int64, error) { } // NOTE: a percentage over 100% is valid for POSIX because that means the // processes is using multiple cores - paramData["percentage"] = percentage - + stats.CpuStats.CpuUsage.PercentUsage = percentage // Delta usage is in nanoseconds of CPU time so get the usage (in cores) over the sample time. - paramData["usage"] = deltaUsage / usageSampleDuration.Nanoseconds() - return paramData, nil + stats.CpuStats.CpuUsage.CurrentUsage = deltaUsage / uint64(usageSampleDuration.Nanoseconds()) + return nil } // TODO(vmarmol): Use cgroups stats. -func (s *cpuacctGroup) getSystemCpuUsage(d *data) (int64, error) { +func (s *cpuacctGroup) getSystemCpuUsage(d *data) (uint64, error) { f, err := os.Open("/proc/stat") if err != nil { @@ -99,9 +97,9 @@ func (s *cpuacctGroup) getSystemCpuUsage(d *data) (int64, error) { return 0, fmt.Errorf("invalid number of cpu fields") } - var total int64 + var total uint64 for _, i := range parts[1:8] { - v, err := strconv.ParseInt(i, 10, 64) + v, err := strconv.ParseUint(i, 10, 64) if err != nil { return 0.0, fmt.Errorf("Unable to convert value %s to int: %s", i, err) } @@ -115,8 +113,8 @@ func (s *cpuacctGroup) getSystemCpuUsage(d *data) (int64, error) { return 0, fmt.Errorf("invalid stat format") } -func (s *cpuacctGroup) getCpuUsage(d *data, path string) (int64, error) { - cpuTotal := int64(0) +func (s *cpuacctGroup) getCpuUsage(d *data, path string) (uint64, error) { + cpuTotal := uint64(0) f, err := os.Open(filepath.Join(path, "cpuacct.stat")) if err != nil { return 0, err diff --git a/pkg/libcontainer/cgroups/fs/cpuset.go b/pkg/libcontainer/cgroups/fs/cpuset.go index af2dd528d..c0b03c559 100644 --- a/pkg/libcontainer/cgroups/fs/cpuset.go +++ b/pkg/libcontainer/cgroups/fs/cpuset.go @@ -6,6 +6,8 @@ import ( "os" "path/filepath" "strconv" + + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) type cpusetGroup struct { @@ -38,8 +40,8 @@ func (s *cpusetGroup) Remove(d *data) error { return removePath(d.path("cpuset")) } -func (s *cpusetGroup) Stats(d *data) (map[string]int64, error) { - return nil, ErrNotSupportStat +func (s *cpusetGroup) GetStats(d *data, stats *cgroups.Stats) error { + return nil } func (s *cpusetGroup) getSubsystemSettings(parent string) (cpus []byte, mems []byte, err error) { diff --git a/pkg/libcontainer/cgroups/fs/devices.go b/pkg/libcontainer/cgroups/fs/devices.go index 00fea608f..569cbbf0d 100644 --- a/pkg/libcontainer/cgroups/fs/devices.go +++ b/pkg/libcontainer/cgroups/fs/devices.go @@ -1,5 +1,7 @@ package fs +import "github.com/dotcloud/docker/pkg/libcontainer/cgroups" + type devicesGroup struct { } @@ -55,6 +57,6 @@ func (s *devicesGroup) Remove(d *data) error { return removePath(d.path("devices")) } -func (s *devicesGroup) Stats(d *data) (map[string]int64, error) { - return nil, ErrNotSupportStat +func (s *devicesGroup) GetStats(d *data, stats *cgroups.Stats) error { + return nil } diff --git a/pkg/libcontainer/cgroups/fs/freezer.go b/pkg/libcontainer/cgroups/fs/freezer.go index 0738ec1f0..a9a27ef5a 100644 --- a/pkg/libcontainer/cgroups/fs/freezer.go +++ b/pkg/libcontainer/cgroups/fs/freezer.go @@ -1,11 +1,8 @@ package fs import ( - "fmt" "io/ioutil" - "os" "path/filepath" - "strconv" "strings" "github.com/dotcloud/docker/pkg/libcontainer/cgroups" @@ -35,39 +32,25 @@ func (s *freezerGroup) Remove(d *data) error { return removePath(d.path("freezer")) } -func (s *freezerGroup) Stats(d *data) (map[string]int64, error) { - var ( - paramData = make(map[string]int64) - params = []string{ - "parent_freezing", - "self_freezing", - // comment out right now because this is string "state", - } - ) +func getFreezerFileData(path string) (string, error) { + data, err := ioutil.ReadFile(path) + return strings.TrimSuffix(string(data), "\n"), err +} +func (s *freezerGroup) GetStats(d *data, stats *cgroups.Stats) error { path, err := d.path("freezer") if err != nil { - return nil, err + return err } - - // TODO(vmarmol): This currently outputs nothing since the output is a string, fix. - for _, param := range params { - f, err := os.Open(filepath.Join(path, fmt.Sprintf("freezer.%s", param))) - if err != nil { - return nil, err - } - defer f.Close() - - data, err := ioutil.ReadAll(f) - if err != nil { - return nil, err - } - - v, err := strconv.ParseInt(strings.TrimSuffix(string(data), "\n"), 10, 64) - if err != nil { - return nil, err - } - paramData[param] = v + var data string + if data, err = getFreezerFileData(filepath.Join(path, "freezer.parent_freezing")); err != nil { + return err } - return paramData, nil + stats.FreezerStats.ParentState = data + if data, err = getFreezerFileData(filepath.Join(path, "freezer.self_freezing")); err != nil { + return err + } + stats.FreezerStats.SelfState = data + + return nil } diff --git a/pkg/libcontainer/cgroups/fs/memory.go b/pkg/libcontainer/cgroups/fs/memory.go index 9964f8376..f202b1600 100644 --- a/pkg/libcontainer/cgroups/fs/memory.go +++ b/pkg/libcontainer/cgroups/fs/memory.go @@ -2,10 +2,11 @@ package fs import ( "bufio" - "fmt" "os" "path/filepath" "strconv" + + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) type memoryGroup struct { @@ -50,17 +51,16 @@ func (s *memoryGroup) Remove(d *data) error { return removePath(d.path("memory")) } -func (s *memoryGroup) Stats(d *data) (map[string]int64, error) { - paramData := make(map[string]int64) +func (s *memoryGroup) GetStats(d *data, stats *cgroups.Stats) error { path, err := d.path("memory") if err != nil { - return nil, err + return err } // Set stats from memory.stat. statsFile, err := os.Open(filepath.Join(path, "memory.stat")) if err != nil { - return nil, err + return err } defer statsFile.Close() @@ -68,23 +68,22 @@ func (s *memoryGroup) Stats(d *data) (map[string]int64, error) { for sc.Scan() { t, v, err := getCgroupParamKeyValue(sc.Text()) if err != nil { - return nil, err + return err } - paramData[t] = v + stats.MemoryStats.Stats[t] = v } // Set memory usage and max historical usage. - params := []string{ - "usage_in_bytes", - "max_usage_in_bytes", + value, err := getCgroupParamInt(path, "memory.usage_in_bytes") + if err != nil { + return err } - for _, param := range params { - value, err := getCgroupParamInt(path, fmt.Sprintf("memory.%s", param)) - if err != nil { - return nil, err - } - paramData[param] = value + stats.MemoryStats.Usage = value + value, err = getCgroupParamInt(path, "memory.max_usage_in_bytes") + if err != nil { + return err } + stats.MemoryStats.MaxUsage = value - return paramData, nil + return nil } diff --git a/pkg/libcontainer/cgroups/fs/memory_test.go b/pkg/libcontainer/cgroups/fs/memory_test.go index 190d437b1..e7d201871 100644 --- a/pkg/libcontainer/cgroups/fs/memory_test.go +++ b/pkg/libcontainer/cgroups/fs/memory_test.go @@ -2,6 +2,8 @@ package fs import ( "testing" + + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) const ( @@ -21,12 +23,12 @@ func TestMemoryStats(t *testing.T) { }) memory := &memoryGroup{} - stats, err := memory.Stats(helper.CgroupData) + err := memory.GetStats(helper.CgroupData, &actualStats) if err != nil { t.Fatal(err) } - expectedStats := map[string]int64{"cache": 512, "rss": 1024, "usage_in_bytes": 2048, "max_usage_in_bytes": 4096} - expectStats(t, expectedStats, stats) + expectedStats := cgroups.MemoryStats{Usage: 2048, MaxUsage: 4096, Stats: map[string]uint64{"cache": 512, "rss": 1024}} + expectMemoryStatEquals(t, expectedStats, actualStats.MemoryStats) } func TestMemoryStatsNoStatFile(t *testing.T) { @@ -38,7 +40,7 @@ func TestMemoryStatsNoStatFile(t *testing.T) { }) memory := &memoryGroup{} - _, err := memory.Stats(helper.CgroupData) + err := memory.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected failure") } @@ -53,7 +55,7 @@ func TestMemoryStatsNoUsageFile(t *testing.T) { }) memory := &memoryGroup{} - _, err := memory.Stats(helper.CgroupData) + err := memory.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected failure") } @@ -68,7 +70,7 @@ func TestMemoryStatsNoMaxUsageFile(t *testing.T) { }) memory := &memoryGroup{} - _, err := memory.Stats(helper.CgroupData) + err := memory.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected failure") } @@ -84,7 +86,7 @@ func TestMemoryStatsBadStatFile(t *testing.T) { }) memory := &memoryGroup{} - _, err := memory.Stats(helper.CgroupData) + err := memory.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected failure") } @@ -100,7 +102,7 @@ func TestMemoryStatsBadUsageFile(t *testing.T) { }) memory := &memoryGroup{} - _, err := memory.Stats(helper.CgroupData) + err := memory.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected failure") } @@ -116,7 +118,7 @@ func TestMemoryStatsBadMaxUsageFile(t *testing.T) { }) memory := &memoryGroup{} - _, err := memory.Stats(helper.CgroupData) + err := memory.GetStats(helper.CgroupData, &actualStats) if err == nil { t.Fatal("Expected failure") } diff --git a/pkg/libcontainer/cgroups/fs/perf_event.go b/pkg/libcontainer/cgroups/fs/perf_event.go index 1cf1aeef1..1eb4df11b 100644 --- a/pkg/libcontainer/cgroups/fs/perf_event.go +++ b/pkg/libcontainer/cgroups/fs/perf_event.go @@ -19,6 +19,6 @@ func (s *perfEventGroup) Remove(d *data) error { return removePath(d.path("perf_event")) } -func (s *perfEventGroup) Stats(d *data) (map[string]int64, error) { - return nil, ErrNotSupportStat +func (s *perfEventGroup) GetStats(d *data, stats *cgroups.Stats) error { + return nil } diff --git a/pkg/libcontainer/cgroups/fs/stats_test_util.go b/pkg/libcontainer/cgroups/fs/stats_test_util.go new file mode 100644 index 000000000..bebd0cb3e --- /dev/null +++ b/pkg/libcontainer/cgroups/fs/stats_test_util.go @@ -0,0 +1,73 @@ +package fs + +import ( + "fmt" + "log" + "testing" + + "github.com/dotcloud/docker/pkg/libcontainer/cgroups" +) + +func blkioStatEntryEquals(expected, actual []cgroups.BlkioStatEntry) error { + if len(expected) != len(actual) { + return fmt.Errorf("blkioStatEntries length do not match") + } + for i, expValue := range expected { + actValue := actual[i] + if expValue != actValue { + return fmt.Errorf("Expected blkio stat entry %v but found %v", expValue, actValue) + } + } + return nil +} + +func expectBlkioStatsEquals(t *testing.T, expected, actual cgroups.BlkioStats) { + if err := blkioStatEntryEquals(expected.IoServiceBytesRecursive, actual.IoServiceBytesRecursive); err != nil { + log.Printf("blkio IoServiceBytesRecursive do not match - %s\n", err) + t.Fail() + } + + if err := blkioStatEntryEquals(expected.IoServicedRecursive, actual.IoServicedRecursive); err != nil { + log.Printf("blkio IoServicedRecursive do not match - %s\n", err) + t.Fail() + } + + if err := blkioStatEntryEquals(expected.IoQueuedRecursive, actual.IoQueuedRecursive); err != nil { + log.Printf("blkio IoQueuedRecursive do not match - %s\n", err) + t.Fail() + } + + if err := blkioStatEntryEquals(expected.SectorsRecursive, actual.SectorsRecursive); err != nil { + log.Printf("blkio SectorsRecursive do not match - %s\n", err) + t.Fail() + } +} + +func expectThrottlingDataEquals(t *testing.T, expected, actual cgroups.ThrottlingData) { + if expected != actual { + log.Printf("Expected throttling data %v but found %v\n", expected, actual) + t.Fail() + } +} + +func expectMemoryStatEquals(t *testing.T, expected, actual cgroups.MemoryStats) { + if expected.Usage != actual.Usage { + log.Printf("Expected memory usage %d but found %d\n", expected.Usage, actual.Usage) + t.Fail() + } + if expected.MaxUsage != actual.MaxUsage { + log.Printf("Expected memory max usage %d but found %d\n", expected.MaxUsage, actual.MaxUsage) + t.Fail() + } + for key, expValue := range expected.Stats { + actValue, ok := actual.Stats[key] + if !ok { + log.Printf("Expected memory stat key %s not found\n", key) + t.Fail() + } + if expValue != actValue { + log.Printf("Expected memory stat value %d but found %d\n", expValue, actValue) + t.Fail() + } + } +} diff --git a/pkg/libcontainer/cgroups/fs/test_util.go b/pkg/libcontainer/cgroups/fs/test_util.go index 333386c5d..548870a8a 100644 --- a/pkg/libcontainer/cgroups/fs/test_util.go +++ b/pkg/libcontainer/cgroups/fs/test_util.go @@ -8,7 +8,6 @@ package fs import ( "fmt" "io/ioutil" - "log" "os" "testing" ) @@ -59,17 +58,3 @@ func (c *cgroupTestUtil) writeFileContents(fileContents map[string]string) { } } } - -// Expect the specified stats. -func expectStats(t *testing.T, expected, actual map[string]int64) { - for stat, expectedValue := range expected { - actualValue, ok := actual[stat] - if !ok { - log.Printf("Expected stat %s to exist: %s", stat, actual) - t.Fail() - } else if actualValue != expectedValue { - log.Printf("Expected stats %s to have value %f but had %f instead", stat, expectedValue, actualValue) - t.Fail() - } - } -} diff --git a/pkg/libcontainer/cgroups/fs/utils.go b/pkg/libcontainer/cgroups/fs/utils.go index 7213b5d6a..ff0586345 100644 --- a/pkg/libcontainer/cgroups/fs/utils.go +++ b/pkg/libcontainer/cgroups/fs/utils.go @@ -16,11 +16,11 @@ var ( // Parses a cgroup param and returns as name, value // i.e. "io_service_bytes 1234" will return as io_service_bytes, 1234 -func getCgroupParamKeyValue(t string) (string, int64, error) { +func getCgroupParamKeyValue(t string) (string, uint64, error) { parts := strings.Fields(t) switch len(parts) { case 2: - value, err := strconv.ParseInt(parts[1], 10, 64) + value, err := strconv.ParseUint(parts[1], 10, 64) if err != nil { return "", 0, fmt.Errorf("Unable to convert param value to int: %s", err) } @@ -31,10 +31,10 @@ func getCgroupParamKeyValue(t string) (string, int64, error) { } // Gets a single int64 value from the specified cgroup file. -func getCgroupParamInt(cgroupPath, cgroupFile string) (int64, error) { +func getCgroupParamInt(cgroupPath, cgroupFile string) (uint64, error) { contents, err := ioutil.ReadFile(filepath.Join(cgroupPath, cgroupFile)) if err != nil { - return -1, err + return 0, err } - return strconv.ParseInt(strings.TrimSpace(string(contents)), 10, 64) + return strconv.ParseUint(strings.TrimSpace(string(contents)), 10, 64) } diff --git a/pkg/libcontainer/cgroups/stats.go b/pkg/libcontainer/cgroups/stats.go index fbcd5dd23..7918d78fd 100644 --- a/pkg/libcontainer/cgroups/stats.go +++ b/pkg/libcontainer/cgroups/stats.go @@ -2,18 +2,18 @@ package cgroups type ThrottlingData struct { // Number of periods with throttling active - Periods int64 `json:"periods,omitempty"` + Periods uint64 `json:"periods,omitempty"` // Number of periods when the container hit its throttling limit. - ThrottledPeriods int64 `json:"throttled_periods,omitempty"` + ThrottledPeriods uint64 `json:"throttled_periods,omitempty"` // Aggregate time the container was throttled for in nanoseconds. - ThrottledTime int64 `json:"throttled_time,omitempty"` + ThrottledTime uint64 `json:"throttled_time,omitempty"` } type CpuUsage struct { // percentage of available CPUs currently being used. - PercentUsage int64 `json:"percent_usage,omitempty"` + PercentUsage uint64 `json:"percent_usage,omitempty"` // nanoseconds of cpu time consumed over the last 100 ms. - CurrentUsage int64 `json:"current_usage,omitempty"` + CurrentUsage uint64 `json:"current_usage,omitempty"` } type CpuStats struct { @@ -23,26 +23,27 @@ type CpuStats struct { type MemoryStats struct { // current res_counter usage for memory - Usage int64 `json:"usage,omitempty"` + Usage uint64 `json:"usage,omitempty"` // maximum usage ever recorded. - MaxUsage int64 `json:"max_usage,omitempty"` + MaxUsage uint64 `json:"max_usage,omitempty"` // TODO(vishh): Export these as stronger types. // all the stats exported via memory.stat. - Stats map[string]int64 `json:"stats,omitempty"` + Stats map[string]uint64 `json:"stats,omitempty"` } type BlkioStatEntry struct { - Major int64 `json:"major,omitempty"` - Minor int64 `json:"minor,omitempty"` + Major uint64 `json:"major,omitempty"` + Minor uint64 `json:"minor,omitempty"` Op string `json:"op,omitempty"` - Value int64 `json:"value,omitempty"` + Value uint64 `json:"value,omitempty"` } -type BlockioStats struct { +type BlkioStats struct { // number of bytes tranferred to and from the block device IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive,omitempty"` IoServicedRecursive []BlkioStatEntry `json:"io_serviced_recusrive,omitempty"` IoQueuedRecursive []BlkioStatEntry `json:"io_queue_recursive,omitempty"` + SectorsRecursive []BlkioStatEntry `json:"sectors_recursive,omitempty"` } // TODO(Vishh): Remove freezer from stats since it does not logically belong in stats. @@ -54,6 +55,11 @@ type FreezerStats struct { type Stats struct { CpuStats CpuStats `json:"cpu_stats,omitempty"` MemoryStats MemoryStats `json:"memory_stats,omitempty"` - BlockioStats BlockioStats `json:"blockio_stats,omitempty"` + BlkioStats BlkioStats `json:"blkio_stats,omitempty"` FreezerStats FreezerStats `json:"freezer_stats,omitempty"` } + +func NewStats() *Stats { + memoryStats := MemoryStats{Stats: make(map[string]uint64)} + return &Stats{MemoryStats: memoryStats} +} From e8a87120d459dfeffee5474c3253e787d38b2ac0 Mon Sep 17 00:00:00 2001 From: unclejack Date: Thu, 29 May 2014 22:55:59 +0300 Subject: [PATCH 293/400] block aufs on incompatible file systems Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- daemon/graphdriver/aufs/aufs.go | 27 ++++++++++++++++++++++----- daemon/graphdriver/driver.go | 9 +++++---- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/daemon/graphdriver/aufs/aufs.go b/daemon/graphdriver/aufs/aufs.go index 2b7aa1b68..a8d728bbb 100644 --- a/daemon/graphdriver/aufs/aufs.go +++ b/daemon/graphdriver/aufs/aufs.go @@ -23,20 +23,23 @@ package aufs import ( "bufio" "fmt" - "github.com/dotcloud/docker/archive" - "github.com/dotcloud/docker/daemon/graphdriver" - "github.com/dotcloud/docker/pkg/label" - mountpk "github.com/dotcloud/docker/pkg/mount" - "github.com/dotcloud/docker/utils" "os" "os/exec" "path" "strings" "sync" + "syscall" + + "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/daemon/graphdriver" + "github.com/dotcloud/docker/pkg/label" + mountpk "github.com/dotcloud/docker/pkg/mount" + "github.com/dotcloud/docker/utils" ) var ( ErrAufsNotSupported = fmt.Errorf("AUFS was not found in /proc/filesystems") + IncompatibleFSMagic = []int64{0x9123683E /*btrfs*/, 0x61756673 /*AUFS*/} ) func init() { @@ -56,6 +59,20 @@ func Init(root string) (graphdriver.Driver, error) { if err := supportsAufs(); err != nil { return nil, graphdriver.ErrNotSupported } + + rootdir := path.Dir(root) + + var buf syscall.Statfs_t + if err := syscall.Statfs(rootdir, &buf); err != nil { + return nil, fmt.Errorf("Couldn't stat the root directory: %s", err) + } + + for _, magic := range IncompatibleFSMagic { + if int64(buf.Type) == magic { + return nil, graphdriver.ErrIncompatibleFS + } + } + paths := []string{ "mnt", "diff", diff --git a/daemon/graphdriver/driver.go b/daemon/graphdriver/driver.go index a3039e030..33965ccae 100644 --- a/daemon/graphdriver/driver.go +++ b/daemon/graphdriver/driver.go @@ -44,8 +44,9 @@ var ( "vfs", } - ErrNotSupported = errors.New("driver not supported") - ErrPrerequisites = errors.New("Prerequisites for driver not satisfied (wrong filesystem?)") + ErrNotSupported = errors.New("driver not supported") + ErrPrerequisites = errors.New("prerequisites for driver not satisfied (wrong filesystem?)") + ErrIncompatibleFS = fmt.Errorf("backing file system is unsupported for this graph driver") ) func init() { @@ -79,7 +80,7 @@ func New(root string) (driver Driver, err error) { for _, name := range priority { driver, err = GetDriver(name, root) if err != nil { - if err == ErrNotSupported || err == ErrPrerequisites { + if err == ErrNotSupported || err == ErrPrerequisites || err == ErrIncompatibleFS { continue } return nil, err @@ -90,7 +91,7 @@ func New(root string) (driver Driver, err error) { // Check all registered drivers if no priority driver is found for _, initFunc := range drivers { if driver, err = initFunc(root); err != nil { - if err == ErrNotSupported || err == ErrPrerequisites { + if err == ErrNotSupported || err == ErrPrerequisites || err == ErrIncompatibleFS { continue } return nil, err From 23dd221e5250398d4120a1f3d1bcb591923f4892 Mon Sep 17 00:00:00 2001 From: SvenDowideit Date: Wed, 28 May 2014 14:42:19 +1000 Subject: [PATCH 294/400] sync the initial docs from b2d Docker-DCO-1.1-Signed-off-by: SvenDowideit (github: SvenDowideit) --- docs/sources/installation/mac.md | 26 ++++++++++++++++++++++++++ docs/sources/installation/windows.md | 26 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/docs/sources/installation/mac.md b/docs/sources/installation/mac.md index 9b8acc70e..ef91081a5 100644 --- a/docs/sources/installation/mac.md +++ b/docs/sources/installation/mac.md @@ -78,5 +78,31 @@ $ ./boot2docker Usage: ./boot2docker [] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|delete|download|version} [] ``` +## Container port redirection + +The latest version of `boot2docker` sets up two network adaptors: one using NAT +to allow the VM to download images and files from the Internet, and one host only +network adaptor to which the container's ports will be exposed on. + +If you run a container with an exposed port: + +``` + docker run --rm -i -t -p 80:80 apache +``` + +Then you should be able to access that Apache server using the IP address reported +to you using: + +``` + boot2docker ssh ip addr show dev eth1 +``` + +Typically, it is 192.168.59.103, but at this point it can change. + +If you want to share container ports with other computers on your LAN, you will +need to set up [NAT adaptor based port forwarding]( +https://github.com/boot2docker/boot2docker/blob/master/doc/WORKAROUNDS.md) + + For further information or to report issues, please see the [Boot2Docker site](http://boot2docker.io). diff --git a/docs/sources/installation/windows.md b/docs/sources/installation/windows.md index 2980cad14..a73611429 100644 --- a/docs/sources/installation/windows.md +++ b/docs/sources/installation/windows.md @@ -69,5 +69,31 @@ $ ./boot2docker Usage: ./boot2docker [] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|delete|download|version} [] ``` +## Container port redirection + +The latest version of `boot2docker` sets up two network adaptors: one using NAT +to allow the VM to download images and files from the Internet, and one host only +network adaptor to which the container's ports will be exposed on. + +If you run a container with an exposed port: + +``` + docker run --rm -i -t -p 80:80 apache +``` + +Then you should be able to access that Apache server using the IP address reported +to you using: + +``` + boot2docker ssh ip addr show dev eth1 +``` + +Typically, it is 192.168.59.103, but at this point it can change. + +If you want to share container ports with other computers on your LAN, you will +need to set up [NAT adaptor based port forwarding]( +https://github.com/boot2docker/boot2docker/blob/master/doc/WORKAROUNDS.md) + + For further information or to report issues, please see the [Boot2Docker site](http://boot2docker.io) From c4990ab999d49e261c5079925f0b13ef735a729f Mon Sep 17 00:00:00 2001 From: Alexandr Morozov Date: Thu, 29 May 2014 16:22:13 +0400 Subject: [PATCH 295/400] Fix races on TagStore accessing Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- graph/tags.go | 53 +++++++++++++++++++++++++++++++++++++----------- server/server.go | 15 +++----------- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/graph/tags.go b/graph/tags.go index 524e1a1f9..7af6d383d 100644 --- a/graph/tags.go +++ b/graph/tags.go @@ -3,13 +3,15 @@ package graph import ( "encoding/json" "fmt" - "github.com/dotcloud/docker/image" - "github.com/dotcloud/docker/utils" "io/ioutil" "os" "path/filepath" "sort" "strings" + "sync" + + "github.com/dotcloud/docker/image" + "github.com/dotcloud/docker/utils" ) const DEFAULTTAG = "latest" @@ -18,6 +20,7 @@ type TagStore struct { path string graph *Graph Repositories map[string]Repository + sync.Mutex } type Repository map[string]string @@ -33,8 +36,8 @@ func NewTagStore(path string, graph *Graph) (*TagStore, error) { Repositories: make(map[string]Repository), } // Load the json file if it exists, otherwise create it. - if err := store.Reload(); os.IsNotExist(err) { - if err := store.Save(); err != nil { + if err := store.reload(); os.IsNotExist(err) { + if err := store.save(); err != nil { return nil, err } } else if err != nil { @@ -43,7 +46,7 @@ func NewTagStore(path string, graph *Graph) (*TagStore, error) { return store, nil } -func (store *TagStore) Save() error { +func (store *TagStore) save() error { // Store the json ball jsonData, err := json.Marshal(store) if err != nil { @@ -55,7 +58,7 @@ func (store *TagStore) Save() error { return nil } -func (store *TagStore) Reload() error { +func (store *TagStore) reload() error { jsonData, err := ioutil.ReadFile(store.path) if err != nil { return err @@ -74,6 +77,8 @@ func (store *TagStore) LookupImage(name string) (*image.Image, error) { tag = DEFAULTTAG } img, err := store.GetImage(repos, tag) + store.Lock() + defer store.Unlock() if err != nil { return nil, err } else if img == nil { @@ -87,6 +92,8 @@ func (store *TagStore) LookupImage(name string) (*image.Image, error) { // Return a reverse-lookup table of all the names which refer to each image // Eg. {"43b5f19b10584": {"base:latest", "base:v1"}} func (store *TagStore) ByID() map[string][]string { + store.Lock() + defer store.Unlock() byID := make(map[string][]string) for repoName, repository := range store.Repositories { for tag, id := range repository { @@ -130,8 +137,10 @@ func (store *TagStore) DeleteAll(id string) error { } func (store *TagStore) Delete(repoName, tag string) (bool, error) { + store.Lock() + defer store.Unlock() deleted := false - if err := store.Reload(); err != nil { + if err := store.reload(); err != nil { return false, err } if r, exists := store.Repositories[repoName]; exists { @@ -150,13 +159,15 @@ func (store *TagStore) Delete(repoName, tag string) (bool, error) { deleted = true } } else { - fmt.Errorf("No such repository: %s", repoName) + return false, fmt.Errorf("No such repository: %s", repoName) } - return deleted, store.Save() + return deleted, store.save() } func (store *TagStore) Set(repoName, tag, imageName string, force bool) error { img, err := store.LookupImage(imageName) + store.Lock() + defer store.Unlock() if err != nil { return err } @@ -169,7 +180,7 @@ func (store *TagStore) Set(repoName, tag, imageName string, force bool) error { if err := validateTagName(tag); err != nil { return err } - if err := store.Reload(); err != nil { + if err := store.reload(); err != nil { return err } var repo Repository @@ -183,11 +194,13 @@ func (store *TagStore) Set(repoName, tag, imageName string, force bool) error { store.Repositories[repoName] = repo } repo[tag] = img.ID - return store.Save() + return store.save() } func (store *TagStore) Get(repoName string) (Repository, error) { - if err := store.Reload(); err != nil { + store.Lock() + defer store.Unlock() + if err := store.reload(); err != nil { return nil, err } if r, exists := store.Repositories[repoName]; exists { @@ -198,6 +211,8 @@ func (store *TagStore) Get(repoName string) (Repository, error) { func (store *TagStore) GetImage(repoName, tagOrID string) (*image.Image, error) { repo, err := store.Get(repoName) + store.Lock() + defer store.Unlock() if err != nil { return nil, err } else if repo == nil { @@ -215,6 +230,20 @@ func (store *TagStore) GetImage(repoName, tagOrID string) (*image.Image, error) return nil, nil } +func (store *TagStore) GetRepoRefs() map[string][]string { + store.Lock() + reporefs := make(map[string][]string) + + for name, repository := range store.Repositories { + for tag, id := range repository { + shortID := utils.TruncateID(id) + reporefs[shortID] = append(reporefs[shortID], fmt.Sprintf("%s:%s", name, tag)) + } + } + store.Unlock() + return reporefs +} + // Validate the name of a repository func validateRepoName(name string) error { if name == "" { diff --git a/server/server.go b/server/server.go index 0deda0d95..b81706836 100644 --- a/server/server.go +++ b/server/server.go @@ -684,15 +684,7 @@ func (srv *Server) ImagesViz(job *engine.Job) engine.Status { } } - reporefs := make(map[string][]string) - - for name, repository := range srv.daemon.Repositories().Repositories { - for tag, id := range repository { - reporefs[utils.TruncateID(id)] = append(reporefs[utils.TruncateID(id)], fmt.Sprintf("%s:%s", name, tag)) - } - } - - for id, repos := range reporefs { + for id, repos := range srv.daemon.Repositories().GetRepoRefs() { job.Stdout.Write([]byte(" \"" + id + "\" [label=\"" + id + "\\n" + strings.Join(repos, "\\n") + "\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n")) } job.Stdout.Write([]byte(" base [style=invisible]\n}\n")) @@ -713,6 +705,7 @@ func (srv *Server) Images(job *engine.Job) engine.Status { return job.Error(err) } lookup := make(map[string]*engine.Env) + srv.daemon.Repositories().Lock() for name, repository := range srv.daemon.Repositories().Repositories { if job.Getenv("filter") != "" { if match, _ := path.Match(job.Getenv("filter"), name); !match { @@ -742,6 +735,7 @@ func (srv *Server) Images(job *engine.Job) engine.Status { } } + srv.daemon.Repositories().Unlock() outs := engine.NewTable("Created", len(lookup)) for _, value := range lookup { @@ -1303,9 +1297,6 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName return err } } - if err := srv.daemon.Repositories().Save(); err != nil { - return err - } return nil } From ce9e9ff4a101eec5632704003fae772e8762eb15 Mon Sep 17 00:00:00 2001 From: Alexandr Morozov Date: Thu, 29 May 2014 16:26:52 +0400 Subject: [PATCH 296/400] Fix race on shutting down Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- server/server.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/server/server.go b/server/server.go index b81706836..85f1125ba 100644 --- a/server/server.go +++ b/server/server.go @@ -39,6 +39,7 @@ import ( "strconv" "strings" "sync" + "sync/atomic" "syscall" "time" @@ -87,17 +88,17 @@ func InitServer(job *engine.Job) engine.Status { c := make(chan os.Signal, 1) gosignal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) go func() { - interruptCount := 0 + interruptCount := uint32(0) for sig := range c { - go func() { + go func(sig os.Signal) { log.Printf("Received signal '%v', starting shutdown of docker...\n", sig) switch sig { case os.Interrupt, syscall.SIGTERM: // If the user really wants to interrupt, let him do so. - if interruptCount < 3 { - interruptCount++ + if atomic.LoadUint32(&interruptCount) < 3 { + atomic.AddUint32(&interruptCount, 1) // Initiate the cleanup only once - if interruptCount == 1 { + if atomic.LoadUint32(&interruptCount) == 1 { utils.RemovePidFile(srv.daemon.Config().Pidfile) srv.Close() } else { @@ -109,7 +110,7 @@ func InitServer(job *engine.Job) engine.Status { case syscall.SIGQUIT: } os.Exit(128 + int(sig.(syscall.Signal))) - }() + }(sig) } }() job.Eng.Hack_SetGlobalVar("httpapi.server", srv) From 4e5f6951f2af836c3d9163c8a150298ee87468d4 Mon Sep 17 00:00:00 2001 From: Alexandr Morozov Date: Thu, 29 May 2014 16:28:06 +0400 Subject: [PATCH 297/400] Fix races in set/get currentInterfaces in networkdriver Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- daemon/networkdriver/bridge/driver.go | 29 ++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index a14941a8f..a960aead6 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -6,6 +6,7 @@ import ( "log" "net" "strings" + "sync" "github.com/dotcloud/docker/daemon/networkdriver" "github.com/dotcloud/docker/daemon/networkdriver/ipallocator" @@ -28,6 +29,24 @@ type networkInterface struct { PortMappings []net.Addr // there are mappings to the host interfaces } +type ifaces struct { + c map[string]*networkInterface + sync.Mutex +} + +func (i *ifaces) Set(key string, n *networkInterface) { + i.Lock() + i.c[key] = n + i.Unlock() +} + +func (i *ifaces) Get(key string) *networkInterface { + i.Lock() + res := i.c[key] + i.Unlock() + return res +} + var ( addrs = []string{ // Here we don't follow the convention of using the 1st IP of the range for the gateway. @@ -53,7 +72,7 @@ var ( bridgeNetwork *net.IPNet defaultBindingIP = net.ParseIP("0.0.0.0") - currentInterfaces = make(map[string]*networkInterface) + currentInterfaces = ifaces{c: make(map[string]*networkInterface)} ) func InitDriver(job *engine.Job) engine.Status { @@ -321,9 +340,9 @@ func Allocate(job *engine.Job) engine.Status { size, _ := bridgeNetwork.Mask.Size() out.SetInt("IPPrefixLen", size) - currentInterfaces[id] = &networkInterface{ + currentInterfaces.Set(id, &networkInterface{ IP: *ip, - } + }) out.WriteTo(job.Stdout) @@ -334,7 +353,7 @@ func Allocate(job *engine.Job) engine.Status { func Release(job *engine.Job) engine.Status { var ( id = job.Args[0] - containerInterface = currentInterfaces[id] + containerInterface = currentInterfaces.Get(id) ip net.IP port int proto string @@ -383,7 +402,7 @@ func AllocatePort(job *engine.Job) engine.Status { origHostPort = job.GetenvInt("HostPort") containerPort = job.GetenvInt("ContainerPort") proto = job.Getenv("Proto") - network = currentInterfaces[id] + network = currentInterfaces.Get(id) ) if hostIP != "" { From 64bd6a6a5342c87db7096f60365d270d0d69e9d2 Mon Sep 17 00:00:00 2001 From: Alexandr Morozov Date: Thu, 29 May 2014 16:40:42 +0400 Subject: [PATCH 298/400] Fix race in native driver on activeContainers usage Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- daemon/execdriver/native/create.go | 4 ++++ daemon/execdriver/native/driver.go | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index 3a7001db1..f4c2a5bd6 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -47,9 +47,11 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Container return nil, err } cmds := make(map[string]*exec.Cmd) + d.Lock() for k, v := range d.activeContainers { cmds[k] = v.cmd } + d.Unlock() if err := configuration.ParseConfiguration(container, cmds, c.Config["native"]); err != nil { return nil, err } @@ -86,7 +88,9 @@ func (d *driver) createNetwork(container *libcontainer.Container, c *execdriver. } if c.Network.ContainerID != "" { + d.Lock() active := d.activeContainers[c.Network.ContainerID] + d.Unlock() if active == nil || active.cmd.Process == nil { return fmt.Errorf("%s is not a valid running container to join", c.Network.ContainerID) } diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index 425403fa4..c84dbf7dc 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -8,6 +8,7 @@ import ( "os/exec" "path/filepath" "strings" + "sync" "syscall" "github.com/dotcloud/docker/daemon/execdriver" @@ -62,6 +63,7 @@ type driver struct { root string initPath string activeContainers map[string]*activeContainer + sync.Mutex } func NewDriver(root, initPath string) (*driver, error) { @@ -87,10 +89,12 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba if err != nil { return -1, err } + d.Lock() d.activeContainers[c.ID] = &activeContainer{ container: container, cmd: &c.Cmd, } + d.Unlock() var ( dataPath = filepath.Join(d.root, c.ID) @@ -186,7 +190,9 @@ func (d *driver) Name() string { } func (d *driver) GetPidsForContainer(id string) ([]int, error) { + d.Lock() active := d.activeContainers[id] + d.Unlock() if active == nil { return nil, fmt.Errorf("active container for %s does not exist", id) @@ -212,7 +218,9 @@ func (d *driver) createContainerRoot(id string) error { } func (d *driver) removeContainerRoot(id string) error { + d.Lock() delete(d.activeContainers, id) + d.Unlock() return os.RemoveAll(filepath.Join(d.root, id)) } From af17b01ad7ba0c4d243e2e234710e44a14b6dad4 Mon Sep 17 00:00:00 2001 From: Alexandr Morozov Date: Fri, 30 May 2014 12:55:25 +0400 Subject: [PATCH 299/400] Goroutine-safe daemon.containers Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- daemon/daemon.go | 71 ++++++++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index 78dee6252..cffe58e1f 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1,7 +1,6 @@ package daemon import ( - "container/list" "fmt" "io" "io/ioutil" @@ -48,10 +47,43 @@ var ( validContainerNamePattern = regexp.MustCompile(`^/?` + validContainerNameChars + `+$`) ) +type contStore struct { + s map[string]*Container + sync.Mutex +} + +func (c *contStore) Add(id string, cont *Container) { + c.Lock() + c.s[id] = cont + c.Unlock() +} + +func (c *contStore) Get(id string) *Container { + c.Lock() + res := c.s[id] + c.Unlock() + return res +} + +func (c *contStore) Delete(id string) { + c.Lock() + delete(c.s, id) + c.Unlock() +} + +func (c *contStore) List() []*Container { + containers := new(History) + for _, cont := range c.s { + containers.Add(cont) + } + containers.Sort() + return *containers +} + type Daemon struct { repository string sysInitPath string - containers *list.List + containers *contStore graph *graph.Graph repositories *graph.TagStore idIndex *utils.TruncIndex @@ -87,22 +119,7 @@ func remountPrivate(mountPoint string) error { // List returns an array of all containers registered in the daemon. func (daemon *Daemon) List() []*Container { - containers := new(History) - for e := daemon.containers.Front(); e != nil; e = e.Next() { - containers.Add(e.Value.(*Container)) - } - containers.Sort() - return *containers -} - -func (daemon *Daemon) getContainerElement(id string) *list.Element { - for e := daemon.containers.Front(); e != nil; e = e.Next() { - container := e.Value.(*Container) - if container.ID == id { - return e - } - } - return nil + return daemon.containers.List() } // Get looks for a container by the specified ID or name, and returns it. @@ -117,11 +134,7 @@ func (daemon *Daemon) Get(name string) *Container { return nil } - e := daemon.getContainerElement(id) - if e == nil { - return nil - } - return e.Value.(*Container) + return daemon.containers.Get(id) } // Exists returns a true if a container of the specified ID or name exists, @@ -177,7 +190,7 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err container.stdinPipe = utils.NopWriteCloser(ioutil.Discard) // Silently drop stdin } // done - daemon.containers.PushBack(container) + daemon.containers.Add(container.ID, container) // don't update the Suffixarray if we're starting up // we'll waste time if we update it for every container @@ -279,7 +292,7 @@ func (daemon *Daemon) Destroy(container *Container) error { return fmt.Errorf("The given container is ") } - element := daemon.getContainerElement(container.ID) + element := daemon.containers.Get(container.ID) if element == nil { return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.ID) } @@ -290,7 +303,7 @@ func (daemon *Daemon) Destroy(container *Container) error { // Deregister the container before removing its directory, to avoid race conditions daemon.idIndex.Delete(container.ID) - daemon.containers.Remove(element) + daemon.containers.Delete(container.ID) if _, err := daemon.containerGraph.Purge(container.ID); err != nil { utils.Debugf("Unable to remove container from link graph: %s", err) @@ -677,11 +690,11 @@ func (daemon *Daemon) GetByName(name string) (*Container, error) { if entity == nil { return nil, fmt.Errorf("Could not find entity for %s", name) } - e := daemon.getContainerElement(entity.ID()) + e := daemon.containers.Get(entity.ID()) if e == nil { return nil, fmt.Errorf("Could not find container for entity id %s", entity.ID()) } - return e.Value.(*Container), nil + return e, nil } func (daemon *Daemon) Children(name string) (map[string]*Container, error) { @@ -860,7 +873,7 @@ func NewDaemonFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (*D daemon := &Daemon{ repository: daemonRepo, - containers: list.New(), + containers: &contStore{s: make(map[string]*Container)}, graph: g, repositories: repositories, idIndex: utils.NewTruncIndex([]string{}), From adb639117b5c61479d65dbf8398c0fbeda1d6cad Mon Sep 17 00:00:00 2001 From: Alexandr Morozov Date: Fri, 30 May 2014 13:12:02 +0400 Subject: [PATCH 300/400] Atomically increment sequence in pkg/netlink Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- pkg/netlink/netlink_linux.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pkg/netlink/netlink_linux.go b/pkg/netlink/netlink_linux.go index 21d459362..14e30aa02 100644 --- a/pkg/netlink/netlink_linux.go +++ b/pkg/netlink/netlink_linux.go @@ -7,6 +7,7 @@ import ( "fmt" "math/rand" "net" + "sync/atomic" "syscall" "unsafe" ) @@ -22,7 +23,7 @@ const ( SIOC_BRADDIF = 0x89a2 ) -var nextSeqNr int +var nextSeqNr uint32 type ifreqHwaddr struct { IfrnName [16]byte @@ -42,11 +43,6 @@ func nativeEndian() binary.ByteOrder { return binary.LittleEndian } -func getSeq() int { - nextSeqNr = nextSeqNr + 1 - return nextSeqNr -} - func getIpFamily(ip net.IP) int { if len(ip) <= net.IPv4len { return syscall.AF_INET @@ -266,7 +262,7 @@ func newNetlinkRequest(proto, flags int) *NetlinkRequest { Len: uint32(syscall.NLMSG_HDRLEN), Type: uint16(proto), Flags: syscall.NLM_F_REQUEST | uint16(flags), - Seq: uint32(getSeq()), + Seq: atomic.AddUint32(&nextSeqNr, 1), }, } } From ba1c0f4c817e958693cfcef30f1de9991765d5db Mon Sep 17 00:00:00 2001 From: Matt Heon Date: Fri, 30 May 2014 13:55:13 -0400 Subject: [PATCH 301/400] Documented special case of container memory limits in manpages If a container is started with a memory limit of 0, no memory limit is applied. To prevent this from causing confusion, make interaction explicit in docs. Docker-DCO-1.1-Signed-off-by: Matthew Heon (github: mheon) --- contrib/man/md/docker-run.1.md | 4 ++-- contrib/man/old-man/docker-run.1 | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/man/md/docker-run.1.md b/contrib/man/md/docker-run.1.md index e90ecab85..3d2420cdb 100644 --- a/contrib/man/md/docker-run.1.md +++ b/contrib/man/md/docker-run.1.md @@ -100,8 +100,8 @@ container can be started with the **--link**. **-m**, **-memory**=*memory-limit* Allows you to constrain the memory available to a container. If the host supports swap memory, then the -m memory setting can be larger than physical -RAM. The memory limit format: , where unit = b, k, m or -g. +RAM. If a limit of 0 is specified, the container's memory is not limited. The +memory limit format: , where unit = b, k, m or g. **-P**, **-publish-all**=*true*|*false* When set to true publish all exposed ports to the host interfaces. The diff --git a/contrib/man/old-man/docker-run.1 b/contrib/man/old-man/docker-run.1 index ae0295943..0e06e8d68 100644 --- a/contrib/man/old-man/docker-run.1 +++ b/contrib/man/old-man/docker-run.1 @@ -39,7 +39,7 @@ CPU shares in relative weight. You can increase the priority of a container with .TP .B -m, --memory=\fImemory-limit\fR: -Allows you to constrain the memory available to a container. If the host supports swap memory, then the -m memory setting can be larger than physical RAM. The memory limit format: , where unit = b, k, m or g. +Allows you to constrain the memory available to a container. If the host supports swap memory, then the -m memory setting can be larger than physical RAM. If a limit of 0 is specified, the container's memory is not limited. The memory limit format: , where unit = b, k, m or g. .TP .B --cidfile=\fIfile\fR: From 0fd0deb75d6e38973f154c5b41c7d4e22a9c4fef Mon Sep 17 00:00:00 2001 From: unclejack Date: Fri, 30 May 2014 21:03:56 +0300 Subject: [PATCH 302/400] optimize restore & use Getenv less in daemon.go Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- daemon/daemon.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index cffe58e1f..e080e989f 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -327,7 +327,9 @@ func (daemon *Daemon) Destroy(container *Container) error { } func (daemon *Daemon) restore() error { - if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { + debug := (os.Getenv("DEBUG") != "" || os.Getenv("TEST") != "") + + if !debug { fmt.Printf("Loading containers: ") } dir, err := ioutil.ReadDir(daemon.repository) @@ -340,7 +342,7 @@ func (daemon *Daemon) restore() error { for _, v := range dir { id := v.Name() container, err := daemon.load(id) - if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { + if !debug { fmt.Print(".") } if err != nil { @@ -357,20 +359,16 @@ func (daemon *Daemon) restore() error { } } - registerContainer := func(container *Container) { - if err := daemon.register(container, false); err != nil { - utils.Debugf("Failed to register container %s: %s", container.ID, err) - } - } - if entities := daemon.containerGraph.List("/", -1); entities != nil { for _, p := range entities.Paths() { - if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { + if !debug { fmt.Print(".") } e := entities[p] if container, ok := containers[e.ID()]; ok { - registerContainer(container) + if err := daemon.register(container, false); err != nil { + utils.Debugf("Failed to register container %s: %s", container.ID, err) + } delete(containers, e.ID()) } } @@ -383,11 +381,13 @@ func (daemon *Daemon) restore() error { if err != nil { utils.Debugf("Setting default id - %s", err) } - registerContainer(container) + if err := daemon.register(container, false); err != nil { + utils.Debugf("Failed to register container %s: %s", container.ID, err) + } } daemon.idIndex.UpdateSuffixarray() - if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { + if !debug { fmt.Printf(": done.\n") } From e70a5ab0149562609d3a16105aada365ed940cb5 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 30 May 2014 19:08:21 +0000 Subject: [PATCH 303/400] no default default number in names Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- daemon/daemon.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index cffe58e1f..f018ad246 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -524,7 +524,7 @@ func (daemon *Daemon) reserveName(id, name string) (string, error) { func (daemon *Daemon) generateNewName(id string) (string, error) { var name string - for i := 1; i < 6; i++ { + for i := 0; i < 6; i++ { name = namesgenerator.GetRandomName(i) if name[0] != '/' { name = "/" + name From 608702b98064a4dfd70b5ff0bd6fb45d2429f45b Mon Sep 17 00:00:00 2001 From: Timothy Hobbs Date: Mon, 17 Feb 2014 15:14:30 -0800 Subject: [PATCH 304/400] Refactor device handling code We now have one place that keeps track of (most) devices that are allowed and created within the container. That place is pkg/libcontainer/devices/devices.go This fixes several inconsistencies between which devices were created in the lxc backend and the native backend. It also fixes inconsistencies between wich devices were created and which were allowed. For example, /dev/full was being created but it was not allowed within the cgroup. It also declares the file modes and permissions of the default devices, rather than copying them from the host. This is in line with docker's philosphy of not being host dependent. Docker-DCO-1.1-Signed-off-by: Timothy Hobbs (github: https://github.com/timthelion) --- daemon/container.go | 27 +- daemon/execdriver/driver.go | 32 ++- daemon/execdriver/lxc/driver.go | 5 + daemon/execdriver/lxc/lxc_template.go | 35 +-- .../execdriver/lxc/lxc_template_unit_test.go | 3 + daemon/execdriver/native/create.go | 10 +- .../native/template/default_template.go | 9 +- integration-cli/docker_cli_run_test.go | 38 +++ pkg/libcontainer/cgroups/cgroups.go | 23 +- pkg/libcontainer/cgroups/fs/devices.go | 34 +-- .../cgroups/systemd/apply_systemd.go | 50 +--- pkg/libcontainer/container.go | 10 +- pkg/libcontainer/container.json | 56 +++- pkg/libcontainer/container_test.go | 9 - pkg/libcontainer/devices/devices.go | 239 ++++++++++++++++++ pkg/libcontainer/devices/number.go | 26 ++ pkg/libcontainer/mount/init.go | 7 +- pkg/libcontainer/mount/nodes/nodes.go | 56 ++-- .../mount/nodes/nodes_unsupported.go | 11 +- 19 files changed, 466 insertions(+), 214 deletions(-) create mode 100644 pkg/libcontainer/devices/devices.go create mode 100644 pkg/libcontainer/devices/number.go diff --git a/daemon/container.go b/daemon/container.go index 647f4c187..edb596e22 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -23,6 +23,7 @@ import ( "github.com/dotcloud/docker/links" "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/pkg/label" + "github.com/dotcloud/docker/pkg/libcontainer/devices" "github.com/dotcloud/docker/pkg/networkfs/etchosts" "github.com/dotcloud/docker/pkg/networkfs/resolvconf" "github.com/dotcloud/docker/pkg/symlink" @@ -230,18 +231,20 @@ func populateCommand(c *Container, env []string) error { Cpuset: c.Config.Cpuset, } c.command = &execdriver.Command{ - ID: c.ID, - Privileged: c.hostConfig.Privileged, - Rootfs: c.RootfsPath(), - InitPath: "/.dockerinit", - Entrypoint: c.Path, - Arguments: c.Args, - WorkingDir: c.Config.WorkingDir, - Network: en, - Tty: c.Config.Tty, - User: c.Config.User, - Config: context, - Resources: resources, + ID: c.ID, + Privileged: c.hostConfig.Privileged, + Rootfs: c.RootfsPath(), + InitPath: "/.dockerinit", + Entrypoint: c.Path, + Arguments: c.Args, + WorkingDir: c.Config.WorkingDir, + Network: en, + Tty: c.Config.Tty, + User: c.Config.User, + Config: context, + Resources: resources, + AllowedDevices: devices.DefaultAllowedDevices, + AutoCreatedDevices: devices.DefaultAutoCreatedDevices, } c.command.SysProcAttr = &syscall.SysProcAttr{Setsid: true} c.command.Env = env diff --git a/daemon/execdriver/driver.go b/daemon/execdriver/driver.go index a3e43e60a..2e4366149 100644 --- a/daemon/execdriver/driver.go +++ b/daemon/execdriver/driver.go @@ -5,6 +5,8 @@ import ( "io" "os" "os/exec" + + "github.com/dotcloud/docker/pkg/libcontainer/devices" ) // Context is a generic key value pair that allows @@ -120,20 +122,22 @@ type Mount struct { type Command struct { exec.Cmd `json:"-"` - ID string `json:"id"` - Privileged bool `json:"privileged"` - User string `json:"user"` - Rootfs string `json:"rootfs"` // root fs of the container - InitPath string `json:"initpath"` // dockerinit - Entrypoint string `json:"entrypoint"` - Arguments []string `json:"arguments"` - WorkingDir string `json:"working_dir"` - ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver - Tty bool `json:"tty"` - Network *Network `json:"network"` - Config map[string][]string `json:"config"` // generic values that specific drivers can consume - Resources *Resources `json:"resources"` - Mounts []Mount `json:"mounts"` + ID string `json:"id"` + Privileged bool `json:"privileged"` + User string `json:"user"` + Rootfs string `json:"rootfs"` // root fs of the container + InitPath string `json:"initpath"` // dockerinit + Entrypoint string `json:"entrypoint"` + Arguments []string `json:"arguments"` + WorkingDir string `json:"working_dir"` + ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver + Tty bool `json:"tty"` + Network *Network `json:"network"` + Config map[string][]string `json:"config"` // generic values that specific drivers can consume + Resources *Resources `json:"resources"` + Mounts []Mount `json:"mounts"` + AllowedDevices []devices.Device `json:"allowed_devices"` + AutoCreatedDevices []devices.Device `json:"autocreated_devices"` Terminal Terminal `json:"-"` // standard or tty terminal Console string `json:"-"` // dev/console path diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index 54f105419..1df1d68f6 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -17,6 +17,7 @@ import ( "github.com/dotcloud/docker/daemon/execdriver" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer/cgroups" + "github.com/dotcloud/docker/pkg/libcontainer/mount/nodes" "github.com/dotcloud/docker/pkg/system" "github.com/dotcloud/docker/utils" ) @@ -159,6 +160,10 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba c.Path = aname c.Args = append([]string{name}, arg...) + if err := nodes.CreateDeviceNodes(c.Rootfs, c.AutoCreatedDevices); err != nil { + return -1, err + } + if err := c.Start(); err != nil { return -1, err } diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index d660df902..fcebe134e 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -47,37 +47,10 @@ lxc.cgroup.devices.allow = a {{else}} # no implicit access to devices lxc.cgroup.devices.deny = a - -# but allow mknod for any device -lxc.cgroup.devices.allow = c *:* m -lxc.cgroup.devices.allow = b *:* m - -# /dev/null and zero -lxc.cgroup.devices.allow = c 1:3 rwm -lxc.cgroup.devices.allow = c 1:5 rwm - -# consoles -lxc.cgroup.devices.allow = c 5:1 rwm -lxc.cgroup.devices.allow = c 5:0 rwm -lxc.cgroup.devices.allow = c 4:0 rwm -lxc.cgroup.devices.allow = c 4:1 rwm - -# /dev/urandom,/dev/random -lxc.cgroup.devices.allow = c 1:9 rwm -lxc.cgroup.devices.allow = c 1:8 rwm - -# /dev/pts/ - pts namespaces are "coming soon" -lxc.cgroup.devices.allow = c 136:* rwm -lxc.cgroup.devices.allow = c 5:2 rwm - -# tuntap -lxc.cgroup.devices.allow = c 10:200 rwm - -# fuse -#lxc.cgroup.devices.allow = c 10:229 rwm - -# rtc -#lxc.cgroup.devices.allow = c 254:0 rwm +#Allow the devices passed to us in the AllowedDevices list. +{{range $allowedDevice := .AllowedDevices}} +lxc.cgroup.devices.allow = {{$allowedDevice.GetCgroupAllowString}} +{{end}} {{end}} # standard mount point diff --git a/daemon/execdriver/lxc/lxc_template_unit_test.go b/daemon/execdriver/lxc/lxc_template_unit_test.go index 96d11b204..26474284b 100644 --- a/daemon/execdriver/lxc/lxc_template_unit_test.go +++ b/daemon/execdriver/lxc/lxc_template_unit_test.go @@ -11,6 +11,8 @@ import ( "strings" "testing" "time" + + "github.com/dotcloud/docker/pkg/libcontainer/devices" ) func TestLXCConfig(t *testing.T) { @@ -47,6 +49,7 @@ func TestLXCConfig(t *testing.T) { Mtu: 1500, Interface: nil, }, + AllowedDevices: make([]devices.Device, 0), } p, err := driver.generateLXCConfig(command) if err != nil { diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index 3a7001db1..8344670ca 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -11,7 +11,6 @@ import ( "github.com/dotcloud/docker/daemon/execdriver/native/template" "github.com/dotcloud/docker/pkg/apparmor" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/mount/nodes" ) // createContainer populates and configures the container type with the @@ -25,6 +24,8 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Container container.WorkingDir = c.WorkingDir container.Env = c.Env container.Cgroups.Name = c.ID + container.Cgroups.AllowedDevices = c.AllowedDevices + container.DeviceNodes = c.AutoCreatedDevices // check to see if we are running in ramdisk to disable pivot root container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != "" container.Context["restrictions"] = "true" @@ -105,15 +106,10 @@ func (d *driver) createNetwork(container *libcontainer.Container, c *execdriver. func (d *driver) setPrivileged(container *libcontainer.Container) (err error) { container.Capabilities = libcontainer.GetAllCapabilities() - container.Cgroups.DeviceAccess = true + container.Cgroups.AllowAllDevices = true delete(container.Context, "restrictions") - container.OptionalDeviceNodes = nil - if container.RequiredDeviceNodes, err = nodes.GetHostDeviceNodes(); err != nil { - return err - } - if apparmor.IsEnabled() { container.Context["apparmor_profile"] = "unconfined" } diff --git a/daemon/execdriver/native/template/default_template.go b/daemon/execdriver/native/template/default_template.go index a80b609a1..21c888a03 100644 --- a/daemon/execdriver/native/template/default_template.go +++ b/daemon/execdriver/native/template/default_template.go @@ -4,7 +4,6 @@ import ( "github.com/dotcloud/docker/pkg/apparmor" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/cgroups" - "github.com/dotcloud/docker/pkg/libcontainer/mount/nodes" ) // New returns the docker default configuration for libcontainer @@ -30,12 +29,10 @@ func New() *libcontainer.Container { "NEWNET": true, }, Cgroups: &cgroups.Cgroup{ - Parent: "docker", - DeviceAccess: false, + Parent: "docker", + AllowAllDevices: false, }, - Context: libcontainer.Context{}, - RequiredDeviceNodes: nodes.DefaultNodes, - OptionalDeviceNodes: []string{"/dev/fuse"}, + Context: libcontainer.Context{}, } if apparmor.IsEnabled() { container.Context["apparmor_profile"] = "docker-default" diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index b72d8e32c..209d730f9 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -5,6 +5,7 @@ import ( "os" "os/exec" "path/filepath" + "reflect" "regexp" "sort" "strings" @@ -835,3 +836,40 @@ func TestRunWithCpuset(t *testing.T) { logDone("run - cpuset 0") } + +func TestDeviceNumbers(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "ls -l /dev/null") + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + deviceLineFields := strings.Fields(out) + deviceLineFields[6] = "" + deviceLineFields[7] = "" + deviceLineFields[8] = "" + expected := []string{"crw-rw-rw-", "1", "root", "root", "1,", "3", "", "", "", "/dev/null"} + + if !(reflect.DeepEqual(deviceLineFields, expected)) { + t.Fatalf("expected output\ncrw-rw-rw- 1 root root 1, 3 May 24 13:29 /dev/null\n received\n %s\n", out) + } + deleteAllContainers() + + logDone("run - test device numbers") +} + +func TestThatCharacterDevicesActLikeCharacterDevices(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "dd if=/dev/zero of=/zero bs=1k count=5 2> /dev/null ; du -h /zero") + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + + if actual := strings.Trim(out, "\r\n"); actual[0] == '0' { + t.Fatalf("expected a new file called /zero to be create that is greater than 0 bytes long, but du says: %s", actual) + } + deleteAllContainers() + + logDone("run - test that character devices work.") +} diff --git a/pkg/libcontainer/cgroups/cgroups.go b/pkg/libcontainer/cgroups/cgroups.go index 0f9332072..537a27f25 100644 --- a/pkg/libcontainer/cgroups/cgroups.go +++ b/pkg/libcontainer/cgroups/cgroups.go @@ -2,6 +2,8 @@ package cgroups import ( "errors" + + "github.com/dotcloud/docker/pkg/libcontainer/devices" ) var ( @@ -10,17 +12,18 @@ var ( type Cgroup struct { Name string `json:"name,omitempty"` - Parent string `json:"parent,omitempty"` + Parent string `json:"parent,omitempty"` // name of parent cgroup or slice - DeviceAccess bool `json:"device_access,omitempty"` // name of parent cgroup or slice - Memory int64 `json:"memory,omitempty"` // Memory limit (in bytes) - MemoryReservation int64 `json:"memory_reservation,omitempty"` // Memory reservation or soft_limit (in bytes) - MemorySwap int64 `json:"memory_swap,omitempty"` // Total memory usage (memory + swap); set `-1' to disable swap - CpuShares int64 `json:"cpu_shares,omitempty"` // CPU shares (relative weight vs. other containers) - CpuQuota int64 `json:"cpu_quota,omitempty"` // CPU hardcap limit (in usecs). Allowed cpu time in a given period. - CpuPeriod int64 `json:"cpu_period,omitempty"` // CPU period to be used for hardcapping (in usecs). 0 to use system default. - CpusetCpus string `json:"cpuset_cpus,omitempty"` // CPU to use - Freezer string `json:"freezer,omitempty"` // set the freeze value for the process + AllowAllDevices bool `json:"allow_all_devices,omitempty"` // If this is true allow access to any kind of device within the container. If false, allow access only to devices explicitly listed in the allowed_devices list. + AllowedDevices []devices.Device `json:"allowed_devices,omitempty"` + Memory int64 `json:"memory,omitempty"` // Memory limit (in bytes) + MemoryReservation int64 `json:"memory_reservation,omitempty"` // Memory reservation or soft_limit (in bytes) + MemorySwap int64 `json:"memory_swap,omitempty"` // Total memory usage (memory + swap); set `-1' to disable swap + CpuShares int64 `json:"cpu_shares,omitempty"` // CPU shares (relative weight vs. other containers) + CpuQuota int64 `json:"cpu_quota,omitempty"` // CPU hardcap limit (in usecs). Allowed cpu time in a given period. + CpuPeriod int64 `json:"cpu_period,omitempty"` // CPU period to be used for hardcapping (in usecs). 0 to use system default. + CpusetCpus string `json:"cpuset_cpus,omitempty"` // CPU to use + Freezer string `json:"freezer,omitempty"` // set the freeze value for the process Slice string `json:"slice,omitempty"` // Parent slice to use for systemd } diff --git a/pkg/libcontainer/cgroups/fs/devices.go b/pkg/libcontainer/cgroups/fs/devices.go index 00fea608f..68941e1e2 100644 --- a/pkg/libcontainer/cgroups/fs/devices.go +++ b/pkg/libcontainer/cgroups/fs/devices.go @@ -9,41 +9,13 @@ func (s *devicesGroup) Set(d *data) error { return err } - if !d.c.DeviceAccess { + if !d.c.AllowAllDevices { if err := writeFile(dir, "devices.deny", "a"); err != nil { return err } - allow := []string{ - // allow mknod for any device - "c *:* m", - "b *:* m", - - // /dev/null, zero, full - "c 1:3 rwm", - "c 1:5 rwm", - "c 1:7 rwm", - - // consoles - "c 5:1 rwm", - "c 5:0 rwm", - "c 4:0 rwm", - "c 4:1 rwm", - - // /dev/urandom,/dev/random - "c 1:9 rwm", - "c 1:8 rwm", - - // /dev/pts/ - pts namespaces are "coming soon" - "c 136:* rwm", - "c 5:2 rwm", - - // tuntap - "c 10:200 rwm", - } - - for _, val := range allow { - if err := writeFile(dir, "devices.allow", val); err != nil { + for _, dev := range d.c.AllowedDevices { + if err := writeFile(dir, "devices.allow", dev.GetCgroupAllowString()); err != nil { return err } } diff --git a/pkg/libcontainer/cgroups/systemd/apply_systemd.go b/pkg/libcontainer/cgroups/systemd/apply_systemd.go index 0f6beb658..e57cf16b2 100644 --- a/pkg/libcontainer/cgroups/systemd/apply_systemd.go +++ b/pkg/libcontainer/cgroups/systemd/apply_systemd.go @@ -21,11 +21,6 @@ type systemdCgroup struct { cleanupDirs []string } -type DeviceAllow struct { - Node string - Permissions string -} - var ( connLock sync.Mutex theConn *systemd1.Conn @@ -116,24 +111,9 @@ func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) { systemd1.Property{"PIDs", dbus.MakeVariant([]uint32{uint32(pid)})}, ) - if !c.DeviceAccess { + if !c.AllowAllDevices { properties = append(properties, - systemd1.Property{"DevicePolicy", dbus.MakeVariant("strict")}, - systemd1.Property{"DeviceAllow", dbus.MakeVariant([]DeviceAllow{ - {"/dev/null", "rwm"}, - {"/dev/zero", "rwm"}, - {"/dev/full", "rwm"}, - {"/dev/random", "rwm"}, - {"/dev/urandom", "rwm"}, - {"/dev/tty", "rwm"}, - {"/dev/console", "rwm"}, - {"/dev/tty0", "rwm"}, - {"/dev/tty1", "rwm"}, - {"/dev/pts/ptmx", "rwm"}, - // There is no way to add /dev/pts/* here atm, so we hack this manually below - // /dev/pts/* (how to add this?) - // Same with tuntap, which doesn't exist as a node most of the time - })}) + systemd1.Property{"DevicePolicy", dbus.MakeVariant("strict")}) } // Always enable accounting, this gets us the same behaviour as the fs implementation, @@ -167,28 +147,16 @@ func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) { cgroup := props["ControlGroup"].(string) - if !c.DeviceAccess { + if !c.AllowAllDevices { mountpoint, err := cgroups.FindCgroupMountpoint("devices") if err != nil { return nil, err } - path := filepath.Join(mountpoint, cgroup) - - allow := []string{ - // allow mknod for any device - "c *:* m", - "b *:* m", - - // /dev/pts/ - pts namespaces are "coming soon" - "c 136:* rwm", - - // tuntap - "c 10:200 rwm", - } - - for _, val := range allow { - if err := ioutil.WriteFile(filepath.Join(path, "devices.allow"), []byte(val), 0700); err != nil { + dir := filepath.Join(mountpoint, cgroup) + // We use the same method of allowing devices as in the fs backend. This needs to be changed to use DBUS as soon as possible. However, that change has to wait untill http://cgit.freedesktop.org/systemd/systemd/commit/?id=90060676c442604780634c0a993e3f9c3733f8e6 has been applied in most commonly used systemd versions. + for _, dev := range c.AllowedDevices { + if err := writeFile(dir, "devices.allow", dev.GetCgroupAllowString()); err != nil { return nil, err } } @@ -295,6 +263,10 @@ func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) { return &res, nil } +func writeFile(dir, file, data string) error { + return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700) +} + func (c *systemdCgroup) Cleanup() error { // systemd cleans up, we don't need to do much diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index 6734bfd59..fcb5dc992 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -2,6 +2,7 @@ package libcontainer import ( "github.com/dotcloud/docker/pkg/libcontainer/cgroups" + "github.com/dotcloud/docker/pkg/libcontainer/devices" ) // Context is a generic key value pair that allows arbatrary data to be sent @@ -60,13 +61,8 @@ type Container struct { // rootfs and mount namespace if specified Mounts Mounts `json:"mounts,omitempty"` - // RequiredDeviceNodes are a list of device nodes that will be mknod into the container's rootfs at /dev - // If the host system does not support the device that the container requests an error is returned - RequiredDeviceNodes []string `json:"required_device_nodes,omitempty"` - - // OptionalDeviceNodes are a list of device nodes that will be mknod into the container's rootfs at /dev - // If the host system does not support the device that the container requests the error is ignored - OptionalDeviceNodes []string `json:"optional_device_nodes,omitempty"` + // The device nodes that should be automatically created within the container upon container start. Note, make sure that the node is marked as allowed in the cgroup as well! + DeviceNodes []devices.Device `json:"device_nodes,omitempty"` } // Network defines configuration for a container's networking stack diff --git a/pkg/libcontainer/container.json b/pkg/libcontainer/container.json index ba8117091..6dcd1d293 100644 --- a/pkg/libcontainer/container.json +++ b/pkg/libcontainer/container.json @@ -44,12 +44,54 @@ "type": "devtmpfs" } ], - "required_device_nodes": [ - "/dev/null", - "/dev/zero", - "/dev/full", - "/dev/random", - "/dev/urandom", - "/dev/tty" + "device_nodes": [ + { + "path": "/dev/null", + "type": 99, + "major_number": 1, + "minor_number": 3, + "cgroup_permissions": "rwm", + "file_mode": 438 + }, + { + "path": "/dev/zero", + "type": 99, + "major_number": 1, + "minor_number": 5, + "cgroup_permissions": "rwm", + "file_mode": 438 + }, + { + "path": "/dev/full", + "type": 99, + "major_number": 1, + "minor_number": 7, + "cgroup_permissions": "rwm", + "file_mode": 438 + }, + { + "path": "/dev/tty", + "type": 99, + "major_number": 5, + "minor_number": 0, + "cgroup_permissions": "rwm", + "file_mode": 438 + }, + { + "path": "/dev/urandom", + "type": 99, + "major_number": 1, + "minor_number": 9, + "cgroup_permissions": "rwm", + "file_mode": 438 + }, + { + "path": "/dev/random", + "type": 99, + "major_number": 1, + "minor_number": 8, + "cgroup_permissions": "rwm", + "file_mode": 438 + } ] } diff --git a/pkg/libcontainer/container_test.go b/pkg/libcontainer/container_test.go index f6e991edf..10548810d 100644 --- a/pkg/libcontainer/container_test.go +++ b/pkg/libcontainer/container_test.go @@ -4,8 +4,6 @@ import ( "encoding/json" "os" "testing" - - "github.com/dotcloud/docker/pkg/libcontainer/mount/nodes" ) // Checks whether the expected capability is specified in the capabilities. @@ -63,11 +61,4 @@ func TestContainerJsonFormat(t *testing.T) { t.Log("capabilities mask should not contain SYS_CHROOT") t.Fail() } - - for _, n := range nodes.DefaultNodes { - if !contains(n, container.RequiredDeviceNodes) { - t.Logf("devices should contain %s", n) - t.Fail() - } - } } diff --git a/pkg/libcontainer/devices/devices.go b/pkg/libcontainer/devices/devices.go new file mode 100644 index 000000000..6423337d2 --- /dev/null +++ b/pkg/libcontainer/devices/devices.go @@ -0,0 +1,239 @@ +package devices + +import ( + "fmt" + "os" + "syscall" +) + +const ( + Wildcard = -1 +) + +type Device struct { + Type rune `json:"type,omitempty"` + Path string `json:"path,omitempty"` // It is fine if this is an empty string in the case that you are using Wildcards + MajorNumber int64 `json:"major_number,omitempty"` // Use the wildcard constant for wildcards. + MinorNumber int64 `json:"minor_number,omitempty"` // Use the wildcard constant for wildcards. + CgroupPermissions string `json:"cgroup_permissions,omitempty"` // Typically just "rwm" + FileMode os.FileMode `json:"file_mode,omitempty"` // The permission bits of the file's mode +} + +func GetDeviceNumberString(deviceNumber int64) string { + if deviceNumber == Wildcard { + return "*" + } else { + return fmt.Sprintf("%d", deviceNumber) + } +} + +func (device Device) GetCgroupAllowString() string { + return fmt.Sprintf("%c %s:%s %s", device.Type, GetDeviceNumberString(device.MajorNumber), GetDeviceNumberString(device.MinorNumber), device.CgroupPermissions) +} + +// Given the path to a device and it's cgroup_permissions(which cannot be easilly queried) look up the information about a linux device and return that information as a Device struct. +func GetDevice(path string, cgroupPermissions string) (Device, error) { + var ( + err error + fileInfo os.FileInfo + mode os.FileMode + fileModePermissionBits os.FileMode + devType rune + devNumber int + stat_t *syscall.Stat_t + ok bool + device Device + ) + + fileInfo, err = os.Stat(path) + if err != nil { + return Device{}, err + } + + mode = fileInfo.Mode() + fileModePermissionBits = os.FileMode.Perm(mode) + switch { + case (mode & os.ModeDevice) == 0: + return Device{}, fmt.Errorf("%s is not a device", path) + case (mode & os.ModeCharDevice) != 0: + fileModePermissionBits |= syscall.S_IFCHR + devType = 'c' + default: + fileModePermissionBits |= syscall.S_IFBLK + devType = 'b' + } + + stat_t, ok = fileInfo.Sys().(*syscall.Stat_t) + if !ok { + return Device{}, fmt.Errorf("cannot determine the device number for device %s", path) + } + devNumber = int(stat_t.Rdev) + + device = Device{ + Type: devType, + Path: path, + MajorNumber: Major(devNumber), + MinorNumber: Minor(devNumber), + CgroupPermissions: cgroupPermissions, + FileMode: fileModePermissionBits, + } + return device, nil +} + +var ( + // These are devices that are to be both allowed and created. + + DefaultSimpleDevices = []Device{ + // /dev/null and zero + { + Path: "/dev/null", + Type: 'c', + MajorNumber: 1, + MinorNumber: 3, + CgroupPermissions: "rwm", + FileMode: 0666, + }, + { + Path: "/dev/zero", + Type: 'c', + MajorNumber: 1, + MinorNumber: 5, + CgroupPermissions: "rwm", + FileMode: 0666, + }, + + { + Path: "/dev/full", + Type: 'c', + MajorNumber: 1, + MinorNumber: 7, + CgroupPermissions: "rwm", + FileMode: 0666, + }, + + // consoles and ttys + { + Path: "/dev/tty", + Type: 'c', + MajorNumber: 5, + MinorNumber: 0, + CgroupPermissions: "rwm", + FileMode: 0666, + }, + + // /dev/urandom,/dev/random + { + Path: "/dev/urandom", + Type: 'c', + MajorNumber: 1, + MinorNumber: 9, + CgroupPermissions: "rwm", + FileMode: 0666, + }, + { + Path: "/dev/random", + Type: 'c', + MajorNumber: 1, + MinorNumber: 8, + CgroupPermissions: "rwm", + FileMode: 0666, + }, + } + + DefaultAllowedDevices = append([]Device{ + // allow mknod for any device + { + Type: 'c', + MajorNumber: Wildcard, + MinorNumber: Wildcard, + CgroupPermissions: "m", + }, + { + Type: 'b', + MajorNumber: Wildcard, + MinorNumber: Wildcard, + CgroupPermissions: "m", + }, + + { + Path: "/dev/console", + Type: 'c', + MajorNumber: 5, + MinorNumber: 1, + CgroupPermissions: "rwm", + }, + { + Path: "/dev/tty0", + Type: 'c', + MajorNumber: 4, + MinorNumber: 0, + CgroupPermissions: "rwm", + }, + { + Path: "/dev/tty1", + Type: 'c', + MajorNumber: 4, + MinorNumber: 1, + CgroupPermissions: "rwm", + }, + // /dev/pts/ - pts namespaces are "coming soon" + { + Path: "", + Type: 'c', + MajorNumber: 136, + MinorNumber: Wildcard, + CgroupPermissions: "rwm", + }, + { + Path: "", + Type: 'c', + MajorNumber: 5, + MinorNumber: 2, + CgroupPermissions: "rwm", + }, + + // tuntap + { + Path: "", + Type: 'c', + MajorNumber: 10, + MinorNumber: 200, + CgroupPermissions: "rwm", + }, + + /*// fuse + { + Path: "", + Type: 'c', + MajorNumber: 10, + MinorNumber: 229, + CgroupPermissions: "rwm", + }, + + // rtc + { + Path: "", + Type: 'c', + MajorNumber: 254, + MinorNumber: 0, + CgroupPermissions: "rwm", + }, + */ + }, DefaultSimpleDevices...) + + DefaultAutoCreatedDevices = append([]Device{ + { + // /dev/fuse is created but not allowed. + // This is to allow java to work. Because java + // Insists on there being a /dev/fuse + // https://github.com/dotcloud/docker/issues/514 + // https://github.com/dotcloud/docker/issues/2393 + // + Path: "/dev/fuse", + Type: 'c', + MajorNumber: 10, + MinorNumber: 229, + CgroupPermissions: "rwm", + }, + }, DefaultSimpleDevices...) +) diff --git a/pkg/libcontainer/devices/number.go b/pkg/libcontainer/devices/number.go new file mode 100644 index 000000000..3aae380bb --- /dev/null +++ b/pkg/libcontainer/devices/number.go @@ -0,0 +1,26 @@ +package devices + +/* + +This code provides support for manipulating linux device numbers. It should be replaced by normal syscall functions once http://code.google.com/p/go/issues/detail?id=8106 is solved. + +You can read what they are here: + + - http://www.makelinux.net/ldd3/chp-3-sect-2 + - http://www.linux-tutorial.info/modules.php?name=MContent&pageid=94 + +Note! These are NOT the same as the MAJOR(dev_t device);, MINOR(dev_t device); and MKDEV(int major, int minor); functions as defined in as the representation of device numbers used by go is different than the one used internally to the kernel! - https://github.com/torvalds/linux/blob/master/include/linux/kdev_t.h#L9 + +*/ + +func Major(devNumber int) int64 { + return int64((devNumber >> 8) & 0xfff) +} + +func Minor(devNumber int) int64 { + return int64((devNumber & 0xff) | ((devNumber >> 12) & 0xfff00)) +} + +func Mkdev(majorNumber int64, minorNumber int64) int { + return int((majorNumber << 8) | (minorNumber & 0xff) | ((minorNumber & 0xfff00) << 12)) +} diff --git a/pkg/libcontainer/mount/init.go b/pkg/libcontainer/mount/init.go index 82c76aad7..af7a521c4 100644 --- a/pkg/libcontainer/mount/init.go +++ b/pkg/libcontainer/mount/init.go @@ -48,11 +48,8 @@ func InitializeMountNamespace(rootfs, console string, container *libcontainer.Co if err := setupBindmounts(rootfs, container.Mounts); err != nil { return fmt.Errorf("bind mounts %s", err) } - if err := nodes.CopyN(rootfs, container.RequiredDeviceNodes, true); err != nil { - return fmt.Errorf("copy required dev nodes %s", err) - } - if err := nodes.CopyN(rootfs, container.OptionalDeviceNodes, false); err != nil { - return fmt.Errorf("copy optional dev nodes %s", err) + if err := nodes.CreateDeviceNodes(rootfs, container.DeviceNodes); err != nil { + return fmt.Errorf("create device nodes %s", err) } if err := SetupPtmx(rootfs, console, container.Context["mount_label"]); err != nil { return err diff --git a/pkg/libcontainer/mount/nodes/nodes.go b/pkg/libcontainer/mount/nodes/nodes.go index f8e6e9745..18ef487a1 100644 --- a/pkg/libcontainer/mount/nodes/nodes.go +++ b/pkg/libcontainer/mount/nodes/nodes.go @@ -9,47 +9,27 @@ import ( "path/filepath" "syscall" + "github.com/dotcloud/docker/pkg/libcontainer/devices" "github.com/dotcloud/docker/pkg/system" ) -// Default list of device nodes to copy -var DefaultNodes = []string{ - "/dev/null", - "/dev/zero", - "/dev/full", - "/dev/random", - "/dev/urandom", - "/dev/tty", -} - -// CopyN copies the device node from the host into the rootfs -func CopyN(rootfs string, nodesToCopy []string, shouldExist bool) error { +// Create the device nodes in the container. +func CreateDeviceNodes(rootfs string, nodesToCreate []devices.Device) error { oldMask := system.Umask(0000) defer system.Umask(oldMask) - for _, node := range nodesToCopy { - if err := Copy(rootfs, node, shouldExist); err != nil { + for _, node := range nodesToCreate { + if err := CreateDeviceNode(rootfs, node); err != nil { return err } } return nil } -// Copy copies the device node into the rootfs. If the node -// on the host system does not exist and the boolean flag is passed -// an error will be returned -func Copy(rootfs, node string, shouldExist bool) error { - stat, err := os.Stat(node) - if err != nil { - if os.IsNotExist(err) && !shouldExist { - return nil - } - return err - } - +// Creates the device node in the rootfs of the container. +func CreateDeviceNode(rootfs string, node devices.Device) error { var ( - dest = filepath.Join(rootfs, node) - st = stat.Sys().(*syscall.Stat_t) + dest = filepath.Join(rootfs, node.Path) parent = filepath.Dir(dest) ) @@ -57,13 +37,23 @@ func Copy(rootfs, node string, shouldExist bool) error { return err } - if err := system.Mknod(dest, st.Mode, int(st.Rdev)); err != nil && !os.IsExist(err) { - return fmt.Errorf("mknod %s %s", node, err) + fileMode := node.FileMode + switch node.Type { + case 'c': + fileMode |= syscall.S_IFCHR + case 'b': + fileMode |= syscall.S_IFBLK + default: + return fmt.Errorf("%c is not a valid device type for device %s", node.Type, node.Path) + } + + if err := system.Mknod(dest, uint32(fileMode), devices.Mkdev(node.MajorNumber, node.MinorNumber)); err != nil && !os.IsExist(err) { + return fmt.Errorf("mknod %s %s", node.Path, err) } return nil } -func getNodes(path string) ([]string, error) { +func getDeviceNodes(path string) ([]string, error) { out := []string{} files, err := ioutil.ReadDir(path) if err != nil { @@ -71,7 +61,7 @@ func getNodes(path string) ([]string, error) { } for _, f := range files { if f.IsDir() && f.Name() != "pts" && f.Name() != "shm" { - sub, err := getNodes(filepath.Join(path, f.Name())) + sub, err := getDeviceNodes(filepath.Join(path, f.Name())) if err != nil { return nil, err } @@ -84,5 +74,5 @@ func getNodes(path string) ([]string, error) { } func GetHostDeviceNodes() ([]string, error) { - return getNodes("/dev") + return getDeviceNodes("/dev") } diff --git a/pkg/libcontainer/mount/nodes/nodes_unsupported.go b/pkg/libcontainer/mount/nodes/nodes_unsupported.go index 24409f411..b92f89b0e 100644 --- a/pkg/libcontainer/mount/nodes/nodes_unsupported.go +++ b/pkg/libcontainer/mount/nodes/nodes_unsupported.go @@ -2,10 +2,15 @@ package nodes -import "github.com/dotcloud/docker/pkg/libcontainer" - -var DefaultNodes = []string{} +import ( + "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/devices" +) func GetHostDeviceNodes() ([]string, error) { return nil, libcontainer.ErrUnsupported } + +func CreateDeviceNodes(rootfs string, nodesToCreate []devices.Device) error { + return libcontainer.ErrUnsupported +} From 5708aa62f36eadca5ada235ca05fddeb1510c1c6 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 30 May 2014 19:39:42 +0000 Subject: [PATCH 305/400] use stderr to debug iptables Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- pkg/iptables/iptables.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/iptables/iptables.go b/pkg/iptables/iptables.go index e3f4d74b9..748b69a44 100644 --- a/pkg/iptables/iptables.go +++ b/pkg/iptables/iptables.go @@ -158,7 +158,7 @@ func Raw(args ...string) ([]byte, error) { } if os.Getenv("DEBUG") != "" { - fmt.Printf("[DEBUG] [iptables]: %s, %v\n", path, args) + fmt.Fprintf(os.Stderr, fmt.Sprintf("[debug] %s, %v\n", path, args)) } output, err := exec.Command(path, args...).CombinedOutput() From b64209f7b6818337a90d2f04627664b5964a29ce Mon Sep 17 00:00:00 2001 From: unclejack Date: Sat, 31 May 2014 03:03:51 +0300 Subject: [PATCH 306/400] reuse timestamp, don't call time.Now() 3 times Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- archive/changes.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/archive/changes.go b/archive/changes.go index 88cea0f70..1e588b8eb 100644 --- a/archive/changes.go +++ b/archive/changes.go @@ -352,12 +352,13 @@ func ExportChanges(dir string, changes []Change) (Archive, error) { whiteOutDir := filepath.Dir(change.Path) whiteOutBase := filepath.Base(change.Path) whiteOut := filepath.Join(whiteOutDir, ".wh."+whiteOutBase) + timestamp := time.Now() hdr := &tar.Header{ Name: whiteOut[1:], Size: 0, - ModTime: time.Now(), - AccessTime: time.Now(), - ChangeTime: time.Now(), + ModTime: timestamp, + AccessTime: timestamp, + ChangeTime: timestamp, } if err := tw.WriteHeader(hdr); err != nil { utils.Debugf("Can't write whiteout header: %s\n", err) From b0ea389c694484c0e4907e12a9cf8ff10f80c164 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 30 May 2014 17:44:00 -0700 Subject: [PATCH 307/400] Init database if empty file Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/graphdb/conn_sqlite3.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pkg/graphdb/conn_sqlite3.go b/pkg/graphdb/conn_sqlite3.go index 33355ae4d..b6a8027a8 100644 --- a/pkg/graphdb/conn_sqlite3.go +++ b/pkg/graphdb/conn_sqlite3.go @@ -3,23 +3,32 @@ package graphdb import ( - _ "code.google.com/p/gosqlite/sqlite3" // registers sqlite "database/sql" "os" + + _ "code.google.com/p/gosqlite/sqlite3" // registers sqlite ) func NewSqliteConn(root string) (*Database, error) { initDatabase := false - if _, err := os.Stat(root); err != nil { + + stat, err := os.Stat(root) + if err != nil { if os.IsNotExist(err) { initDatabase = true } else { return nil, err } } + + if stat != nil && stat.Size() == 0 { + initDatabase = true + } + conn, err := sql.Open("sqlite3", root) if err != nil { return nil, err } + return NewDatabase(conn, initDatabase) } From 68fb7f4b744bf71206898d32fe203556a6261e5d Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Sat, 31 May 2014 01:13:37 +0000 Subject: [PATCH 308/400] Standardize API keys: CamelCase Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api/server/server.go | 6 ++++ daemon/inspect.go | 38 +++++++++++++++++++---- graph/service.go | 39 ++++++++++++++++++------ integration-cli/docker_cli_build_test.go | 16 +++++----- integration-cli/docker_cli_tag_test.go | 2 +- integration/commands_test.go | 2 +- 6 files changed, 77 insertions(+), 26 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 9c838c4bb..25b377ffd 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -848,6 +848,9 @@ func getContainersByName(eng *engine.Engine, version version.Version, w http.Res return fmt.Errorf("Missing parameter") } var job = eng.Job("container_inspect", vars["name"]) + if version.LessThan("1.12") { + job.SetenvBool("dirty", true) + } streamJSON(job, w, false) return job.Run() } @@ -857,6 +860,9 @@ func getImagesByName(eng *engine.Engine, version version.Version, w http.Respons return fmt.Errorf("Missing parameter") } var job = eng.Job("image_inspect", vars["name"]) + if version.LessThan("1.12") { + job.SetenvBool("dirty", true) + } streamJSON(job, w, false) return job.Run() } diff --git a/daemon/inspect.go b/daemon/inspect.go index 0f771a3ca..cf70d1579 100644 --- a/daemon/inspect.go +++ b/daemon/inspect.go @@ -13,14 +13,40 @@ func (daemon *Daemon) ContainerInspect(job *engine.Job) engine.Status { } name := job.Args[0] if container := daemon.Get(name); container != nil { - b, err := json.Marshal(&struct { - *Container - HostConfig *runconfig.HostConfig - }{container, container.HostConfig()}) - if err != nil { + if job.GetenvBool("dirty") { + b, err := json.Marshal(&struct { + *Container + HostConfig *runconfig.HostConfig + }{container, container.HostConfig()}) + if err != nil { + return job.Error(err) + } + job.Stdout.Write(b) + return engine.StatusOK + } + + out := &engine.Env{} + out.Set("Id", container.ID) + out.SetAuto("Created", container.Created) + out.Set("Path", container.Path) + out.SetList("Args", container.Args) + out.SetJson("Config", container.Config) + out.SetJson("State", container.State) + out.Set("Image", container.Image) + out.SetJson("NetworkSettings", container.NetworkSettings) + out.Set("ResolvConfPath", container.ResolvConfPath) + out.Set("HostnamePath", container.HostnamePath) + out.Set("HostsPath", container.HostsPath) + out.Set("Name", container.Name) + out.Set("Driver", container.Driver) + out.Set("ExecDriver", container.ExecDriver) + out.Set("MountLabel", container.MountLabel) + out.Set("ProcessLabel", container.ProcessLabel) + out.SetJson("VolumesRW", container.VolumesRW) + out.SetJson("HostConfig", container.hostConfig) + if _, err := out.WriteTo(job.Stdout); err != nil { return job.Error(err) } - job.Stdout.Write(b) return engine.StatusOK } return job.Errorf("No such container: %s", name) diff --git a/graph/service.go b/graph/service.go index 881a19904..4bce6b564 100644 --- a/graph/service.go +++ b/graph/service.go @@ -2,7 +2,6 @@ package graph import ( "encoding/json" - "fmt" "io" "github.com/dotcloud/docker/engine" @@ -117,12 +116,12 @@ func (s *TagStore) CmdGet(job *engine.Job) engine.Status { // - Comment: initially created to fulfill the "every image is a git commit" // metaphor, in practice people either ignore it or use it as a // generic description field which it isn't. On deprecation shortlist. - res.Set("created", fmt.Sprintf("%v", img.Created)) - res.Set("author", img.Author) - res.Set("os", img.OS) - res.Set("architecture", img.Architecture) - res.Set("docker_version", img.DockerVersion) - res.Set("ID", img.ID) + res.SetAuto("Created", img.Created) + res.Set("Author", img.Author) + res.Set("Os", img.OS) + res.Set("Architecture", img.Architecture) + res.Set("DockerVersion", img.DockerVersion) + res.Set("Id", img.ID) res.Set("Parent", img.Parent) } res.WriteTo(job.Stdout) @@ -136,11 +135,31 @@ func (s *TagStore) CmdLookup(job *engine.Job) engine.Status { } name := job.Args[0] if image, err := s.LookupImage(name); err == nil && image != nil { - b, err := json.Marshal(image) - if err != nil { + if job.GetenvBool("dirty") { + b, err := json.Marshal(image) + if err != nil { + return job.Error(err) + } + job.Stdout.Write(b) + return engine.StatusOK + } + + out := &engine.Env{} + out.Set("Id", image.ID) + out.Set("Parent", image.Parent) + out.Set("Comment", image.Comment) + out.SetAuto("Created", image.Created) + out.Set("Container", image.Container) + out.SetJson("ContainerConfig", image.ContainerConfig) + out.Set("DockerVersion", image.DockerVersion) + out.Set("Author", image.Author) + out.SetJson("Config", image.Config) + out.Set("Architecture", image.Architecture) + out.Set("Os", image.OS) + out.SetInt64("Size", image.Size) + if _, err = out.WriteTo(job.Stdout); err != nil { return job.Error(err) } - job.Stdout.Write(b) return engine.StatusOK } return job.Errorf("No such image: %s", name) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index d804ba796..4360032bb 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -439,7 +439,7 @@ func TestBuildWithVolume(t *testing.T) { VOLUME /test `, "testbuildimg", - "{{json .config.Volumes}}", + "{{json .Config.Volumes}}", `{"/test":{}}`) deleteImages("testbuildimg") @@ -453,7 +453,7 @@ func TestBuildMaintainer(t *testing.T) { MAINTAINER dockerio `, "testbuildimg", - "{{json .author}}", + "{{json .Author}}", `"dockerio"`) deleteImages("testbuildimg") @@ -469,7 +469,7 @@ func TestBuildUser(t *testing.T) { RUN [ $(whoami) = 'dockerio' ] `, "testbuildimg", - "{{json .config.User}}", + "{{json .Config.User}}", `"dockerio"`) deleteImages("testbuildimg") @@ -489,7 +489,7 @@ func TestBuildRelativeWorkdir(t *testing.T) { RUN [ "$PWD" = '/test2/test3' ] `, "testbuildimg", - "{{json .config.WorkingDir}}", + "{{json .Config.WorkingDir}}", `"/test2/test3"`) deleteImages("testbuildimg") @@ -504,7 +504,7 @@ func TestBuildEnv(t *testing.T) { RUN [ $(env | grep PORT) = 'PORT=4243' ] `, "testbuildimg", - "{{json .config.Env}}", + "{{json .Config.Env}}", `["HOME=/","PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","PORT=4243"]`) deleteImages("testbuildimg") @@ -518,7 +518,7 @@ func TestBuildCmd(t *testing.T) { CMD ["/bin/echo", "Hello World"] `, "testbuildimg", - "{{json .config.Cmd}}", + "{{json .Config.Cmd}}", `["/bin/echo","Hello World"]`) deleteImages("testbuildimg") @@ -533,7 +533,7 @@ func TestBuildExpose(t *testing.T) { `, "testbuildimg", - "{{json .config.ExposedPorts}}", + "{{json .Config.ExposedPorts}}", `{"4243/tcp":{}}`) deleteImages("testbuildimg") @@ -547,7 +547,7 @@ func TestBuildEntrypoint(t *testing.T) { ENTRYPOINT ["/bin/echo"] `, "testbuildimg", - "{{json .config.Entrypoint}}", + "{{json .Config.Entrypoint}}", `["/bin/echo"]`) deleteImages("testbuildimg") diff --git a/integration-cli/docker_cli_tag_test.go b/integration-cli/docker_cli_tag_test.go index d75b7db38..ef51f6464 100644 --- a/integration-cli/docker_cli_tag_test.go +++ b/integration-cli/docker_cli_tag_test.go @@ -27,7 +27,7 @@ func TestTagUnprefixedRepoByName(t *testing.T) { // tagging an image by ID in a new unprefixed repo should work func TestTagUnprefixedRepoByID(t *testing.T) { - getIDCmd := exec.Command(dockerBinary, "inspect", "-f", "{{.id}}", "busybox") + getIDCmd := exec.Command(dockerBinary, "inspect", "-f", "{{.Id}}", "busybox") out, _, err := runCommandWithOutput(getIDCmd) errorOut(err, t, fmt.Sprintf("failed to get the image ID of busybox: %v", err)) diff --git a/integration/commands_test.go b/integration/commands_test.go index 2ee5842e1..4ad225bb4 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -1057,7 +1057,7 @@ func TestContainerOrphaning(t *testing.T) { if err := job.Run(); err != nil { t.Fatal(err) } - return info.Get("ID") + return info.Get("Id") } // build an image From 1dd02ff4a09218eab47533032d1222008d6419b8 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Sat, 31 May 2014 01:28:08 +0000 Subject: [PATCH 309/400] update docs Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- docs/sources/reference/api/docker_remote_api.md | 11 ++++++++++- docs/sources/reference/api/docker_remote_api_v1.12.md | 10 +++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/docs/sources/reference/api/docker_remote_api.md b/docs/sources/reference/api/docker_remote_api.md index 84b56866b..64837817e 100644 --- a/docs/sources/reference/api/docker_remote_api.md +++ b/docs/sources/reference/api/docker_remote_api.md @@ -36,7 +36,16 @@ You can still call an old version of the api using ### What's new -docker build now has support for the `forcerm` parameter to always remove containers +`POST /build` + +**New!** +Build now has support for the `forcerm` parameter to always remove containers + +`GET /containers/(name)/json` +`GET /images/(name)/json` + +**New!** +All the JSON keys are now in CamelCase ## v1.11 diff --git a/docs/sources/reference/api/docker_remote_api_v1.12.md b/docs/sources/reference/api/docker_remote_api_v1.12.md index 188d4fe0a..9cc84f4ff 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.12.md +++ b/docs/sources/reference/api/docker_remote_api_v1.12.md @@ -798,11 +798,9 @@ Return low-level information on the image `name` Content-Type: application/json { - "id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", - "parent":"27cf784147099545", - "created":"2013-03-23T22:24:18.818426-07:00", - "container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0", - "container_config": + "Created":"2013-03-23T22:24:18.818426-07:00", + "Container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0", + "ContainerConfig": { "Hostname":"", "User":"", @@ -823,6 +821,8 @@ Return low-level information on the image `name` "VolumesFrom":"", "WorkingDir":"" }, + "Id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "Parent":"27cf784147099545", "Size": 6824592 } From 69989b7c06b0ca6737e83ddf8fcfa2dfccc57a7c Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 30 May 2014 18:30:27 -0700 Subject: [PATCH 310/400] Ensure all dev nodes are copied for privileged This also makes sure that devices are pointers to avoid copies Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/execdriver/driver.go | 4 +- .../execdriver/lxc/lxc_template_unit_test.go | 4 +- daemon/execdriver/native/create.go | 7 + pkg/libcontainer/cgroups/cgroups.go | 20 +- pkg/libcontainer/container.go | 2 +- pkg/libcontainer/devices/defaults.go | 159 ++++++++++++ pkg/libcontainer/devices/devices.go | 236 +++++------------- pkg/libcontainer/mount/nodes/nodes.go | 29 +-- .../mount/nodes/nodes_unsupported.go | 6 +- 9 files changed, 242 insertions(+), 225 deletions(-) create mode 100644 pkg/libcontainer/devices/defaults.go diff --git a/daemon/execdriver/driver.go b/daemon/execdriver/driver.go index 2e4366149..b2af92d08 100644 --- a/daemon/execdriver/driver.go +++ b/daemon/execdriver/driver.go @@ -136,8 +136,8 @@ type Command struct { Config map[string][]string `json:"config"` // generic values that specific drivers can consume Resources *Resources `json:"resources"` Mounts []Mount `json:"mounts"` - AllowedDevices []devices.Device `json:"allowed_devices"` - AutoCreatedDevices []devices.Device `json:"autocreated_devices"` + AllowedDevices []*devices.Device `json:"allowed_devices"` + AutoCreatedDevices []*devices.Device `json:"autocreated_devices"` Terminal Terminal `json:"-"` // standard or tty terminal Console string `json:"-"` // dev/console path diff --git a/daemon/execdriver/lxc/lxc_template_unit_test.go b/daemon/execdriver/lxc/lxc_template_unit_test.go index 26474284b..12760adb7 100644 --- a/daemon/execdriver/lxc/lxc_template_unit_test.go +++ b/daemon/execdriver/lxc/lxc_template_unit_test.go @@ -3,7 +3,6 @@ package lxc import ( "bufio" "fmt" - "github.com/dotcloud/docker/daemon/execdriver" "io/ioutil" "math/rand" "os" @@ -12,6 +11,7 @@ import ( "testing" "time" + "github.com/dotcloud/docker/daemon/execdriver" "github.com/dotcloud/docker/pkg/libcontainer/devices" ) @@ -49,7 +49,7 @@ func TestLXCConfig(t *testing.T) { Mtu: 1500, Interface: nil, }, - AllowedDevices: make([]devices.Device, 0), + AllowedDevices: make([]*devices.Device, 0), } p, err := driver.generateLXCConfig(command) if err != nil { diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index 0e1364313..9de500dbe 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -11,6 +11,7 @@ import ( "github.com/dotcloud/docker/daemon/execdriver/native/template" "github.com/dotcloud/docker/pkg/apparmor" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/devices" ) // createContainer populates and configures the container type with the @@ -112,6 +113,12 @@ func (d *driver) setPrivileged(container *libcontainer.Container) (err error) { container.Capabilities = libcontainer.GetAllCapabilities() container.Cgroups.AllowAllDevices = true + hostDeviceNodes, err := devices.GetHostDeviceNodes() + if err != nil { + return err + } + container.DeviceNodes = hostDeviceNodes + delete(container.Context, "restrictions") if apparmor.IsEnabled() { diff --git a/pkg/libcontainer/cgroups/cgroups.go b/pkg/libcontainer/cgroups/cgroups.go index 537a27f25..905d0ca0b 100644 --- a/pkg/libcontainer/cgroups/cgroups.go +++ b/pkg/libcontainer/cgroups/cgroups.go @@ -14,16 +14,16 @@ type Cgroup struct { Name string `json:"name,omitempty"` Parent string `json:"parent,omitempty"` // name of parent cgroup or slice - AllowAllDevices bool `json:"allow_all_devices,omitempty"` // If this is true allow access to any kind of device within the container. If false, allow access only to devices explicitly listed in the allowed_devices list. - AllowedDevices []devices.Device `json:"allowed_devices,omitempty"` - Memory int64 `json:"memory,omitempty"` // Memory limit (in bytes) - MemoryReservation int64 `json:"memory_reservation,omitempty"` // Memory reservation or soft_limit (in bytes) - MemorySwap int64 `json:"memory_swap,omitempty"` // Total memory usage (memory + swap); set `-1' to disable swap - CpuShares int64 `json:"cpu_shares,omitempty"` // CPU shares (relative weight vs. other containers) - CpuQuota int64 `json:"cpu_quota,omitempty"` // CPU hardcap limit (in usecs). Allowed cpu time in a given period. - CpuPeriod int64 `json:"cpu_period,omitempty"` // CPU period to be used for hardcapping (in usecs). 0 to use system default. - CpusetCpus string `json:"cpuset_cpus,omitempty"` // CPU to use - Freezer string `json:"freezer,omitempty"` // set the freeze value for the process + AllowAllDevices bool `json:"allow_all_devices,omitempty"` // If this is true allow access to any kind of device within the container. If false, allow access only to devices explicitly listed in the allowed_devices list. + AllowedDevices []*devices.Device `json:"allowed_devices,omitempty"` + Memory int64 `json:"memory,omitempty"` // Memory limit (in bytes) + MemoryReservation int64 `json:"memory_reservation,omitempty"` // Memory reservation or soft_limit (in bytes) + MemorySwap int64 `json:"memory_swap,omitempty"` // Total memory usage (memory + swap); set `-1' to disable swap + CpuShares int64 `json:"cpu_shares,omitempty"` // CPU shares (relative weight vs. other containers) + CpuQuota int64 `json:"cpu_quota,omitempty"` // CPU hardcap limit (in usecs). Allowed cpu time in a given period. + CpuPeriod int64 `json:"cpu_period,omitempty"` // CPU period to be used for hardcapping (in usecs). 0 to use system default. + CpusetCpus string `json:"cpuset_cpus,omitempty"` // CPU to use + Freezer string `json:"freezer,omitempty"` // set the freeze value for the process Slice string `json:"slice,omitempty"` // Parent slice to use for systemd } diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index d56e037b1..c5864e948 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -65,7 +65,7 @@ type Container struct { Mounts Mounts `json:"mounts,omitempty"` // The device nodes that should be automatically created within the container upon container start. Note, make sure that the node is marked as allowed in the cgroup as well! - DeviceNodes []devices.Device `json:"device_nodes,omitempty"` + DeviceNodes []*devices.Device `json:"device_nodes,omitempty"` } // Network defines configuration for a container's networking stack diff --git a/pkg/libcontainer/devices/defaults.go b/pkg/libcontainer/devices/defaults.go new file mode 100644 index 000000000..393c438c5 --- /dev/null +++ b/pkg/libcontainer/devices/defaults.go @@ -0,0 +1,159 @@ +package devices + +var ( + // These are devices that are to be both allowed and created. + + DefaultSimpleDevices = []*Device{ + // /dev/null and zero + { + Path: "/dev/null", + Type: 'c', + MajorNumber: 1, + MinorNumber: 3, + CgroupPermissions: "rwm", + FileMode: 0666, + }, + { + Path: "/dev/zero", + Type: 'c', + MajorNumber: 1, + MinorNumber: 5, + CgroupPermissions: "rwm", + FileMode: 0666, + }, + + { + Path: "/dev/full", + Type: 'c', + MajorNumber: 1, + MinorNumber: 7, + CgroupPermissions: "rwm", + FileMode: 0666, + }, + + // consoles and ttys + { + Path: "/dev/tty", + Type: 'c', + MajorNumber: 5, + MinorNumber: 0, + CgroupPermissions: "rwm", + FileMode: 0666, + }, + + // /dev/urandom,/dev/random + { + Path: "/dev/urandom", + Type: 'c', + MajorNumber: 1, + MinorNumber: 9, + CgroupPermissions: "rwm", + FileMode: 0666, + }, + { + Path: "/dev/random", + Type: 'c', + MajorNumber: 1, + MinorNumber: 8, + CgroupPermissions: "rwm", + FileMode: 0666, + }, + } + + DefaultAllowedDevices = append([]*Device{ + // allow mknod for any device + { + Type: 'c', + MajorNumber: Wildcard, + MinorNumber: Wildcard, + CgroupPermissions: "m", + }, + { + Type: 'b', + MajorNumber: Wildcard, + MinorNumber: Wildcard, + CgroupPermissions: "m", + }, + + { + Path: "/dev/console", + Type: 'c', + MajorNumber: 5, + MinorNumber: 1, + CgroupPermissions: "rwm", + }, + { + Path: "/dev/tty0", + Type: 'c', + MajorNumber: 4, + MinorNumber: 0, + CgroupPermissions: "rwm", + }, + { + Path: "/dev/tty1", + Type: 'c', + MajorNumber: 4, + MinorNumber: 1, + CgroupPermissions: "rwm", + }, + // /dev/pts/ - pts namespaces are "coming soon" + { + Path: "", + Type: 'c', + MajorNumber: 136, + MinorNumber: Wildcard, + CgroupPermissions: "rwm", + }, + { + Path: "", + Type: 'c', + MajorNumber: 5, + MinorNumber: 2, + CgroupPermissions: "rwm", + }, + + // tuntap + { + Path: "", + Type: 'c', + MajorNumber: 10, + MinorNumber: 200, + CgroupPermissions: "rwm", + }, + + /*// fuse + { + Path: "", + Type: 'c', + MajorNumber: 10, + MinorNumber: 229, + CgroupPermissions: "rwm", + }, + + // rtc + { + Path: "", + Type: 'c', + MajorNumber: 254, + MinorNumber: 0, + CgroupPermissions: "rwm", + }, + */ + }, DefaultSimpleDevices...) + + DefaultAutoCreatedDevices = append([]*Device{ + { + // /dev/fuse is created but not allowed. + // This is to allow java to work. Because java + // Insists on there being a /dev/fuse + // https://github.com/dotcloud/docker/issues/514 + // https://github.com/dotcloud/docker/issues/2393 + // + Path: "/dev/fuse", + Type: 'c', + MajorNumber: 10, + MinorNumber: 229, + CgroupPermissions: "rwm", + }, + }, DefaultSimpleDevices...) +) diff --git a/pkg/libcontainer/devices/devices.go b/pkg/libcontainer/devices/devices.go index 6423337d2..f6bee56df 100644 --- a/pkg/libcontainer/devices/devices.go +++ b/pkg/libcontainer/devices/devices.go @@ -1,8 +1,11 @@ package devices import ( + "errors" "fmt" + "io/ioutil" "os" + "path/filepath" "syscall" ) @@ -10,6 +13,10 @@ const ( Wildcard = -1 ) +var ( + ErrNotADeviceNode = errors.New("not a device node") +) + type Device struct { Type rune `json:"type,omitempty"` Path string `json:"path,omitempty"` // It is fine if this is an empty string in the case that you are using Wildcards @@ -27,35 +34,27 @@ func GetDeviceNumberString(deviceNumber int64) string { } } -func (device Device) GetCgroupAllowString() string { +func (device *Device) GetCgroupAllowString() string { return fmt.Sprintf("%c %s:%s %s", device.Type, GetDeviceNumberString(device.MajorNumber), GetDeviceNumberString(device.MinorNumber), device.CgroupPermissions) } // Given the path to a device and it's cgroup_permissions(which cannot be easilly queried) look up the information about a linux device and return that information as a Device struct. -func GetDevice(path string, cgroupPermissions string) (Device, error) { - var ( - err error - fileInfo os.FileInfo - mode os.FileMode - fileModePermissionBits os.FileMode - devType rune - devNumber int - stat_t *syscall.Stat_t - ok bool - device Device - ) - - fileInfo, err = os.Stat(path) +func GetDevice(path string, cgroupPermissions string) (*Device, error) { + fileInfo, err := os.Stat(path) if err != nil { - return Device{}, err + return nil, err } - mode = fileInfo.Mode() - fileModePermissionBits = os.FileMode.Perm(mode) + var ( + devType rune + mode = fileInfo.Mode() + fileModePermissionBits = os.FileMode.Perm(mode) + ) + switch { - case (mode & os.ModeDevice) == 0: - return Device{}, fmt.Errorf("%s is not a device", path) - case (mode & os.ModeCharDevice) != 0: + case mode&os.ModeDevice == 0: + return nil, ErrNotADeviceNode + case mode&os.ModeCharDevice != 0: fileModePermissionBits |= syscall.S_IFCHR devType = 'c' default: @@ -63,177 +62,58 @@ func GetDevice(path string, cgroupPermissions string) (Device, error) { devType = 'b' } - stat_t, ok = fileInfo.Sys().(*syscall.Stat_t) + stat_t, ok := fileInfo.Sys().(*syscall.Stat_t) if !ok { - return Device{}, fmt.Errorf("cannot determine the device number for device %s", path) + return nil, fmt.Errorf("cannot determine the device number for device %s", path) } - devNumber = int(stat_t.Rdev) + devNumber := int(stat_t.Rdev) - device = Device{ + return &Device{ Type: devType, Path: path, MajorNumber: Major(devNumber), MinorNumber: Minor(devNumber), CgroupPermissions: cgroupPermissions, FileMode: fileModePermissionBits, - } - return device, nil + }, nil } -var ( - // These are devices that are to be both allowed and created. +func GetHostDeviceNodes() ([]*Device, error) { + return getDeviceNodes("/dev") +} - DefaultSimpleDevices = []Device{ - // /dev/null and zero - { - Path: "/dev/null", - Type: 'c', - MajorNumber: 1, - MinorNumber: 3, - CgroupPermissions: "rwm", - FileMode: 0666, - }, - { - Path: "/dev/zero", - Type: 'c', - MajorNumber: 1, - MinorNumber: 5, - CgroupPermissions: "rwm", - FileMode: 0666, - }, - - { - Path: "/dev/full", - Type: 'c', - MajorNumber: 1, - MinorNumber: 7, - CgroupPermissions: "rwm", - FileMode: 0666, - }, - - // consoles and ttys - { - Path: "/dev/tty", - Type: 'c', - MajorNumber: 5, - MinorNumber: 0, - CgroupPermissions: "rwm", - FileMode: 0666, - }, - - // /dev/urandom,/dev/random - { - Path: "/dev/urandom", - Type: 'c', - MajorNumber: 1, - MinorNumber: 9, - CgroupPermissions: "rwm", - FileMode: 0666, - }, - { - Path: "/dev/random", - Type: 'c', - MajorNumber: 1, - MinorNumber: 8, - CgroupPermissions: "rwm", - FileMode: 0666, - }, +func getDeviceNodes(path string) ([]*Device, error) { + files, err := ioutil.ReadDir(path) + if err != nil { + return nil, err } - DefaultAllowedDevices = append([]Device{ - // allow mknod for any device - { - Type: 'c', - MajorNumber: Wildcard, - MinorNumber: Wildcard, - CgroupPermissions: "m", - }, - { - Type: 'b', - MajorNumber: Wildcard, - MinorNumber: Wildcard, - CgroupPermissions: "m", - }, + out := []*Device{} + for _, f := range files { + if f.IsDir() { + switch f.Name() { + case "pts", "shm", "fd": + continue + default: + sub, err := getDeviceNodes(filepath.Join(path, f.Name())) + if err != nil { + return nil, err + } - { - Path: "/dev/console", - Type: 'c', - MajorNumber: 5, - MinorNumber: 1, - CgroupPermissions: "rwm", - }, - { - Path: "/dev/tty0", - Type: 'c', - MajorNumber: 4, - MinorNumber: 0, - CgroupPermissions: "rwm", - }, - { - Path: "/dev/tty1", - Type: 'c', - MajorNumber: 4, - MinorNumber: 1, - CgroupPermissions: "rwm", - }, - // /dev/pts/ - pts namespaces are "coming soon" - { - Path: "", - Type: 'c', - MajorNumber: 136, - MinorNumber: Wildcard, - CgroupPermissions: "rwm", - }, - { - Path: "", - Type: 'c', - MajorNumber: 5, - MinorNumber: 2, - CgroupPermissions: "rwm", - }, + out = append(out, sub...) + continue + } + } - // tuntap - { - Path: "", - Type: 'c', - MajorNumber: 10, - MinorNumber: 200, - CgroupPermissions: "rwm", - }, + device, err := GetDevice(filepath.Join(path, f.Name()), "rwm") + if err != nil { + if err == ErrNotADeviceNode { + continue + } + return nil, err + } + out = append(out, device) + } - /*// fuse - { - Path: "", - Type: 'c', - MajorNumber: 10, - MinorNumber: 229, - CgroupPermissions: "rwm", - }, - - // rtc - { - Path: "", - Type: 'c', - MajorNumber: 254, - MinorNumber: 0, - CgroupPermissions: "rwm", - }, - */ - }, DefaultSimpleDevices...) - - DefaultAutoCreatedDevices = append([]Device{ - { - // /dev/fuse is created but not allowed. - // This is to allow java to work. Because java - // Insists on there being a /dev/fuse - // https://github.com/dotcloud/docker/issues/514 - // https://github.com/dotcloud/docker/issues/2393 - // - Path: "/dev/fuse", - Type: 'c', - MajorNumber: 10, - MinorNumber: 229, - CgroupPermissions: "rwm", - }, - }, DefaultSimpleDevices...) -) + return out, nil +} diff --git a/pkg/libcontainer/mount/nodes/nodes.go b/pkg/libcontainer/mount/nodes/nodes.go index 18ef487a1..dd67ae2d5 100644 --- a/pkg/libcontainer/mount/nodes/nodes.go +++ b/pkg/libcontainer/mount/nodes/nodes.go @@ -4,7 +4,6 @@ package nodes import ( "fmt" - "io/ioutil" "os" "path/filepath" "syscall" @@ -14,7 +13,7 @@ import ( ) // Create the device nodes in the container. -func CreateDeviceNodes(rootfs string, nodesToCreate []devices.Device) error { +func CreateDeviceNodes(rootfs string, nodesToCreate []*devices.Device) error { oldMask := system.Umask(0000) defer system.Umask(oldMask) @@ -27,7 +26,7 @@ func CreateDeviceNodes(rootfs string, nodesToCreate []devices.Device) error { } // Creates the device node in the rootfs of the container. -func CreateDeviceNode(rootfs string, node devices.Device) error { +func CreateDeviceNode(rootfs string, node *devices.Device) error { var ( dest = filepath.Join(rootfs, node.Path) parent = filepath.Dir(dest) @@ -52,27 +51,3 @@ func CreateDeviceNode(rootfs string, node devices.Device) error { } return nil } - -func getDeviceNodes(path string) ([]string, error) { - out := []string{} - files, err := ioutil.ReadDir(path) - if err != nil { - return nil, err - } - for _, f := range files { - if f.IsDir() && f.Name() != "pts" && f.Name() != "shm" { - sub, err := getDeviceNodes(filepath.Join(path, f.Name())) - if err != nil { - return nil, err - } - out = append(out, sub...) - } else if f.Mode()&os.ModeDevice == os.ModeDevice { - out = append(out, filepath.Join(path, f.Name())) - } - } - return out, nil -} - -func GetHostDeviceNodes() ([]string, error) { - return getDeviceNodes("/dev") -} diff --git a/pkg/libcontainer/mount/nodes/nodes_unsupported.go b/pkg/libcontainer/mount/nodes/nodes_unsupported.go index b92f89b0e..0e5d12c73 100644 --- a/pkg/libcontainer/mount/nodes/nodes_unsupported.go +++ b/pkg/libcontainer/mount/nodes/nodes_unsupported.go @@ -7,10 +7,6 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer/devices" ) -func GetHostDeviceNodes() ([]string, error) { - return nil, libcontainer.ErrUnsupported -} - -func CreateDeviceNodes(rootfs string, nodesToCreate []devices.Device) error { +func CreateDeviceNodes(rootfs string, nodesToCreate []*devices.Device) error { return libcontainer.ErrUnsupported } From f9705477d023c63fb316a30204761aa1e3cb3e6d Mon Sep 17 00:00:00 2001 From: Shane Canon Date: Sat, 31 May 2014 10:20:07 -0700 Subject: [PATCH 311/400] Fix for setuid race condition in LXC driver This is a fix for a race condition in the LXC driver. This is described more in issue #6092. Closes #6092 Docker-DCO-1.1-Signed-off-by: Shane Canon (github: scanon) --- daemon/execdriver/lxc/driver.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index 1df1d68f6..e1b476358 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -9,6 +9,7 @@ import ( "os/exec" "path" "path/filepath" + "runtime" "strconv" "strings" "syscall" @@ -26,6 +27,7 @@ const DriverName = "lxc" func init() { execdriver.RegisterInitFunc(DriverName, func(args *execdriver.InitArgs) error { + runtime.LockOSThread() if err := setupEnv(args); err != nil { return err } From 0658868c7f81dd225df8678f31f7ef70274d8d94 Mon Sep 17 00:00:00 2001 From: Zane DeGraffenried Date: Sat, 31 May 2014 14:55:59 -0600 Subject: [PATCH 312/400] Updated link to new docker-node-hello repository --- docs/sources/examples/nodejs_web_app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/examples/nodejs_web_app.md b/docs/sources/examples/nodejs_web_app.md index bc0e908d2..fe52aa945 100644 --- a/docs/sources/examples/nodejs_web_app.md +++ b/docs/sources/examples/nodejs_web_app.md @@ -16,7 +16,7 @@ The goal of this example is to show you how you can build your own Docker images from a parent image using a `Dockerfile` . We will do that by making a simple Node.js hello world web application running on CentOS. You can get the full source code at -[https://github.com/gasi/docker-node-hello](https://github.com/gasi/docker-node-hello). +[https://github.com/enokd/docker-node-hello/](https://github.com/enokd/docker-node-hello/). ## Create Node.js app From e471a87f1975a101cf7464c0c2cc48db3b79ec26 Mon Sep 17 00:00:00 2001 From: William Henry Date: Sat, 31 May 2014 15:44:17 -0600 Subject: [PATCH 313/400] Fixed some typos and other issues from ostezer comments. Docker-DCO-1.1-Signed-off-by: William Henry (github: ipbabble) Changes to be committed: modified: contrib/man/md/docker-build.1.md modified: contrib/man/md/docker-run.1.md modified: contrib/man/md/docker-tag.1.md --- contrib/man/md/docker-build.1.md | 20 +++++++++++--------- contrib/man/md/docker-run.1.md | 4 ++-- contrib/man/md/docker-tag.1.md | 11 ++++++----- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/contrib/man/md/docker-build.1.md b/contrib/man/md/docker-build.1.md index da4f5c017..1f8cb9cc1 100644 --- a/contrib/man/md/docker-build.1.md +++ b/contrib/man/md/docker-build.1.md @@ -70,29 +70,31 @@ specified within the `ADD` instruction into the specified target. ## Building an image and naming that image A good practice is to give a name to the image you are building. There are -not hard rules here but it is best to give the names consideration. +no hard rules here but it is best to give the names consideration. The **-t**/**--tag** flag is used to rename an image. Here are some examples: -Though t is not good practice, image names can be aribtrary: +Though it is not a good practice, image names can be arbtrary: docker build -t myimage . -A better approach is provide a fully qualified and meaningful repository -name, name, and tag (where tag in this context means the qualifier after -the ":"). In this example we build a Jboss image for the Fedora repository -and give it a version 1.0: +A better approach is to provide a fully qualified and meaningful repository, +name, and tag (where the tag in this context means the qualifier after +the ":"). In this example we build a JBoss image for the Fedora repository +and give it the version 1.0: docker build -t fedora/jboss:1.0 The next example is for the "whenry" user repository and uses Fedora and -JBoss and gives it a version 2.1 : +JBoss and gives it the version 2.1 : docker build -t whenry/fedora-jboss:V2.1 -Or: +If you do not provide a version tag then Docker will assign `latest`: - docker build -t whenry/fedora-jboss:latest + docker build -t whenry/fedora-jboss + +When you list the images, the image above will have the tag `latest`. So renaming an image is arbitrary but consideration should be given to a useful convention that makes sense for consumers and should also take diff --git a/contrib/man/md/docker-run.1.md b/contrib/man/md/docker-run.1.md index 72b01419b..a1e380ff5 100644 --- a/contrib/man/md/docker-run.1.md +++ b/contrib/man/md/docker-run.1.md @@ -64,8 +64,8 @@ the other shell to view a list of the running containers. You can reattach to a detached container with **docker attach**. If you choose to run a container in the detached mode, then you cannot use the **-rm** option. - When attached in tty mode, you can detach from a running container by pressing -the keys ctrl+p ctrl+q. + When attached in the tty mode, you can detach from a running container without +stopping the process by pressing the keys CTRL-P CTRL-Q. **--dns**=*IP-address* diff --git a/contrib/man/md/docker-tag.1.md b/contrib/man/md/docker-tag.1.md index 687b6ddb4..eca821ebf 100644 --- a/contrib/man/md/docker-tag.1.md +++ b/contrib/man/md/docker-tag.1.md @@ -9,7 +9,7 @@ docker-tag - Tag an image in the repository IMAGE [REGISTRYHOST/][USERNAME/]NAME[:TAG] # DESCRIPTION -This will rename an image in the repository. "Tag" is this context means the +This will rename an image in the repository. This refers to the entire image name including the optional TAG after the ':'. # "OPTIONS" @@ -27,15 +27,16 @@ separated by a ':' The image name. **TAG** - The tag you are assigning to the image. This is often a version or other -'tag' to distinguish from other similarly named images. + The tag you are assigning to the image. Though this is arbitrary it is +recommended to be used for a version to disinguish images with the same name. +Note that here TAG is a part of the overall name or "tag". # EXAMPLES ## Tagging an image -Here is an example of renaming an image with the repository 'fedora', name -'httpd', and tag version1.0 : +Here is an example of renaming an image (e.g. 0e5574283393) as "httpd" and +tagging it into the "fedora" repository with "version1.0": docker tag 0e5574283393 fedora/httpd:version1.0 From 953abf6ae7b31a3563fa57a9898305018add0571 Mon Sep 17 00:00:00 2001 From: SvenDowideit Date: Tue, 20 May 2014 15:54:10 +1000 Subject: [PATCH 314/400] Add documentation build steps for the next release manager to follow. Docker-DCO-1.1-Signed-off-by: SvenDowideit (github: SvenDowideit) --- hack/RELEASE-CHECKLIST.md | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/hack/RELEASE-CHECKLIST.md b/hack/RELEASE-CHECKLIST.md index 583328b46..2652e16c4 100644 --- a/hack/RELEASE-CHECKLIST.md +++ b/hack/RELEASE-CHECKLIST.md @@ -104,8 +104,21 @@ make test ### 5. Test the docs Make sure that your tree includes documentation for any modified or -new features, syntax or semantic changes. Instructions for building -the docs are in `docs/README.md`. +new features, syntax or semantic changes. + +To test locally: + +```bash +make docs +``` + +To make a shared test at http://beta-docs.docker.io: + +(You will need the `awsconfig` file added to the `docs/` dir) + +```bash +make AWS_S3_BUCKET=beta-docs.docker.io docs-release +``` ### 6. Commit and create a pull request to the "release" branch @@ -211,17 +224,20 @@ branch afterwards! ### 12. Update the docs branch +You will need the `awsconfig` file added to the `docs/` directory to contain the +s3 credentials for the bucket you are deploying to. + ```bash git checkout docs git fetch git reset --hard origin/release git push -f origin docs +make AWS_S3_BUCKET=docs.docker.io docs-release ``` -Updating the docs branch will automatically update the documentation on the -"latest" revision of the docs. You should see the updated docs 5-10 minutes -after the merge. The docs will appear on http://docs.docker.io/. For more -information about documentation releases, see `docs/README.md`. +The docs will appear on http://docs.docker.io/ (though there may be cached +versions, so its worth checking http://docs.docker.io.s3-website-us-west-2.amazonaws.com/). +For more information about documentation releases, see `docs/README.md`. ### 13. Create a new pull request to merge release back into master From a7b2c4804b2d98c2b5622db40d3d70b88529d7fe Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 22 May 2014 07:05:19 +1000 Subject: [PATCH 315/400] Adding User Guide * Added User Guide section outlines. * Added User Guide to menu. * Moved HTTPS example to articles. * Replaced Hello World example with User Guide. * Moved use cases out of examples. * Updated Introduction to add User Guide. * Redirected migrated /use and /articles links. * Added Docker.io section * Added Dockerized section * Added Using Docker section * Added Docker Images section * Added Docker Links section * Added Docker Volumes section Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- docs/mkdocs.yml | 80 ++-- docs/s3_website.json | 9 +- docs/sources/articles.md | 11 +- .../ambassador_pattern_linking.md | 0 docs/sources/{use => articles}/basics.md | 9 +- .../cfengine_process_management.md | 0 docs/sources/{use => articles}/chef.md | 0 .../{use => articles}/host_integration.md | 0 docs/sources/{examples => articles}/https.md | 0 docs/sources/{use => articles}/networking.md | 18 +- docs/sources/{use => articles}/puppet.md | 0 docs/sources/articles/security.md | 2 +- .../using_supervisord.md | 8 +- docs/sources/docker-io/builds.md | 78 ++-- docs/sources/docker-io/index.md | 8 + docs/sources/examples.md | 30 +- docs/sources/examples/apt-cacher-ng.md | 8 +- docs/sources/examples/couchdb_data_volumes.md | 8 +- docs/sources/examples/example_header.inc | 8 - docs/sources/examples/hello_world.md | 162 ------- docs/sources/examples/mongodb.md | 20 +- docs/sources/examples/nodejs_web_app.md | 13 +- docs/sources/examples/postgresql_service.md | 24 +- docs/sources/examples/python_web_app.md | 127 ------ .../sources/examples/running_redis_service.md | 12 +- docs/sources/examples/running_riak_service.md | 19 +- docs/sources/examples/running_ssh_service.md | 27 +- docs/sources/faq.md | 14 +- docs/sources/index.md | 29 +- docs/sources/installation/amazon.md | 5 +- docs/sources/installation/binaries.md | 25 +- docs/sources/installation/centos.md | 41 +- docs/sources/installation/debian.md | 26 +- docs/sources/installation/fedora.md | 6 +- docs/sources/installation/mac.md | 34 +- docs/sources/installation/openSUSE.md | 5 +- docs/sources/installation/rhel.md | 3 +- docs/sources/installation/softlayer.md | 6 +- docs/sources/installation/ubuntulinux.md | 6 +- .../introduction/understanding-docker.md | 11 +- .../introduction/working-with-docker.md | 292 ------------- .../reference/api/docker_remote_api_v1.10.md | 5 +- .../reference/api/docker_remote_api_v1.11.md | 5 +- .../reference/api/docker_remote_api_v1.6.md | 5 +- .../reference/api/docker_remote_api_v1.7.md | 5 +- .../reference/api/docker_remote_api_v1.8.md | 5 +- .../reference/api/docker_remote_api_v1.9.md | 5 +- docs/sources/reference/builder.md | 12 +- docs/sources/reference/commandline/cli.md | 33 +- docs/sources/reference/run.md | 24 +- docs/sources/terms/container.md | 23 +- docs/sources/terms/image.md | 4 +- docs/sources/terms/layer.md | 2 +- docs/sources/terms/registry.md | 10 +- docs/sources/terms/repository.md | 6 +- docs/sources/use.md | 13 - docs/sources/use/port_redirection.md | 127 ------ docs/sources/use/working_with_links_names.md | 139 ------ docs/sources/use/working_with_volumes.md | 171 -------- docs/sources/use/workingwithrepository.md | 251 ----------- docs/sources/userguide/dockerimages.md | 397 ++++++++++++++++++ docs/sources/userguide/dockerio.md | 73 ++++ docs/sources/userguide/dockerizing.md | 193 +++++++++ docs/sources/userguide/dockerlinks.md | 241 +++++++++++ docs/sources/userguide/dockerrepos.md | 176 ++++++++ docs/sources/userguide/dockervolumes.md | 142 +++++++ docs/sources/userguide/index.md | 98 +++++ docs/sources/userguide/login-web.png | Bin 0 -> 31788 bytes docs/sources/userguide/register-confirm.png | Bin 0 -> 65441 bytes docs/sources/userguide/register-web.png | Bin 0 -> 72463 bytes docs/sources/userguide/search.png | Bin 0 -> 101216 bytes docs/sources/userguide/usingdocker.md | 316 ++++++++++++++ docs/sources/userguide/webapp1.png | Bin 0 -> 51848 bytes 73 files changed, 1958 insertions(+), 1707 deletions(-) rename docs/sources/{use => articles}/ambassador_pattern_linking.md (100%) rename docs/sources/{use => articles}/basics.md (95%) rename docs/sources/{examples => articles}/cfengine_process_management.md (100%) rename docs/sources/{use => articles}/chef.md (100%) rename docs/sources/{use => articles}/host_integration.md (100%) rename docs/sources/{examples => articles}/https.md (100%) rename docs/sources/{use => articles}/networking.md (97%) rename docs/sources/{use => articles}/puppet.md (100%) rename docs/sources/{examples => articles}/using_supervisord.md (93%) create mode 100644 docs/sources/docker-io/index.md delete mode 100644 docs/sources/examples/example_header.inc delete mode 100644 docs/sources/examples/hello_world.md delete mode 100644 docs/sources/examples/python_web_app.md delete mode 100644 docs/sources/introduction/working-with-docker.md delete mode 100644 docs/sources/use.md delete mode 100644 docs/sources/use/port_redirection.md delete mode 100644 docs/sources/use/working_with_links_names.md delete mode 100644 docs/sources/use/working_with_volumes.md delete mode 100644 docs/sources/use/workingwithrepository.md create mode 100644 docs/sources/userguide/dockerimages.md create mode 100644 docs/sources/userguide/dockerio.md create mode 100644 docs/sources/userguide/dockerizing.md create mode 100644 docs/sources/userguide/dockerlinks.md create mode 100644 docs/sources/userguide/dockerrepos.md create mode 100644 docs/sources/userguide/dockervolumes.md create mode 100644 docs/sources/userguide/index.md create mode 100644 docs/sources/userguide/login-web.png create mode 100644 docs/sources/userguide/register-confirm.png create mode 100644 docs/sources/userguide/register-web.png create mode 100644 docs/sources/userguide/search.png create mode 100644 docs/sources/userguide/usingdocker.md create mode 100644 docs/sources/userguide/webapp1.png diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 1e6b8b16d..2835cd9dd 100755 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -28,7 +28,6 @@ pages: - ['index.md', 'About', 'Docker'] - ['introduction/index.md', '**HIDDEN**'] - ['introduction/understanding-docker.md', 'About', 'Understanding Docker'] -- ['introduction/working-with-docker.md', 'About', 'Working with Docker'] # Installation: - ['installation/index.md', '**HIDDEN**'] @@ -50,44 +49,47 @@ pages: - ['installation/windows.md', 'Installation', 'Microsoft Windows'] - ['installation/binaries.md', 'Installation', 'Binaries'] -# Examples: -- ['use/index.md', '**HIDDEN**'] -- ['use/basics.md', 'Examples', 'First steps with Docker'] -- ['examples/index.md', '**HIDDEN**'] -- ['examples/hello_world.md', 'Examples', 'Hello World'] -- ['examples/nodejs_web_app.md', 'Examples', 'Node.js web application'] -- ['examples/python_web_app.md', 'Examples', 'Python web application'] -- ['examples/mongodb.md', 'Examples', 'Dockerizing MongoDB'] -- ['examples/running_redis_service.md', 'Examples', 'Redis service'] -- ['examples/postgresql_service.md', 'Examples', 'PostgreSQL service'] -- ['examples/running_riak_service.md', 'Examples', 'Running a Riak service'] -- ['examples/running_ssh_service.md', 'Examples', 'Running an SSH service'] -- ['examples/couchdb_data_volumes.md', 'Examples', 'CouchDB service'] -- ['examples/apt-cacher-ng.md', 'Examples', 'Apt-Cacher-ng service'] -- ['examples/https.md', 'Examples', 'Running Docker with HTTPS'] -- ['examples/using_supervisord.md', 'Examples', 'Using Supervisor'] -- ['examples/cfengine_process_management.md', 'Examples', 'Process management with CFEngine'] -- ['use/working_with_links_names.md', 'Examples', 'Linking containers together'] -- ['use/working_with_volumes.md', 'Examples', 'Sharing Directories using volumes'] -- ['use/puppet.md', 'Examples', 'Using Puppet'] -- ['use/chef.md', 'Examples', 'Using Chef'] -- ['use/workingwithrepository.md', 'Examples', 'Working with a Docker Repository'] -- ['use/port_redirection.md', 'Examples', 'Redirect ports'] -- ['use/ambassador_pattern_linking.md', 'Examples', 'Cross-Host linking using Ambassador Containers'] -- ['use/host_integration.md', 'Examples', 'Automatically starting Containers'] - -#- ['user-guide/index.md', '**HIDDEN**'] -# - ['user-guide/writing-your-docs.md', 'User Guide', 'Writing your docs'] -# - ['user-guide/styling-your-docs.md', 'User Guide', 'Styling your docs'] -# - ['user-guide/configuration.md', 'User Guide', 'Configuration'] -# ./faq.md +# User Guide: +- ['userguide/index.md', 'User Guide', 'The Docker User Guide' ] +- ['userguide/dockerio.md', 'User Guide', 'Getting Started with Docker.io' ] +- ['userguide/dockerizing.md', 'User Guide', 'Dockerizing Applications' ] +- ['userguide/usingdocker.md', 'User Guide', 'Working with Containers' ] +- ['userguide/dockerimages.md', 'User Guide', 'Working with Docker Images' ] +- ['userguide/dockerlinks.md', 'User Guide', 'Linking containers together' ] +- ['userguide/dockervolumes.md', 'User Guide', 'Managing data in containers' ] +- ['userguide/dockerrepos.md', 'User Guide', 'Working with Docker.io' ] # Docker.io docs: -- ['docker-io/index.md', '**HIDDEN**'] -# - ['index/home.md', 'Docker Index', 'Help'] +- ['docker-io/index.md', 'Docker.io', 'Docker.io' ] - ['docker-io/accounts.md', 'Docker.io', 'Accounts'] - ['docker-io/repos.md', 'Docker.io', 'Repositories'] -- ['docker-io/builds.md', 'Docker.io', 'Trusted Builds'] +- ['docker-io/builds.md', 'Docker.io', 'Automated Builds'] + +# Examples: +- ['examples/index.md', '**HIDDEN**'] +- ['examples/nodejs_web_app.md', 'Examples', 'Dockerizing a Node.js web application'] +- ['examples/mongodb.md', 'Examples', 'Dockerizing MongoDB'] +- ['examples/running_redis_service.md', 'Examples', 'Dockerizing a Redis service'] +- ['examples/postgresql_service.md', 'Examples', 'Dockerizing a PostgreSQL service'] +- ['examples/running_riak_service.md', 'Examples', 'Dockerizing a Riak service'] +- ['examples/running_ssh_service.md', 'Examples', 'Dockerizing an SSH service'] +- ['examples/couchdb_data_volumes.md', 'Examples', 'Dockerizing a CouchDB service'] +- ['examples/apt-cacher-ng.md', 'Examples', 'Dockerizing an Apt-Cacher-ng service'] + +# Articles +- ['articles/index.md', '**HIDDEN**'] +- ['articles/basics.md', 'Articles', 'Docker basics'] +- ['articles/networking.md', 'Articles', 'Advanced networking'] +- ['articles/security.md', 'Articles', 'Security'] +- ['articles/https.md', 'Articles', 'Running Docker with HTTPS'] +- ['articles/host_integration.md', 'Articles', 'Automatically starting Containers'] +- ['articles/using_supervisord.md', 'Articles', 'Using Supervisor'] +- ['articles/cfengine_process_management.md', 'Articles', 'Process management with CFEngine'] +- ['articles/puppet.md', 'Articles', 'Using Puppet'] +- ['articles/chef.md', 'Articles', 'Using Chef'] +- ['articles/ambassador_pattern_linking.md', 'Articles', 'Cross-Host linking using Ambassador Containers'] +- ['articles/runmetrics.md', 'Articles', 'Runtime metrics'] +- ['articles/baseimages.md', 'Articles', 'Creating a Base Image'] # Reference - ['reference/index.md', '**HIDDEN**'] @@ -96,11 +98,6 @@ pages: - ['reference/builder.md', 'Reference', 'Dockerfile'] - ['faq.md', 'Reference', 'FAQ'] - ['reference/run.md', 'Reference', 'Run Reference'] -- ['articles/index.md', '**HIDDEN**'] -- ['articles/runmetrics.md', 'Reference', 'Runtime metrics'] -- ['articles/security.md', 'Reference', 'Security'] -- ['articles/baseimages.md', 'Reference', 'Creating a Base Image'] -- ['use/networking.md', 'Reference', 'Advanced networking'] - ['reference/api/index.md', '**HIDDEN**'] - ['reference/api/docker-io_api.md', 'Reference', 'Docker.io API'] - ['reference/api/registry_api.md', 'Reference', 'Docker Registry API'] @@ -134,9 +131,6 @@ pages: - ['terms/filesystem.md', '**HIDDEN**'] - ['terms/image.md', '**HIDDEN**'] -# TODO: our theme adds a dropdown even for sections that have no subsections. - #- ['faq.md', 'FAQ'] - # Contribute: - ['contributing/index.md', '**HIDDEN**'] - ['contributing/contributing.md', 'Contribute', 'Contributing'] diff --git a/docs/s3_website.json b/docs/s3_website.json index 2d158cf9d..35c86f462 100644 --- a/docs/s3_website.json +++ b/docs/s3_website.json @@ -11,7 +11,14 @@ { "Condition": { "KeyPrefixEquals": "en/v0.6.3/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "" } }, { "Condition": { "KeyPrefixEquals": "jsearch/index.html" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "jsearch/" } }, { "Condition": { "KeyPrefixEquals": "index/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "docker-io/" } }, - { "Condition": { "KeyPrefixEquals": "reference/api/index_api/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "reference/api/docker-io_api/" } } + { "Condition": { "KeyPrefixEquals": "reference/api/index_api/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "reference/api/docker-io_api/" } }, + { "Condition": { "KeyPrefixEquals": "examples/hello_world/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "userguide/dockerizing/" } }, + { "Condition": { "KeyPrefixEquals": "examples/python_web_app/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "userguide/dockerizing/" } }, + { "Condition": { "KeyPrefixEquals": "use/working_with_volumes/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "userguide/dockervolumes/" } }, + { "Condition": { "KeyPrefixEquals": "use/working_with_links_names/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "userguide/dockerlinks/" } }, + { "Condition": { "KeyPrefixEquals": "use/workingwithrepository/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "userguide/dockerrepos/" } }, + { "Condition": { "KeyPrefixEquals": "use/port_redirection" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "userguide/dockerlinks/" } }, + { "Condition": { "KeyPrefixEquals": "use/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "examples/" } }, ] } diff --git a/docs/sources/articles.md b/docs/sources/articles.md index 54c067d0c..51335c6af 100644 --- a/docs/sources/articles.md +++ b/docs/sources/articles.md @@ -1,8 +1,13 @@ # Articles -## Contents: - + - [Docker Basics](basics/) - [Docker Security](security/) + - [Running the Docker daemon with HTTPS](https/) + - [Configure Networking](networking/) + - [Using Supervisor with Docker](using_supervisord/) + - [Process Management with CFEngine](cfengine_process_management/) + - [Using Puppet](puppet/) - [Create a Base Image](baseimages/) - [Runtime Metrics](runmetrics/) - + - [Automatically Start Containers](host_integration/) + - [Link via an Ambassador Container](ambassador_pattern_linking/) diff --git a/docs/sources/use/ambassador_pattern_linking.md b/docs/sources/articles/ambassador_pattern_linking.md similarity index 100% rename from docs/sources/use/ambassador_pattern_linking.md rename to docs/sources/articles/ambassador_pattern_linking.md diff --git a/docs/sources/use/basics.md b/docs/sources/articles/basics.md similarity index 95% rename from docs/sources/use/basics.md rename to docs/sources/articles/basics.md index 1b8c0a7b1..1f21e3454 100644 --- a/docs/sources/use/basics.md +++ b/docs/sources/articles/basics.md @@ -26,7 +26,7 @@ for installation instructions. $ sudo docker pull ubuntu This will find the `ubuntu` image by name on -[*Docker.io*](../workingwithrepository/#find-public-images-on-dockerio) +[*Docker.io*](/userguide/dockerrepos/#find-public-images-on-dockerio) and download it from [Docker.io](https://index.docker.io) to a local image cache. @@ -173,6 +173,7 @@ will be stored (as a diff). See which images you already have using the You now have an image state from which you can create new instances. -Read more about [*Share Images via Repositories*]( -../workingwithrepository/#working-with-the-repository) or -continue to the complete [*Command Line*](/reference/commandline/cli/#cli) +Read more about [*Share Images via +Repositories*](/userguide/dockerrepos/#working-with-the-repository) or +continue to the complete [*Command +Line*](/reference/commandline/cli/#cli) diff --git a/docs/sources/examples/cfengine_process_management.md b/docs/sources/articles/cfengine_process_management.md similarity index 100% rename from docs/sources/examples/cfengine_process_management.md rename to docs/sources/articles/cfengine_process_management.md diff --git a/docs/sources/use/chef.md b/docs/sources/articles/chef.md similarity index 100% rename from docs/sources/use/chef.md rename to docs/sources/articles/chef.md diff --git a/docs/sources/use/host_integration.md b/docs/sources/articles/host_integration.md similarity index 100% rename from docs/sources/use/host_integration.md rename to docs/sources/articles/host_integration.md diff --git a/docs/sources/examples/https.md b/docs/sources/articles/https.md similarity index 100% rename from docs/sources/examples/https.md rename to docs/sources/articles/https.md diff --git a/docs/sources/use/networking.md b/docs/sources/articles/networking.md similarity index 97% rename from docs/sources/use/networking.md rename to docs/sources/articles/networking.md index 5b76233ef..aedc5aaa3 100644 --- a/docs/sources/use/networking.md +++ b/docs/sources/articles/networking.md @@ -14,6 +14,13 @@ Docker made the choice `172.17.42.1/16` when I started it a few minutes ago, for example — a 16-bit netmask providing 65,534 addresses for the host machine and its containers. +> **Note:** +> This document discusses advanced networking configuration +> and options for Docker. In most cases you won't need this information. +> If you're looking to get started with a simpler explanation of Docker +> networking and an introduction to the concept of container linking see +> the [Docker User Guide](/userguide/dockerlinks/). + But `docker0` is no ordinary interface. It is a virtual *Ethernet bridge* that automatically forwards packets between any other network interfaces that are attached to it. This lets containers communicate @@ -29,7 +36,7 @@ container. The remaining sections of this document explain all of the ways that you can use Docker options and — in advanced cases — raw Linux networking -commands to tweak, supplement, or entirely replace Docker’s default +commands to tweak, supplement, or entirely replace Docker's default networking configuration. ## Quick Guide to the Options @@ -53,9 +60,6 @@ server when it starts up, and cannot be changed once it is running: it tells the Docker server over what channels it should be willing to receive commands like “run container” and “stop container.” - To learn about the option, - read [Bind Docker to another host/port or a Unix socket](../basics/#bind-docker-to-another-hostport-or-a-unix-socket) - over in the Basics document. * `--icc=true|false` — see [Communication between containers](#between-containers) @@ -219,7 +223,7 @@ services. If the Docker daemon is running with both `--icc=false` and `ACCEPT` rules so that the new container can connect to the ports exposed by the other container — the ports that it mentioned in the `EXPOSE` lines of its `Dockerfile`. Docker has more documentation on -this subject — see the [Link Containers](working_with_links_names.md) +this subject — see the [linking Docker containers](/userguide/dockerlinks) page for further details. > **Note**: @@ -280,7 +284,7 @@ machine that the Docker server creates when it starts: But if you want containers to accept incoming connections, you will need to provide special options when invoking `docker run`. These options -are covered in more detail on the [Redirect Ports](port_redirection.md) +are covered in more detail in the [Docker User Guide](/userguide/dockerlinks) page. There are two approaches. First, you can supply `-P` or `--publish-all=true|false` to `docker run` @@ -329,7 +333,7 @@ option `--ip=IP_ADDRESS`. Remember to restart your Docker server after editing this setting. Again, this topic is covered without all of these low-level networking -details in the [Redirect Ports](port_redirection.md) document if you +details in the [Docker User Guide](/userguide/dockerlinks/) document if you would like to use that as your port redirection reference instead. ## Customizing docker0 diff --git a/docs/sources/use/puppet.md b/docs/sources/articles/puppet.md similarity index 100% rename from docs/sources/use/puppet.md rename to docs/sources/articles/puppet.md diff --git a/docs/sources/articles/security.md b/docs/sources/articles/security.md index 69284db83..eef257730 100644 --- a/docs/sources/articles/security.md +++ b/docs/sources/articles/security.md @@ -38,7 +38,7 @@ of another container. Of course, if the host system is setup accordingly, containers can interact with each other through their respective network interfaces — just like they can interact with external hosts. When you specify public ports for your containers or use -[*links*](/use/working_with_links_names/#working-with-links-names) +[*links*](/userguide/dockerlinks/#working-with-links-names) then IP traffic is allowed between containers. They can ping each other, send/receive UDP packets, and establish TCP connections, but that can be restricted if necessary. From a network architecture point of view, all diff --git a/docs/sources/examples/using_supervisord.md b/docs/sources/articles/using_supervisord.md similarity index 93% rename from docs/sources/examples/using_supervisord.md rename to docs/sources/articles/using_supervisord.md index 0f1094532..fd7c07cab 100644 --- a/docs/sources/examples/using_supervisord.md +++ b/docs/sources/articles/using_supervisord.md @@ -5,10 +5,6 @@ page_keywords: docker, supervisor, process management # Using Supervisor with Docker > **Note**: -> -> - This example assumes you have Docker running in daemon mode. For -> more information please see [*Check your Docker -> install*](../hello_world/#running-examples). > - **If you don't like sudo** then see [*Giving non-root > access*](/installation/binaries/#dockergroup) @@ -16,8 +12,8 @@ Traditionally a Docker container runs a single process when it is launched, for example an Apache daemon or a SSH server daemon. Often though you want to run more than one process in a container. There are a number of ways you can achieve this ranging from using a simple Bash -script as the value of your container's `CMD` -instruction to installing a process management tool. +script as the value of your container's `CMD` instruction to installing +a process management tool. In this example we're going to make use of the process management tool, [Supervisor](http://supervisord.org/), to manage multiple processes in diff --git a/docs/sources/docker-io/builds.md b/docs/sources/docker-io/builds.md index 1f6e00220..dd18907cf 100644 --- a/docs/sources/docker-io/builds.md +++ b/docs/sources/docker-io/builds.md @@ -1,33 +1,32 @@ -page_title: Trusted Builds on Docker.io -page_description: Docker.io Trusted Builds -page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker.io, docs, documentation, trusted, builds, trusted builds +page_title: Automated Builds on Docker.io +page_description: Docker.io Automated Builds +page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker.io, docs, documentation, trusted, builds, trusted builds, automated builds +# Automated Builds on Docker.io -# Trusted Builds on Docker.io +## Automated Builds -## Trusted Builds - -*Trusted Builds* is a special feature allowing you to specify a source +*Automated Builds* is a special feature allowing you to specify a source repository with a `Dockerfile` to be built by the [Docker.io](https://index.docker.io) build clusters. The system will clone your repository and build the `Dockerfile` using the repository as the context. The resulting image will then be uploaded to the registry -and marked as a *Trusted Build*. +and marked as an *Automated Build*. -Trusted Builds have a number of advantages. For example, users of *your* Trusted -Build can be certain that the resulting image was built exactly how it claims -to be. +Automated Builds have a number of advantages. For example, users of +*your* Automated Build can be certain that the resulting image was built +exactly how it claims to be. Furthermore, the `Dockerfile` will be available to anyone browsing your repository -on the registry. Another advantage of the Trusted Builds feature is the automated +on the registry. Another advantage of the Automated Builds feature is the automated builds. This makes sure that your repository is always up to date. -Trusted builds are supported for both public and private repositories on -both [GitHub](http://github.com) and +Automated Builds are supported for both public and private repositories +on both [GitHub](http://github.com) and [BitBucket](https://bitbucket.org/). -### Setting up Trusted Builds with GitHub +### Setting up Automated Builds with GitHub -In order to setup a Trusted Build, you need to first link your [Docker.io]( +In order to setup an Automated Build, you need to first link your [Docker.io]( https://index.docker.io) account with a GitHub one. This will allow the registry to see your repositories. @@ -35,7 +34,7 @@ to see your repositories. > https://index.docker.io) needs to setup a GitHub service hook. Although nothing > else is done with your account, this is how GitHub manages permissions, sorry! -Click on the [Trusted Builds tab](https://index.docker.io/builds/) to +Click on the [Automated Builds tab](https://index.docker.io/builds/) to get started and then select [+ Add New](https://index.docker.io/builds/add/). @@ -45,9 +44,9 @@ service](https://index.docker.io/associate/github/). Then follow the instructions to authorize and link your GitHub account to Docker.io. -#### Creating a Trusted Build +#### Creating an Automated Build -You can [create a Trusted Build](https://index.docker.io/builds/github/select/) +You can [create an Automated Build](https://index.docker.io/builds/github/select/) from any of your public or private GitHub repositories with a `Dockerfile`. #### GitHub organizations @@ -59,7 +58,7 @@ organization on GitHub. #### GitHub service hooks You can follow the below steps to configure the GitHub service hooks for your -Trusted Build: +Automated Build: @@ -84,13 +83,13 @@ Trusted Build:
-### Setting up Trusted Builds with BitBucket +### Setting up Automated Builds with BitBucket -In order to setup a Trusted Build, you need to first link your +In order to setup an Automated Build, you need to first link your [Docker.io]( https://index.docker.io) account with a BitBucket one. This will allow the registry to see your repositories. -Click on the [Trusted Builds tab](https://index.docker.io/builds/) to +Click on the [Automated Builds tab](https://index.docker.io/builds/) to get started and then select [+ Add New](https://index.docker.io/builds/add/). @@ -100,14 +99,14 @@ service](https://index.docker.io/associate/bitbucket/). Then follow the instructions to authorize and link your BitBucket account to Docker.io. -#### Creating a Trusted Build +#### Creating an Automated Build You can [create a Trusted Build](https://index.docker.io/builds/bitbucket/select/) from any of your public or private BitBucket repositories with a `Dockerfile`. -### The Dockerfile and Trusted Builds +### The Dockerfile and Automated Builds During the build process, we copy the contents of your `Dockerfile`. We also add it to the [Docker.io](https://index.docker.io) for the Docker community @@ -120,20 +119,19 @@ repository's full description. > **Warning:** > If you change the full description after a build, it will be -> rewritten the next time the Trusted Build has been built. To make changes, +> rewritten the next time the Automated Build has been built. To make changes, > modify the README.md from the Git repository. We will look for a README.md > in the same directory as your `Dockerfile`. ### Build triggers -If you need another way to trigger your Trusted Builds outside of GitHub +If you need another way to trigger your Automated Builds outside of GitHub or BitBucket, you can setup a build trigger. When you turn on the build -trigger for a Trusted Build, it will give you a URL to which you can -send POST requests. This will trigger the Trusted Build process, which -is similar to GitHub web hooks. +trigger for an Automated Build, it will give you a URL to which you can +send POST requests. This will trigger the Automated Build process, which +is similar to GitHub webhooks. -Build Triggers are available under the Settings tab of each Trusted -Build. +Build Triggers are available under the Settings tab of each Automated Build. > **Note:** > You can only trigger one build at a time and no more than one @@ -144,10 +142,10 @@ Build. ### Webhooks -Also available for Trusted Builds are Webhooks. Webhooks can be called +Also available for Automated Builds are Webhooks. Webhooks can be called after a successful repository push is made. -The web hook call will generate a HTTP POST with the following JSON +The webhook call will generate a HTTP POST with the following JSON payload: ``` @@ -181,7 +179,7 @@ payload: } ``` -Webhooks are available under the Settings tab of each Trusted +Webhooks are available under the Settings tab of each Automated Build. > **Note:** If you want to test your webhook out then we recommend using @@ -190,15 +188,15 @@ Build. ### Repository links -Repository links are a way to associate one Trusted Build with another. If one -gets updated, linking system also triggers a build for the other Trusted Build. -This makes it easy to keep your Trusted Builds up to date. +Repository links are a way to associate one Automated Build with another. If one +gets updated, linking system also triggers a build for the other Automated Build. +This makes it easy to keep your Automated Builds up to date. -To add a link, go to the settings page of a Trusted Build and click on +To add a link, go to the settings page of an Automated Build and click on *Repository Links*. Then enter the name of the repository that you want have linked. > **Warning:** > You can add more than one repository link, however, you should -> be very careful. Creating a two way relationship between Trusted Builds will +> be very careful. Creating a two way relationship between Automated Builds will > cause a never ending build loop. diff --git a/docs/sources/docker-io/index.md b/docs/sources/docker-io/index.md new file mode 100644 index 000000000..dc83f0b28 --- /dev/null +++ b/docs/sources/docker-io/index.md @@ -0,0 +1,8 @@ +# Docker.io + +## Contents: + +- [Accounts](accounts/) +- [Repositories](repos/) +- [Automated Builds](builds/) + diff --git a/docs/sources/examples.md b/docs/sources/examples.md index f1d1567f5..9dcd67a64 100644 --- a/docs/sources/examples.md +++ b/docs/sources/examples.md @@ -1,25 +1,9 @@ - # Examples -## Introduction: - -Here are some examples of how to use Docker to create running processes, -starting from a very simple *Hello World* and progressing to more -substantial services like those which you might find in production. - -## Contents: - - - [Check your Docker install](hello_world/) - - [Hello World](hello_world/#hello-world) - - [Hello World Daemon](hello_world/#hello-world-daemon) - - [Node.js Web App](nodejs_web_app/) - - [Redis Service](running_redis_service/) - - [SSH Daemon Service](running_ssh_service/) - - [CouchDB Service](couchdb_data_volumes/) - - [PostgreSQL Service](postgresql_service/) - - [Building an Image with MongoDB](mongodb/) - - [Riak Service](running_riak_service/) - - [Using Supervisor with Docker](using_supervisord/) - - [Process Management with CFEngine](cfengine_process_management/) - - [Python Web App](python_web_app/) - + - [Dockerizing a Node.js Web App](nodejs_web_app/) + - [Dockerizing a Redis Service](running_redis_service/) + - [Dockerizing an SSH Daemon Service](running_ssh_service/) + - [Dockerizing a CouchDB Service](couchdb_data_volumes/) + - [Dockerizing a PostgreSQL Service](postgresql_service/) + - [Dockerizing MongoDB](mongodb/) + - [Dockerizing a Riak Service](running_riak_service/) diff --git a/docs/sources/examples/apt-cacher-ng.md b/docs/sources/examples/apt-cacher-ng.md index 0293ac5d0..34e4a4bf0 100644 --- a/docs/sources/examples/apt-cacher-ng.md +++ b/docs/sources/examples/apt-cacher-ng.md @@ -1,14 +1,10 @@ -page_title: Running an apt-cacher-ng service +page_title: Dockerizing an apt-cacher-ng service page_description: Installing and running an apt-cacher-ng service page_keywords: docker, example, package installation, networking, debian, ubuntu -# Apt-Cacher-ng Service +# Dockerizing an Apt-Cacher-ng Service > **Note**: -> -> - This example assumes you have Docker running in daemon mode. For -> more information please see [*Check your Docker -> install*](../hello_world/#running-examples). > - **If you don't like sudo** then see [*Giving non-root > access*](/installation/binaries/#dockergroup). > - **If you're using OS X or docker via TCP** then you shouldn't use diff --git a/docs/sources/examples/couchdb_data_volumes.md b/docs/sources/examples/couchdb_data_volumes.md index ec1a0d947..44043d641 100644 --- a/docs/sources/examples/couchdb_data_volumes.md +++ b/docs/sources/examples/couchdb_data_volumes.md @@ -1,14 +1,10 @@ -page_title: Sharing data between 2 couchdb databases +page_title: Dockerizing a CouchDB Service page_description: Sharing data between 2 couchdb databases page_keywords: docker, example, package installation, networking, couchdb, data volumes -# CouchDB Service +# Dockerizing a CouchDB Service > **Note**: -> -> - This example assumes you have Docker running in daemon mode. For -> more information please see [*Check your Docker -> install*](../hello_world/#running-examples). > - **If you don't like sudo** then see [*Giving non-root > access*](/installation/binaries/#dockergroup) diff --git a/docs/sources/examples/example_header.inc b/docs/sources/examples/example_header.inc deleted file mode 100644 index 5841141e5..000000000 --- a/docs/sources/examples/example_header.inc +++ /dev/null @@ -1,8 +0,0 @@ - -.. note:: - - * This example assumes you have Docker running in daemon mode. For - more information please see :ref:`running_examples`. - * **If you don't like sudo** then see :ref:`dockergroup` - * **If you're using OS X or docker via TCP** then you shouldn't use `sudo` - diff --git a/docs/sources/examples/hello_world.md b/docs/sources/examples/hello_world.md deleted file mode 100644 index 177857816..000000000 --- a/docs/sources/examples/hello_world.md +++ /dev/null @@ -1,162 +0,0 @@ -page_title: Hello world example -page_description: A simple hello world example with Docker -page_keywords: docker, example, hello world - -# Check your Docker installation - -This guide assumes you have a working installation of Docker. To check -your Docker install, run the following command: - - # Check that you have a working install - $ sudo docker info - -If you get `docker: command not found` or something -like `/var/lib/docker/repositories: permission denied` -you may have an incomplete Docker installation or insufficient -privileges to access docker on your machine. - -Please refer to [*Installation*](/installation/) -for installation instructions. - -## Hello World - -> **Note**: -> -> - This example assumes you have Docker running in daemon mode. For -> more information please see [*Check your Docker -> install*](#check-your-docker-installation). -> - **If you don't like sudo** then see [*Giving non-root -> access*](/installation/binaries/#dockergroup) - -This is the most basic example available for using Docker. - -Download the small base image named `busybox`: - - # Download a busybox image - $ sudo docker pull busybox - -The `busybox` image is a minimal Linux system. You can do the same with -any number of other images, such as `debian`, `ubuntu` or `centos`. The -images can be found and retrieved using the -[Docker.io](http://index.docker.io) registry. - - $ sudo docker run busybox /bin/echo hello world - -This command will run a simple `echo` command, that -will echo `hello world` back to the console over -standard out. - -**Explanation:** - -- **"sudo"** execute the following commands as user *root* -- **"docker run"** run a command in a new container -- **"busybox"** is the image we are running the command in. -- **"/bin/echo"** is the command we want to run in the container -- **"hello world"** is the input for the echo command - -**Video:** - -See the example in action - - - - - -## Hello World Daemon - -> **Note**: -> -> - This example assumes you have Docker running in daemon mode. For -> more information please see [*Check your Docker -> install*](#check-your-docker-installation). -> - **If you don't like sudo** then see [*Giving non-root -> access*](/installation/binaries/#dockergroup) - -And now for the most boring daemon ever written! - -We will use the Ubuntu image to run a simple hello world daemon that -will just print hello world to standard out every second. It will -continue to do this until we stop it. - -**Steps:** - - $ container_id=$(sudo docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done") - -We are going to run a simple hello world daemon in a new container made -from the `ubuntu` image. - - - **"sudo docker run -d "** run a command in a new container. We pass - "-d" so it runs as a daemon. - - **"ubuntu"** is the image we want to run the command inside of. - - **"/bin/sh -c"** is the command we want to run in the container - - **"while true; do echo hello world; sleep 1; done"** is the mini - script we want to run, that will just print hello world once a - second until we stop it. - - **$container_id** the output of the run command will return a - container id, we can use in future commands to see what is going on - with this process. - - - - $ sudo docker logs $container_id - -Check the logs make sure it is working correctly. - - - **"docker logs**" This will return the logs for a container - - **$container_id** The Id of the container we want the logs for. - - - - $ sudo docker attach --sig-proxy=false $container_id - -Attach to the container to see the results in real-time. - - - **"docker attach**" This will allow us to attach to a background - process to see what is going on. - - **"–sig-proxy=false"** Do not forward signals to the container; - allows us to exit the attachment using Control-C without stopping - the container. - - **$container_id** The Id of the container we want to attach to. - -Exit from the container attachment by pressing Control-C. - - $ sudo docker ps - -Check the process list to make sure it is running. - - - **"docker ps"** this shows all running process managed by docker - - - - $ sudo docker stop $container_id - -Stop the container, since we don't need it anymore. - - - **"docker stop"** This stops a container - - **$container_id** The Id of the container we want to stop. - - - - $ sudo docker ps - -Make sure it is really stopped. - -**Video:** - -See the example in action - - - - - -The next example in the series is a [*Node.js Web App*]( -../nodejs_web_app/#nodejs-web-app) example, or you could skip to any of the -other examples: - - - [*Node.js Web App*](../nodejs_web_app/#nodejs-web-app) - - [*Redis Service*](../running_redis_service/#running-redis-service) - - [*SSH Daemon Service*](../running_ssh_service/#running-ssh-service) - - [*CouchDB Service*](../couchdb_data_volumes/#running-couchdb-service) - - [*PostgreSQL Service*](../postgresql_service/#postgresql-service) - - [*Building an Image with MongoDB*](../mongodb/#mongodb-image) - - [*Python Web App*](../python_web_app/#python-web-app) diff --git a/docs/sources/examples/mongodb.md b/docs/sources/examples/mongodb.md index e397cb0e5..5b0b7d3e2 100644 --- a/docs/sources/examples/mongodb.md +++ b/docs/sources/examples/mongodb.md @@ -2,7 +2,7 @@ page_title: Dockerizing MongoDB page_description: Creating a Docker image with MongoDB pre-installed using a Dockerfile and sharing the image on Docker.io page_keywords: docker, dockerize, dockerizing, article, example, docker.io, platform, package, installation, networking, mongodb, containers, images, image, sharing, dockerfile, build, auto-building, virtualization, framework -# Dockerizing MongoDB +# Dockerizing MongoDB ## Introduction @@ -18,17 +18,10 @@ instances will bring several benefits, such as: - Ready to run and start working within milliseconds; - Based on globally accessible and shareable images. -> **Note:** -> -> This example assumes you have Docker running in daemon mode. To verify, -> try running `sudo docker info`. -> For more information, please see: [*Check your Docker installation*]( -> /examples/hello_world/#running-examples). - > **Note:** > > If you do **_not_** like `sudo`, you might want to check out: -> [*Giving non-root access*](installation/binaries/#giving-non-root-access). +> [*Giving non-root access*](/installation/binaries/#giving-non-root-access). ## Creating a Dockerfile for MongoDB @@ -101,8 +94,7 @@ Now save the file and let's build our image. > **Note:** > -> The full version of this `Dockerfile` can be found [here](/ -> /examples/mongodb/Dockerfile). +> The full version of this `Dockerfile` can be found [here](/examples/mongodb/Dockerfile). ## Building the MongoDB Docker image @@ -157,8 +149,6 @@ as daemon process(es). # Usage: mongo --port $ mongo --port 12345 -## Learn more - - - [Linking containers](/use/working_with_links_names/) - - [Cross-host linking containers](/use/ambassador_pattern_linking/) + - [Linking containers](/userguide/dockerlinks) + - [Cross-host linking containers](/articles/ambassador_pattern_linking/) - [Creating a Trusted Build](/docker-io/builds/#trusted-builds) diff --git a/docs/sources/examples/nodejs_web_app.md b/docs/sources/examples/nodejs_web_app.md index fe52aa945..99946e99e 100644 --- a/docs/sources/examples/nodejs_web_app.md +++ b/docs/sources/examples/nodejs_web_app.md @@ -1,14 +1,10 @@ -page_title: Running a Node.js app on CentOS -page_description: Installing and running a Node.js app on CentOS +page_title: Dockerizing a Node.js Web App +page_description: Installing and running a Node.js app with Docker page_keywords: docker, example, package installation, node, centos -# Node.js Web App +# Dockerizing a Node.js Web App > **Note**: -> -> - This example assumes you have Docker running in daemon mode. For -> more information please see [*Check your Docker -> install*](../hello_world/#running-examples). > - **If you don't like sudo** then see [*Giving non-root > access*](/installation/binaries/#dockergroup) @@ -187,11 +183,10 @@ Now you can call your app using `curl` (install if needed via: Content-Length: 12 Date: Sun, 02 Jun 2013 03:53:22 GMT Connection: keep-alive - + Hello World We hope this tutorial helped you get up and running with Node.js and CentOS on Docker. You can get the full source code at [https://github.com/gasi/docker-node-hello](https://github.com/gasi/docker-node-hello). -Continue to [*Redis Service*](../running_redis_service/#running-redis-service). diff --git a/docs/sources/examples/postgresql_service.md b/docs/sources/examples/postgresql_service.md index 6f0a3e6bb..b931fd8ba 100644 --- a/docs/sources/examples/postgresql_service.md +++ b/docs/sources/examples/postgresql_service.md @@ -1,26 +1,22 @@ -page_title: PostgreSQL service How-To +page_title: Dockerizing PostgreSQL page_description: Running and installing a PostgreSQL service page_keywords: docker, example, package installation, postgresql -# PostgreSQL Service +# Dockerizing PostgreSQL > **Note**: -> -> - This example assumes you have Docker running in daemon mode. For -> more information please see [*Check your Docker -> install*](../hello_world/#running-examples). > - **If you don't like sudo** then see [*Giving non-root > access*](/installation/binaries/#dockergroup) ## Installing PostgreSQL on Docker -Assuming there is no Docker image that suits your needs in [the index]( -http://index.docker.io), you can create one yourself. +Assuming there is no Docker image that suits your needs on the [Docker +Hub]( http://index.docker.io), you can create one yourself. -Start by creating a new Dockerfile: +Start by creating a new `Dockerfile`: > **Note**: -> This PostgreSQL setup is for development only purposes. Refer to the +> This PostgreSQL setup is for development-only purposes. Refer to the > PostgreSQL documentation to fine-tune these settings so that it is > suitably secure. @@ -32,7 +28,7 @@ Start by creating a new Dockerfile: MAINTAINER SvenDowideit@docker.com # Add the PostgreSQL PGP key to verify their Debian packages. - # It should be the same key as https://www.postgresql.org/media/keys/ACCC4CF8.asc + # It should be the same key as https://www.postgresql.org/media/keys/ACCC4CF8.asc RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8 # Add PostgreSQL's repository. It contains the most recent stable release @@ -87,11 +83,11 @@ And run the PostgreSQL server container (in the foreground): $ sudo docker run --rm -P --name pg_test eg_postgresql There are 2 ways to connect to the PostgreSQL server. We can use [*Link -Containers*](/use/working_with_links_names/#working-with-links-names), -or we can access it from our host (or the network). +Containers*](/userguide/dockerlinks), or we can access it from our host +(or the network). > **Note**: -> The `-rm` removes the container and its image when +> The `--rm` removes the container and its image when > the container exists successfully. ### Using container linking diff --git a/docs/sources/examples/python_web_app.md b/docs/sources/examples/python_web_app.md deleted file mode 100644 index f4b76d061..000000000 --- a/docs/sources/examples/python_web_app.md +++ /dev/null @@ -1,127 +0,0 @@ -page_title: Python Web app example -page_description: Building your own python web app using docker -page_keywords: docker, example, python, web app - -# Python Web App - -> **Note**: -> -> - This example assumes you have Docker running in daemon mode. For -> more information please see [*Check your Docker -> install*](../hello_world/#running-examples). -> - **If you don't like sudo** then see [*Giving non-root -> access*](/installation/binaries/#dockergroup) - -While using Dockerfiles is the preferred way to create maintainable and -repeatable images, its useful to know how you can try things out and -then commit your live changes to an image. - -The goal of this example is to show you how you can modify your own -Docker images by making changes to a running container, and then saving -the results as a new image. We will do that by making a simple `hello -world` Flask web application image. - -## Download the initial image - -Download the `shykes/pybuilder` Docker image from the `http://index.docker.io` -registry. - -This image contains a `buildapp` script to download -the web app and then `pip install` any required -modules, and a `runapp` script that finds the -`app.py` and runs it. - - $ sudo docker pull shykes/pybuilder - -> **Note**: -> This container was built with a very old version of docker (May 2013 - -> see [shykes/pybuilder](https://github.com/shykes/pybuilder) ), when the -> Dockerfile format was different, but the image can -> still be used now. - -## Interactively make some modifications - -We then start a new container running interactively using the image. -First, we set a `URL` variable that points to a -tarball of a simple helloflask web app, and then we run a command -contained in the image called `buildapp`, passing it -the `$URL` variable. The container is given a name -`pybuilder_run` which we will use in the next steps. - -While this example is simple, you could run any number of interactive -commands, try things out, and then exit when you're done. - - $ sudo docker run -i -t --name pybuilder_run shykes/pybuilder bash - - $$ URL=http://github.com/shykes/helloflask/archive/master.tar.gz - $$ /usr/local/bin/buildapp $URL - [...] - $$ exit - -## Commit the container to create a new image - -Save the changes we just made in the container to a new image called -`/builds/github.com/shykes/helloflask/master`. You -now have 3 different ways to refer to the container: name -`pybuilder_run`, short-id `c8b2e8228f11`, or long-id -`c8b2e8228f11b8b3e492cbf9a49923ae66496230056d61e07880dc74c5f495f9`. - - $ sudo docker commit pybuilder_run /builds/github.com/shykes/helloflask/master - c8b2e8228f11b8b3e492cbf9a49923ae66496230056d61e07880dc74c5f495f9 - -## Run the new image to start the web worker - -Use the new image to create a new container with network port 5000 -mapped to a local port - - $ sudo docker run -d -p 5000 --name web_worker /builds/github.com/shykes/helloflask/master /usr/local/bin/runapp - - - **"docker run -d "** run a command in a new container. We pass "-d" - so it runs as a daemon. - - **"-p 5000"** the web app is going to listen on this port, so it - must be mapped from the container to the host system. - - **/usr/local/bin/runapp** is the command which starts the web app. - -## View the container logs - -View the logs for the new `web_worker` container and -if everything worked as planned you should see the line -`Running on http://0.0.0.0:5000/` in the log output. - -To exit the view without stopping the container, hit Ctrl-C, or open -another terminal and continue with the example while watching the result -in the logs. - - $ sudo docker logs -f web_worker - * Running on http://0.0.0.0:5000/ - -## See the webapp output - -Look up the public-facing port which is NAT-ed. Find the private port -used by the container and store it inside of the `WEB_PORT` -variable. - -Access the web app using the `curl` binary. If -everything worked as planned you should see the line -`Hello world!` inside of your console. - - $ WEB_PORT=$(sudo docker port web_worker 5000 | awk -F: '{ print $2 }') - - # install curl if necessary, then ... - $ curl http://127.0.0.1:$WEB_PORT - Hello world! - -## Clean up example containers and images - - $ sudo docker ps --all - -List `--all` the Docker containers. If this -container had already finished running, it will still be listed here -with a status of `Exit 0`. - - $ sudo docker stop web_worker - $ sudo docker rm web_worker pybuilder_run - $ sudo docker rmi /builds/github.com/shykes/helloflask/master shykes/pybuilder:latest - -And now stop the running web worker, and delete the containers, so that -we can then delete the images that we used. diff --git a/docs/sources/examples/running_redis_service.md b/docs/sources/examples/running_redis_service.md index ca6704862..0eeef0625 100644 --- a/docs/sources/examples/running_redis_service.md +++ b/docs/sources/examples/running_redis_service.md @@ -1,16 +1,8 @@ -page_title: Running a Redis service +page_title: Dockerizing a Redis service page_description: Installing and running an redis service page_keywords: docker, example, package installation, networking, redis -# Redis Service - -> **Note**: -> -> - This example assumes you have Docker running in daemon mode. For -> more information please see [*Check your Docker -> install*](../hello_world/#running-examples). -> - **If you don't like sudo** then see [*Giving non-root -> access*](/installation/binaries/#dockergroup) +# Dockerizing a Redis Service Very simple, no frills, Redis service attached to a web application using a link. diff --git a/docs/sources/examples/running_riak_service.md b/docs/sources/examples/running_riak_service.md index 852035f9a..098cc9094 100644 --- a/docs/sources/examples/running_riak_service.md +++ b/docs/sources/examples/running_riak_service.md @@ -1,30 +1,21 @@ -page_title: Running a Riak service +page_title: Dockerizing a Riak service page_description: Build a Docker image with Riak pre-installed page_keywords: docker, example, package installation, networking, riak -# Riak Service - -> **Note**: -> -> - This example assumes you have Docker running in daemon mode. For -> more information please see [*Check your Docker -> install*](../hello_world/#running-examples). -> - **If you don't like sudo** then see [*Giving non-root -> access*](/installation/binaries/#dockergroup) +# Dockerizing a Riak Service The goal of this example is to show you how to build a Docker image with Riak pre-installed. ## Creating a Dockerfile -Create an empty file called Dockerfile: +Create an empty file called `Dockerfile`: $ touch Dockerfile Next, define the parent image you want to use to build your image on top of. We'll use [Ubuntu](https://index.docker.io/_/ubuntu/) (tag: -`latest`), which is available on the [docker -index](http://index.docker.io): +`latest`), which is available on [Docker Hub](http://index.docker.io): # Riak # @@ -101,7 +92,7 @@ are started: ## Create a supervisord configuration file Create an empty file called `supervisord.conf`. Make -sure it's at the same directory level as your Dockerfile: +sure it's at the same directory level as your `Dockerfile`: touch supervisord.conf diff --git a/docs/sources/examples/running_ssh_service.md b/docs/sources/examples/running_ssh_service.md index 534e22e1b..5be2c7053 100644 --- a/docs/sources/examples/running_ssh_service.md +++ b/docs/sources/examples/running_ssh_service.md @@ -1,17 +1,10 @@ -page_title: Running an SSH service -page_description: Installing and running an sshd service +page_title: Dockerizing an SSH service +page_description: Installing and running an SSHd service on Docker page_keywords: docker, example, package installation, networking -# SSH Daemon Service +# Dockerizing an SSH Daemon Service -> **Note:** -> - This example assumes you have Docker running in daemon mode. For -> more information please see [*Check your Docker -> install*](../hello_world/#running-examples). -> - **If you don't like sudo** then see [*Giving non-root -> access*](/installation/binaries/#dockergroup) - -The following Dockerfile sets up an sshd service in a container that you +The following `Dockerfile` sets up an SSHd service in a container that you can use to connect to and inspect other container's volumes, or to get quick access to a test container. @@ -27,7 +20,7 @@ quick access to a test container. RUN apt-get update RUN apt-get install -y openssh-server - RUN mkdir /var/run/sshd + RUN mkdir /var/run/sshd RUN echo 'root:screencast' |chpasswd EXPOSE 22 @@ -37,16 +30,15 @@ Build the image using: $ sudo docker build --rm -t eg_sshd . -Then run it. You can then use `docker port` to find -out what host port the container's port 22 is mapped to: +Then run it. You can then use `docker port` to find out what host port +the container's port 22 is mapped to: $ sudo docker run -d -P --name test_sshd eg_sshd $ sudo docker port test_sshd 22 0.0.0.0:49154 -And now you can ssh to port `49154` on the Docker -daemon's host IP address (`ip address` or -`ifconfig` can tell you that): +And now you can ssh to port `49154` on the Docker daemon's host IP +address (`ip address` or `ifconfig` can tell you that): $ ssh root@192.168.1.2 -p 49154 # The password is ``screencast``. @@ -58,3 +50,4 @@ container, and then removing the image. $ sudo docker stop test_sshd $ sudo docker rm test_sshd $ sudo docker rmi eg_sshd + diff --git a/docs/sources/faq.md b/docs/sources/faq.md index 2494f33e9..8dbdfd184 100644 --- a/docs/sources/faq.md +++ b/docs/sources/faq.md @@ -142,12 +142,11 @@ running in parallel. ### How do I connect Docker containers? Currently the recommended way to link containers is via the link -primitive. You can see details of how to [work with links here]( -http://docs.docker.io/use/working_with_links_names/). +primitive. You can see details of how to [work with links +here](/userguide/dockerlinks). Also of useful when enabling more flexible service portability is the -[Ambassador linking pattern]( -http://docs.docker.io/use/ambassador_pattern_linking/). +[Ambassador linking pattern](/articles/ambassador_pattern_linking/). ### How do I run more than one process in a Docker container? @@ -156,8 +155,7 @@ http://supervisord.org/), runit, s6, or daemontools can do the trick. Docker will start up the process management daemon which will then fork to run additional processes. As long as the processor manager daemon continues to run, the container will continue to as well. You can see a more substantial -example [that uses supervisord here]( -http://docs.docker.io/examples/using_supervisord/). +example [that uses supervisord here](/articles/using_supervisord/). ### What platforms does Docker run on? @@ -207,5 +205,5 @@ You can find more answers on: - [Ask questions on Stackoverflow](http://stackoverflow.com/search?q=docker) - [Join the conversation on Twitter](http://twitter.com/docker) -Looking for something else to read? Checkout the [*Hello World*]( -../examples/hello_world/#hello-world) example. +Looking for something else to read? Checkout the [User +Guide](/userguide/). diff --git a/docs/sources/index.md b/docs/sources/index.md index 16f29f570..f7295d9c1 100644 --- a/docs/sources/index.md +++ b/docs/sources/index.md @@ -6,8 +6,6 @@ page_keywords: docker, introduction, documentation, about, technology, understan **Develop, Ship and Run Any Application, Anywhere** -## Introduction - [**Docker**](https://www.docker.io) is a platform for developers and sysadmins to develop, ship, and run applications. Docker consists of: @@ -78,22 +76,17 @@ section](introduction/understanding-docker.md): > [Click here to go to the Understanding > Docker section](introduction/understanding-docker.md). -Next we get [**practical** with the Working with Docker -section](introduction/working-with-docker.md) and you can learn about: +### Installation Guides - - Docker on the command line; - - Get introduced to your first Docker commands; - - Get to know your way around the basics of Docker operation. - -> [Click here to go to the Working with -> Docker section](introduction/working-with-docker.md). - -If you want to see how to install Docker you can jump to the +Then we'll learn how to install Docker on a variety of platforms in our [installation](/installation/#installation) section. -> **Note**: -> We know how valuable your time is so you if you want to get started -> with Docker straight away don't hesitate to jump to [Working with -> Docker](introduction/working-with-docker.md). For a fuller -> understanding of Docker though we do recommend you read [Understanding -> Docker]( introduction/understanding-docker.md). +> [Click here to go to the Installation +> section](/installation/#installation). + +### Docker User Guide + +Once you've gotten Docker installed we recommend you step through our [Docker User Guide](/userguide/), which will give you an in depth introduction to Docker. + +> [Click here to go to the Docker User Guide](/userguide/). + diff --git a/docs/sources/installation/amazon.md b/docs/sources/installation/amazon.md index f7abec155..86443147e 100644 --- a/docs/sources/installation/amazon.md +++ b/docs/sources/installation/amazon.md @@ -53,8 +53,7 @@ add the *ubuntu* user to it so that you don't have to use `sudo` for every Docker command. Once you`ve got Docker installed, you're ready to try it out – head on -over to the [*First steps with Docker*](/use/basics/) or -[*Examples*](/examples/) section. +over to the [User Guide](/userguide). ## Amazon QuickStart (Release Candidate - March 2014) @@ -94,4 +93,4 @@ QuickStart*](#amazon-quickstart) to pick an image (or use one of your own) and skip the step with the *User Data*. Then continue with the [*Ubuntu*](../ubuntulinux/#ubuntu-linux) instructions. -Continue with the [*Hello World*](/examples/hello_world/#hello-world) example. +Continue with the [User Guide](/userguide/). diff --git a/docs/sources/installation/binaries.md b/docs/sources/installation/binaries.md index 494530a23..97e2f93c4 100644 --- a/docs/sources/installation/binaries.md +++ b/docs/sources/installation/binaries.md @@ -56,20 +56,17 @@ Linux kernel (it even builds on OSX!). ## Giving non-root access -The `docker` daemon always runs as the root user, -and since Docker version 0.5.2, the `docker` daemon -binds to a Unix socket instead of a TCP port. By default that Unix -socket is owned by the user *root*, and so, by default, you can access -it with `sudo`. +The `docker` daemon always runs as the root user, and the `docker` +daemon binds to a Unix socket instead of a TCP port. By default that +Unix socket is owned by the user *root*, and so, by default, you can +access it with `sudo`. -Starting in version 0.5.3, if you (or your Docker installer) create a -Unix group called *docker* and add users to it, then the -`docker` daemon will make the ownership of the Unix -socket read/writable by the *docker* group when the daemon starts. The -`docker` daemon must always run as the root user, -but if you run the `docker` client as a user in the -*docker* group then you don't need to add `sudo` to -all the client commands. +If you (or your Docker installer) create a Unix group called *docker* +and add users to it, then the `docker` daemon will make the ownership of +the Unix socket read/writable by the *docker* group when the daemon +starts. The `docker` daemon must always run as the root user, but if you +run the `docker` client as a user in the *docker* group then you don't +need to add `sudo` to all the client commands. > **Warning**: > The *docker* group (or the group specified with `-G`) is root-equivalent; @@ -93,4 +90,4 @@ Then follow the regular installation steps. # run a container and open an interactive shell in the container $ sudo ./docker run -i -t ubuntu /bin/bash -Continue with the [*Hello World*](/examples/hello_world/#hello-world) example. +Continue with the [User Guide](/userguide/). diff --git a/docs/sources/installation/centos.md b/docs/sources/installation/centos.md index 7d2a9ae5d..3966d0f09 100644 --- a/docs/sources/installation/centos.md +++ b/docs/sources/installation/centos.md @@ -2,23 +2,12 @@ page_title: Installation on CentOS page_description: Instructions for installing Docker on CentOS page_keywords: Docker, Docker documentation, requirements, linux, centos, epel, docker.io, docker-io -# CentOS +# CentOS -> **Note**: -> Docker is still under heavy development! We don't recommend using it in -> production yet, but we're getting closer with each release. Please see -> our blog post, [Getting to Docker 1.0]( -> http://blog.docker.io/2013/08/getting-to-docker-1-0/) - -> **Note**: -> This is a community contributed installation path. The only `official` -> installation is using the [*Ubuntu*](../ubuntulinux/#ubuntu-linux) -> installation path. This version may be out of date because it depends on -> some binaries to be updated and published. - -The Docker package is available via the EPEL repository. These instructions work -for CentOS 6 and later. They will likely work for other binary compatible EL6 -distributions such as Scientific Linux, but they haven't been tested. +The Docker package is available via the EPEL repository. These +instructions work for CentOS 6 and later. They will likely work for +other binary compatible EL6 distributions such as Scientific Linux, but +they haven't been tested. Please note that this package is part of [Extra Packages for Enterprise Linux (EPEL)](https://fedoraproject.org/wiki/EPEL), a community effort @@ -27,13 +16,13 @@ to create and maintain additional packages for the RHEL distribution. Also note that due to the current Docker limitations, Docker is able to run only on the **64 bit** architecture. -To run Docker, you will need [CentOS6](http://www.centos.org) or higher, with -a kernel version 2.6.32-431 or higher as this has specific kernel fixes -to allow Docker to run. +To run Docker, you will need [CentOS6](http://www.centos.org) or higher, +with a kernel version 2.6.32-431 or higher as this has specific kernel +fixes to allow Docker to run. ## Installation -Firstly, you need to ensure you have the EPEL repository enabled. Please +Firstly, you need to ensure you have the EPEL repository enabled. Please follow the [EPEL installation instructions]( https://fedoraproject.org/wiki/EPEL#How_can_I_use_these_extra_packages.3F). @@ -59,7 +48,7 @@ If we want Docker to start at boot, we should also: $ sudo chkconfig docker on Now let's verify that Docker is working. First we'll need to get the latest -centos image. +`centos` image. $ sudo docker pull centos:latest @@ -73,15 +62,15 @@ This should generate some output similar to: REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE centos latest 0b443ba03958 2 hours ago 297.6 MB -Run a simple bash shell to test the image: +Run a simple bash shell to test the image: $ sudo docker run -i -t centos /bin/bash -If everything is working properly, you'll get a simple bash prompt. Type exit to continue. +If everything is working properly, you'll get a simple bash prompt. Type +exit to continue. -**Done!** -You can either continue with the [*Hello World*](/examples/hello_world/#hello-world) example, -or explore and build on the images yourself. +**Done!** You can either continue with the [Docker User +Guide](/userguide/) or explore and build on the images yourself. ## Issues? diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index def8cb77c..0ad54b432 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -38,18 +38,18 @@ Which should download the `ubuntu` image, and then start `bash` in a container. ### Giving non-root access -The `docker` daemon always runs as the `root` user, and since Docker -version 0.5.2, the `docker` daemon binds to a Unix socket instead of a -TCP port. By default that Unix socket is owned by the user `root`, and -so, by default, you can access it with `sudo`. +The `docker` daemon always runs as the `root` user and the `docker` +daemon binds to a Unix socket instead of a TCP port. By default that +Unix socket is owned by the user `root`, and so, by default, you can +access it with `sudo`. -Starting in version 0.5.3, if you (or your Docker installer) create a -Unix group called `docker` and add users to it, then the `docker` daemon -will make the ownership of the Unix socket read/writable by the `docker` -group when the daemon starts. The `docker` daemon must always run as the -root user, but if you run the `docker` client as a user in the `docker` -group then you don't need to add `sudo` to all the client commands. From -Docker 0.9.0 you can use the `-G` flag to specify an alternative group. +If you (or your Docker installer) create a Unix group called `docker` +and add users to it, then the `docker` daemon will make the ownership of +the Unix socket read/writable by the `docker` group when the daemon +starts. The `docker` daemon must always run as the root user, but if you +run the `docker` client as a user in the `docker` group then you don't +need to add `sudo` to all the client commands. From Docker 0.9.0 you can +use the `-G` flag to specify an alternative group. > **Warning**: > The `docker` group (or the group specified with the `-G` flag) is @@ -70,3 +70,7 @@ Docker 0.9.0 you can use the `-G` flag to specify an alternative group. # Restart the Docker daemon. $ sudo service docker restart +## What next? + +Continue with the [User Guide](/userguide/). + diff --git a/docs/sources/installation/fedora.md b/docs/sources/installation/fedora.md index d615d4f6c..bcd54e6bd 100644 --- a/docs/sources/installation/fedora.md +++ b/docs/sources/installation/fedora.md @@ -48,5 +48,7 @@ Now let's verify that Docker is working. $ sudo docker run -i -t fedora /bin/bash -**Done!**, now continue with the [*Hello -World*](/examples/hello_world/#hello-world) example. +## What next? + +Continue with the [User Guide](/userguide/). + diff --git a/docs/sources/installation/mac.md b/docs/sources/installation/mac.md index ef91081a5..135130ef8 100644 --- a/docs/sources/installation/mac.md +++ b/docs/sources/installation/mac.md @@ -40,9 +40,8 @@ virtual machine and run the Docker daemon. (but least secure) is to just hit [Enter]. This passphrase is used by the `boot2docker ssh` command. - -Once you have an initialized virtual machine, you can `boot2docker stop` and -`boot2docker start` it. +Once you have an initialized virtual machine, you can `boot2docker stop` +and `boot2docker start` it. ## Upgrading @@ -60,29 +59,19 @@ To upgrade: boot2docker start ``` - ## Running Docker From your terminal, you can try the “hello world” example. Run: $ docker run ubuntu echo hello world -This will download the ubuntu image and print hello world. +This will download the `ubuntu` image and print `hello world`. -# Further details +## Container port redirection -The Boot2Docker management tool provides some commands: - -``` -$ ./boot2docker -Usage: ./boot2docker [] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|delete|download|version} [] -``` - -## Container port redirection - -The latest version of `boot2docker` sets up two network adaptors: one using NAT +The latest version of `boot2docker` sets up two network adapters: one using NAT to allow the VM to download images and files from the Internet, and one host only -network adaptor to which the container's ports will be exposed on. +network adapter to which the container's ports will be exposed on. If you run a container with an exposed port: @@ -103,6 +92,17 @@ If you want to share container ports with other computers on your LAN, you will need to set up [NAT adaptor based port forwarding]( https://github.com/boot2docker/boot2docker/blob/master/doc/WORKAROUNDS.md) +# Further details +The Boot2Docker management tool provides some commands: + +``` +$ ./boot2docker +Usage: ./boot2docker [] +{help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|delete|download|version} +[] +``` + +Continue with the [User Guide](/userguide/). For further information or to report issues, please see the [Boot2Docker site](http://boot2docker.io). diff --git a/docs/sources/installation/openSUSE.md b/docs/sources/installation/openSUSE.md index d2c8e5484..ce79de269 100644 --- a/docs/sources/installation/openSUSE.md +++ b/docs/sources/installation/openSUSE.md @@ -48,5 +48,6 @@ Docker daemon. $ sudo usermod -G docker **Done!** -Now continue with the [*Hello World*]( -/examples/hello_world/#hello-world) example. + +Continue with the [User Guide](/userguide/). + diff --git a/docs/sources/installation/rhel.md b/docs/sources/installation/rhel.md index 7cb71ec0d..c14457368 100644 --- a/docs/sources/installation/rhel.md +++ b/docs/sources/installation/rhel.md @@ -56,7 +56,8 @@ Now let's verify that Docker is working. $ sudo docker run -i -t fedora /bin/bash **Done!** -Now continue with the [*Hello World*](/examples/hello_world/#hello-world) example. + +Continue with the [User Guide](/userguide/). ## Issues? diff --git a/docs/sources/installation/softlayer.md b/docs/sources/installation/softlayer.md index 80b12741f..d01866720 100644 --- a/docs/sources/installation/softlayer.md +++ b/docs/sources/installation/softlayer.md @@ -24,5 +24,7 @@ page_keywords: IBM SoftLayer, virtualization, cloud, docker, documentation, inst 7. Then continue with the [*Ubuntu*](../ubuntulinux/#ubuntu-linux) instructions. -Continue with the [*Hello World*]( -/examples/hello_world/#hello-world) example. +## What next? + +Continue with the [User Guide](/userguide/). + diff --git a/docs/sources/installation/ubuntulinux.md b/docs/sources/installation/ubuntulinux.md index daba2c4ad..bd32802bb 100644 --- a/docs/sources/installation/ubuntulinux.md +++ b/docs/sources/installation/ubuntulinux.md @@ -111,8 +111,7 @@ Now verify that the installation has worked by downloading the Type `exit` to exit -**Done!**, now continue with the [*Hello -World*](/examples/hello_world/#hello-world) example. +**Done!**, continue with the [User Guide](/userguide/). ## Ubuntu Raring 13.04 and Saucy 13.10 (64 bit) @@ -159,8 +158,7 @@ Now verify that the installation has worked by downloading the Type `exit` to exit -**Done!**, now continue with the [*Hello -World*](/examples/hello_world/#hello-world) example. +**Done!**, now continue with the [User Guide](/userguide/). ### Giving non-root access diff --git a/docs/sources/introduction/understanding-docker.md b/docs/sources/introduction/understanding-docker.md index 1d99be704..e9041420a 100644 --- a/docs/sources/introduction/understanding-docker.md +++ b/docs/sources/introduction/understanding-docker.md @@ -7,7 +7,7 @@ page_keywords: docker, introduction, documentation, about, technology, understan **What is Docker?** Docker is a platform for developing, shipping, and running applications. -Docker is designed to deliver your applications faster. With Docker you +Docker is designed to deliver your applications faster. With Docker you can separate your applications from your infrastructure AND treat your infrastructure like a managed application. We want to help you ship code faster, test faster, deploy faster and shorten the cycle between writing @@ -317,15 +317,12 @@ Zones. ## Next steps -### Learning how to use Docker - -Visit [Working with Docker](working-with-docker.md). - ### Installing Docker Visit the [installation](/installation/#installation) section. -### Get the whole story +### The Docker User Guide + +[Learn how to use Docker](/userguide/). -[https://www.docker.io/the_whole_story/](https://www.docker.io/the_whole_story/) diff --git a/docs/sources/introduction/working-with-docker.md b/docs/sources/introduction/working-with-docker.md deleted file mode 100644 index aefdc586c..000000000 --- a/docs/sources/introduction/working-with-docker.md +++ /dev/null @@ -1,292 +0,0 @@ -page_title: Introduction to working with Docker -page_description: Introduction to working with Docker and Docker commands. -page_keywords: docker, introduction, documentation, about, technology, understanding, Dockerfile - -# An Introduction to working with Docker - -**Getting started with Docker** - -> **Note:** -> If you would like to see how a specific command -> works, check out the glossary of all available client -> commands on our [Commands Reference](/reference/commandline/cli). - -## Introduction - -In the [Understanding Docker](understanding-docker.md) section we -covered the components that make up Docker, learned about the underlying -technology and saw *how* everything works. - -Now, let's get an introduction to the basics of interacting with Docker. - -> **Note:** -> This page assumes you have a host with a running Docker -> daemon and access to a Docker client. To see how to install Docker on -> a variety of platforms see the [installation -> section](/installation/#installation). - -## How to use the client - -The client provides you a command-line interface to Docker. It is -accessed by running the `docker` binary. - -> **Tip:** -> The below instructions can be considered a summary of our -> [interactive tutorial](https://www.docker.io/gettingstarted). If you -> prefer a more hands-on approach without installing anything, why not -> give that a shot and check out the -> [tutorial](https://www.docker.io/gettingstarted). - -The `docker` client usage is pretty simple. Each action you can take -with Docker is a command and each command can take a series of -flags and arguments. - - # Usage: [sudo] docker [flags] [command] [arguments] .. - # Example: - $ docker run -i -t ubuntu /bin/bash - -## Using the Docker client - -Let's get started with the Docker client by running our first Docker -command. We're going to use the `docker version` command to return -version information on the currently installed Docker client and daemon. - - # Usage: [sudo] docker version - # Example: - $ docker version - -This command will not only provide you the version of Docker client and -daemon you are using, but also the version of Go (the programming -language powering Docker). - - Client version: 0.8.0 - Go version (client): go1.2 - - Git commit (client): cc3a8c8 - Server version: 0.8.0 - - Git commit (server): cc3a8c8 - Go version (server): go1.2 - - Last stable version: 0.8.0 - -### Seeing what the Docker client can do - -We can see all of the commands available to us with the Docker client by -running the `docker` binary without any options. - - # Usage: [sudo] docker - # Example: - $ docker - -You will see a list of all currently available commands. - - Commands: - attach Attach to a running container - build Build an image from a Dockerfile - commit Create a new image from a container's changes - . . . - -### Seeing Docker command usage - -You can also zoom in and review the usage for specific Docker commands. - -Try typing Docker followed with a `[command]` to see the usage for that -command: - - # Usage: [sudo] docker [command] [--help] - # Example: - $ docker attach - Help output . . . - -Or you can also pass the `--help` flag to the `docker` binary. - - $ docker images --help - -This will display the help text and all available flags: - - Usage: docker attach [OPTIONS] CONTAINER - - Attach to a running container - - --no-stdin=false: Do not attach stdin - --sig-proxy=true: Proxify all received signal to the process (even in non-tty mode) - -## Working with images - -Let's get started with using Docker by working with Docker images, the -building blocks of Docker containers. - -### Docker Images - -As we've discovered a Docker image is a read-only template that we build -containers from. Every Docker container is launched from an image. You can -use both images provided by Docker, such as the base `ubuntu` image, -as well as images built by others. For example we can build an image that -runs Apache and our own web application as a starting point to launch containers. - -### Searching for images - -To search for Docker image we use the `docker search` command. The -`docker search` command returns a list of all images that match your -search criteria, together with some useful information about that image. - -This information includes social metrics like how many other people like -the image: we call these "likes" *stars*. We also tell you if an image -is *trusted*. A *trusted* image is built from a known source and allows -you to introspect in greater detail how the image is constructed. - - # Usage: [sudo] docker search [image name] - # Example: - $ docker search nginx - - NAME DESCRIPTION STARS OFFICIAL TRUSTED - dockerfile/nginx Trusted Nginx (http://nginx.org/) Build 6 [OK] - paintedfox/nginx-php5 A docker image for running Nginx with PHP5. 3 [OK] - dockerfiles/django-uwsgi-nginx Dockerfile and configuration files to buil... 2 [OK] - . . . - -> **Note:** -> To learn more about trusted builds, check out -> [this](http://blog.docker.io/2013/11/introducing-trusted-builds) blog -> post. - -### Downloading an image - -Once we find an image we'd like to download we can pull it down from -[Docker.io](https://index.docker.io) using the `docker pull` command. - - # Usage: [sudo] docker pull [image name] - # Example: - $ docker pull dockerfile/nginx - - Pulling repository dockerfile/nginx - 0ade68db1d05: Pulling dependent layers - 27cf78414709: Download complete - b750fe79269d: Download complete - . . . - -As you can see, Docker will download, one by one, all the layers forming -the image. - -### Listing available images - -You may already have some images you've pulled down or built yourself -and you can use the `docker images` command to see the images -available to you locally. - - # Usage: [sudo] docker images - # Example: - $ docker images - - REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE - myUserName/nginx latest a0d6c70867d2 41 seconds ago 578.8 MB - nginx latest 173c2dd28ab2 3 minutes ago 578.8 MB - dockerfile/nginx latest 0ade68db1d05 3 weeks ago 578.8 MB - -### Building our own images - -You can build your own images using a `Dockerfile` and the `docker -build` command. The `Dockerfile` is very flexible and provides a -powerful set of instructions for building applications into Docker -images. To learn more about the `Dockerfile` see the [`Dockerfile` -Reference](/reference/builder/) and [tutorial](https://www.docker.io/learn/dockerfile/). - -## Working with containers - -### Docker Containers - -Docker containers run your applications and are built from Docker -images. In order to create or start a container, you need an image. This -could be the base `ubuntu` image or an image built and shared with you -or an image you've built yourself. - -### Running a new container from an image - -The easiest way to create a new container is to *run* one from an image -using the `docker run` command. - - # Usage: [sudo] docker run [arguments] .. - # Example: - $ docker run -d --name nginx_web nginx /usr/sbin/nginx - 25137497b2749e226dd08f84a17e4b2be114ddf4ada04125f130ebfe0f1a03d3 - -This will create a new container from an image called `nginx` which will -launch the command `/usr/sbin/nginx` when the container is run. We've -also given our container a name, `nginx_web`. When the container is run -Docker will return a container ID, a long string that uniquely -identifies our container. We use can the container's name or its string -to work with it. - -Containers can be run in two modes: - -* Interactive; -* Daemonized; - -An interactive container runs in the foreground and you can connect to -it and interact with it, for example sign into a shell on that -container. A daemonized container runs in the background. - -A container will run as long as the process you have launched inside it -is running, for example if the `/usr/bin/nginx` process stops running -the container will also stop. - -### Listing containers - -We can see a list of all the containers on our host using the `docker -ps` command. By default the `docker ps` command only shows running -containers. But we can also add the `-a` flag to show *all* containers: -both running and stopped. - - # Usage: [sudo] docker ps [-a] - # Example: - $ docker ps - - CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES - 842a50a13032 $ dockerfile/nginx:latest nginx 35 minutes ago Up 30 minutes 0.0.0.0:80->80/tcp nginx_web - -### Stopping a container - -You can use the `docker stop` command to stop an active container. This -will gracefully end the active process. - - # Usage: [sudo] docker stop [container ID] - # Example: - $ docker stop nginx_web - nginx_web - -If the `docker stop` command succeeds it will return the name of -the container it has stopped. - -> **Note:** -> If you want you to more aggressively stop a container you can use the -> `docker kill` command. - -### Starting a Container - -Stopped containers can be started again. - - # Usage: [sudo] docker start [container ID] - # Example: - $ docker start nginx_web - nginx_web - -If the `docker start` command succeeds it will return the name of the -freshly started container. - -## Next steps - -Here we've learned the basics of how to interact with Docker images and -how to run and work with our first container. - -### Understanding Docker - -Visit [Understanding Docker](understanding-docker.md). - -### Installing Docker - -Visit the [installation](/installation/#installation) section. - -### Get the whole story - -[https://www.docker.io/the_whole_story/](https://www.docker.io/the_whole_story/) diff --git a/docs/sources/reference/api/docker_remote_api_v1.10.md b/docs/sources/reference/api/docker_remote_api_v1.10.md index f743cb0b2..336edfbe5 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.10.md +++ b/docs/sources/reference/api/docker_remote_api_v1.10.md @@ -7,9 +7,8 @@ page_keywords: API, Docker, rcli, REST, documentation ## 1. Brief introduction - The Remote API has replaced rcli - - The daemon listens on `unix:///var/run/docker.sock` but you can - [*Bind Docker to another host/port or a Unix socket*]( - /use/basics/#bind-docker). + - The daemon listens on `unix:///var/run/docker.sock` but you can bind + Docker to another host/port or a Unix socket. - The API tends to be REST, but for some complex commands, like `attach` or `pull`, the HTTP connection is hijacked to transport `stdout, stdin` and `stderr` diff --git a/docs/sources/reference/api/docker_remote_api_v1.11.md b/docs/sources/reference/api/docker_remote_api_v1.11.md index 8f4709ee6..4133deaea 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.11.md +++ b/docs/sources/reference/api/docker_remote_api_v1.11.md @@ -7,9 +7,8 @@ page_keywords: API, Docker, rcli, REST, documentation ## 1. Brief introduction - The Remote API has replaced rcli - - The daemon listens on `unix:///var/run/docker.sock` but you can - [*Bind Docker to another host/port or a Unix socket*]( - /use/basics/#bind-docker). + - The daemon listens on `unix:///var/run/docker.sock` but you can bind + Docker to another host/port or a Unix socket. - The API tends to be REST, but for some complex commands, like `attach` or `pull`, the HTTP connection is hijacked to transport `stdout, stdin` and `stderr` diff --git a/docs/sources/reference/api/docker_remote_api_v1.6.md b/docs/sources/reference/api/docker_remote_api_v1.6.md index bca09a3a0..cd5b949c6 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.6.md +++ b/docs/sources/reference/api/docker_remote_api_v1.6.md @@ -7,9 +7,8 @@ page_keywords: API, Docker, rcli, REST, documentation # 1. Brief introduction - The Remote API has replaced rcli - - The daemon listens on `unix:///var/run/docker.sock` but you can - [*Bind Docker to another host/port or a Unix socket*]( - /use/basics/#bind-docker). + - The daemon listens on `unix:///var/run/docker.sock` but you can bind + Docker to another host/port or a Unix socket. - The API tends to be REST, but for some complex commands, like `attach` or `pull`, the HTTP connection is hijacked to transport `stdout, stdin` and `stderr` diff --git a/docs/sources/reference/api/docker_remote_api_v1.7.md b/docs/sources/reference/api/docker_remote_api_v1.7.md index 818fbba11..3ad2a729c 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.7.md +++ b/docs/sources/reference/api/docker_remote_api_v1.7.md @@ -7,9 +7,8 @@ page_keywords: API, Docker, rcli, REST, documentation # 1. Brief introduction - The Remote API has replaced rcli - - The daemon listens on `unix:///var/run/docker.sock` but you can - [*Bind Docker to another host/port or a Unix socket*]( - /use/basics/#bind-docker). + - The daemon listens on `unix:///var/run/docker.sock` but you can bind + Docker to another host/port or a Unix socket. - The API tends to be REST, but for some complex commands, like `attach` or `pull`, the HTTP connection is hijacked to transport `stdout, stdin` and `stderr` diff --git a/docs/sources/reference/api/docker_remote_api_v1.8.md b/docs/sources/reference/api/docker_remote_api_v1.8.md index 0d2997693..597922e1f 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.8.md +++ b/docs/sources/reference/api/docker_remote_api_v1.8.md @@ -7,9 +7,8 @@ page_keywords: API, Docker, rcli, REST, documentation # 1. Brief introduction - The Remote API has replaced rcli - - The daemon listens on `unix:///var/run/docker.sock` but you can - [*Bind Docker to another host/port or a Unix socket*]( - /use/basics/#bind-docker). + - The daemon listens on `unix:///var/run/docker.sock` but you can bind + Docker to another host/port or a Unix socket. - The API tends to be REST, but for some complex commands, like `attach` or `pull`, the HTTP connection is hijacked to transport `stdout, stdin` and `stderr` diff --git a/docs/sources/reference/api/docker_remote_api_v1.9.md b/docs/sources/reference/api/docker_remote_api_v1.9.md index d8be62a7a..2030959f9 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.9.md +++ b/docs/sources/reference/api/docker_remote_api_v1.9.md @@ -7,9 +7,8 @@ page_keywords: API, Docker, rcli, REST, documentation # 1. Brief introduction - The Remote API has replaced rcli - - The daemon listens on `unix:///var/run/docker.sock` but you can - [*Bind Docker to another host/port or a Unix socket*]( - /use/basics/#bind-docker). + - The daemon listens on `unix:///var/run/docker.sock` but you can bind + Docker to another host/port or a Unix socket. - The API tends to be REST, but for some complex commands, like `attach` or `pull`, the HTTP connection is hijacked to transport `stdout, stdin` and `stderr` diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index 235d92e1b..10973665b 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -57,7 +57,7 @@ accelerating `docker build` significantly (indicated by `Using cache`): When you're done with your build, you're ready to look into [*Pushing a repository to its registry*]( -/use/workingwithrepository/#image-push). +/userguide/dockerrepos/#image-push). ## Format @@ -95,7 +95,7 @@ The `FROM` instruction sets the [*Base Image*](/terms/image/#base-image-def) for subsequent instructions. As such, a valid Dockerfile must have `FROM` as its first instruction. The image can be any valid image – it is especially easy to start by **pulling an image** from the [*Public Repositories*]( -/use/workingwithrepository/#using-public-repositories). +/userguide/dockerrepos/#using-public-repositories). `FROM` must be the first non-comment instruction in the Dockerfile. @@ -200,10 +200,8 @@ default specified in CMD. The `EXPOSE` instructions informs Docker that the container will listen on the specified network ports at runtime. Docker uses this information to interconnect -containers using links (see -[*links*](/use/working_with_links_names/#working-with-links-names)), -and to setup port redirection on the host system (see [*Redirect Ports*]( -/use/port_redirection/#port-redirection)). +containers using links (see the [Docker User +Guide](/userguide/dockerlinks)). ## ENV @@ -380,7 +378,7 @@ and mark it as holding externally mounted volumes from native host or other containers. The value can be a JSON array, `VOLUME ["/var/log/"]`, or a plain string, `VOLUME /var/log`. For more information/examples and mounting instructions via the Docker client, refer to [*Share Directories via Volumes*]( -/use/working_with_volumes/#volume-def) documentation. +/userguide/dockervolumes/#volume-def) documentation. ## USER diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 8325bc794..f2e370ace 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -602,15 +602,6 @@ contains complex json object, so to grab it as JSON, you use The main process inside the container will be sent SIGKILL, or any signal specified with option `--signal`. -### Known Issues (kill) - -- [Issue 197](https://github.com/dotcloud/docker/issues/197) indicates - that `docker kill` may leave directories behind - and make it difficult to remove the container. -- [Issue 3844](https://github.com/dotcloud/docker/issues/3844) lxc - 1.0.0 beta3 removed `lcx-kill` which is used by - Docker versions before 0.8.0; see the issue for a workaround. - ## load Usage: docker load @@ -864,11 +855,9 @@ of all containers. The `docker run` command can be used in combination with `docker commit` to [*change the command that a container runs*](#commit-an-existing-container). -See [*Redirect Ports*](/use/port_redirection/#port-redirection) -for more detailed information about the `--expose`, `-p`, `-P` and `--link` -parameters, and [*Link Containers*]( -/use/working_with_links_names/#working-with-links-names) for specific -examples using `--link`. +See the [Docker User Guide](/userguide/dockerlinks/) for more detailed +information about the `--expose`, `-p`, `-P` and `--link` parameters, +and linking containers. ### Known Issues (run –volumes-from) @@ -934,16 +923,16 @@ manipulate the host's docker daemon. $ sudo docker run -p 127.0.0.1:80:8080 ubuntu bash -This binds port `8080` of the container to port `80` on `127.0.0.1` of the host -machine. [*Redirect Ports*](/use/port_redirection/#port-redirection) +This binds port `8080` of the container to port `80` on `127.0.0.1` of +the host machine. The [Docker User Guide](/userguide/dockerlinks/) explains in detail how to manipulate ports in Docker. $ sudo docker run --expose 80 ubuntu bash -This exposes port `80` of the container for use within a link without publishing -the port to the host system's interfaces. [*Redirect Ports*]( -/use/port_redirection/#port-redirection) explains in detail how to -manipulate ports in Docker. +This exposes port `80` of the container for use within a link without +publishing the port to the host system's interfaces. The [Docker User +Guide](/userguide/dockerlinks) explains in detail how to manipulate +ports in Docker. $ sudo docker run -e MYVAR1 --env MYVAR2=foo --env-file ./env.list ubuntu bash @@ -1097,7 +1086,7 @@ Search [Docker.io](https://index.docker.io) for images -t, --trusted=false Only show trusted builds See [*Find Public Images on Docker.io*]( -/use/workingwithrepository/#find-public-images-on-dockerio) for +/userguide/dockerrepos/#find-public-images-on-dockerio) for more details on finding shared images from the commandline. ## start @@ -1130,7 +1119,7 @@ grace period, SIGKILL You can group your images together using names and tags, and then upload them to [*Share Images via Repositories*]( -/use/workingwithrepository/#working-with-the-repository). +/userguide/dockerrepos/#working-with-the-repository). ## top diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index aa3d941b1..7d5fcbc51 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -11,21 +11,17 @@ The [*Image*](/terms/image/#image-def) which starts the process may define defaults related to the binary to run, the networking to expose, and more, but `docker run` gives final control to the operator who starts the container from the image. That's the main -reason [*run*](/commandline/cli/#cli-run) has more options than any +reason [*run*](/reference/commandline/cli/#cli-run) has more options than any other `docker` command. -Every one of the [*Examples*](/examples/#example-list) shows -running containers, and so here we try to give more in-depth guidance. - ## General Form -As you`ve seen in the [*Examples*](/examples/#example-list), the -basic run command takes this form: +The basic `docker run` command takes this form: $ docker run [OPTIONS] IMAGE[:TAG] [COMMAND] [ARG...] To learn how to interpret the types of `[OPTIONS]`, -see [*Option types*](/commandline/cli/#cli-options). +see [*Option types*](/reference/commandline/cli/#cli-options). The list of `[OPTIONS]` breaks down into two groups: @@ -75,9 +71,9 @@ default foreground mode: In detached mode (`-d=true` or just `-d`), all I/O should be done through network connections or shared volumes because the container is -no longer listening to the commandline where you executed `docker run`. +no longer listening to the command line where you executed `docker run`. You can reattach to a detached container with `docker` -[*attach*](commandline/cli/#attach). If you choose to run a +[*attach*](/reference/commandline/cli/#attach). If you choose to run a container in the detached mode, then you cannot use the `--rm` option. ### Foreground @@ -85,7 +81,7 @@ container in the detached mode, then you cannot use the `--rm` option. In foreground mode (the default when `-d` is not specified), `docker run` can start the process in the container and attach the console to the process's standard input, output, and standard error. It can even pretend to be a TTY -(this is what most commandline executables expect) and pass along signals. All +(this is what most command line executables expect) and pass along signals. All of that is configurable: -a=[] : Attach to ``stdin``, ``stdout`` and/or ``stderr`` @@ -121,11 +117,11 @@ assign a name to the container with `--name` then the daemon will also generate a random string name too. The name can become a handy way to add meaning to a container since you can use this name when defining -[*links*](/use/working_with_links_names/#working-with-links-names) +[*links*](/userguide/dockerlinks/#working-with-links-names) (or any other place you need to identify a container). This works for both background and foreground Docker containers. -### PID Equivalent +### PID Equivalent And finally, to help with automation, you can have Docker write the container ID out to a file of your choosing. This is similar to how some @@ -256,7 +252,7 @@ familiar with using LXC directly. ## Overriding Dockerfile Image Defaults -When a developer builds an image from a [*Dockerfile*](builder/#dockerbuilder) +When a developer builds an image from a [*Dockerfile*](/reference/builder/#dockerbuilder) or when she commits it, the developer can set a number of default parameters that take effect when the image starts up as a container. @@ -425,7 +421,7 @@ mechanism to communicate with a linked container by its alias: --volumes-from="": Mount all volumes from the given container(s) The volumes commands are complex enough to have their own documentation in -section [*Share Directories via Volumes*](/use/working_with_volumes/#volume-def). +section [*Share Directories via Volumes*](/userguide/dockervolumes/#volume-def). A developer can define one or more `VOLUME's associated with an image, but only the operator can give access from one container to another (or from a container to a volume mounted on the host). diff --git a/docs/sources/terms/container.md b/docs/sources/terms/container.md index 5bedc3160..8b4286878 100644 --- a/docs/sources/terms/container.md +++ b/docs/sources/terms/container.md @@ -8,18 +8,19 @@ page_keywords: containers, lxc, concepts, explanation, image, container ![](/terms/images/docker-filesystems-busyboxrw.png) -Once you start a process in Docker from an [*Image*](image.md), Docker fetches -the image and its [*Parent Image*](image.md), and repeats the process until it -reaches the [*Base Image*](image.md/#base-image-def). Then the -[*Union File System*](layer.md) adds a read-write layer on top. That read-write -layer, plus the information about its [*Parent Image*](image.md) and some -additional information like its unique id, networking configuration, and -resource limits is called a **container**. +Once you start a process in Docker from an [*Image*](/terms/image), Docker +fetches the image and its [*Parent Image*](/terms/image), and repeats the +process until it reaches the [*Base Image*](/terms/image/#base-image-def). Then +the [*Union File System*](/terms/layer) adds a read-write layer on top. That +read-write layer, plus the information about its [*Parent +Image*](/terms/image) +and some additional information like its unique id, networking +configuration, and resource limits is called a **container**. ## Container State -Containers can change, and so they have state. A container may be **running** or -**exited**. +Containers can change, and so they have state. A container may be +**running** or **exited**. When a container is running, the idea of a "container" also includes a tree of processes running on the CPU, isolated from the other processes @@ -31,13 +32,13 @@ processes restart from scratch (their memory state is **not** preserved in a container), but the file system is just as it was when the container was stopped. -You can promote a container to an [*Image*](image.md) with `docker commit`. +You can promote a container to an [*Image*](/terms/image) with `docker commit`. Once a container is an image, you can use it as a parent for new containers. ## Container IDs All containers are identified by a 64 hexadecimal digit string (internally a 256bit value). To simplify their use, a short ID of the -first 12 characters can be used on the commandline. There is a small +first 12 characters can be used on the command line. There is a small possibility of short id collisions, so the docker server will always return the long ID. diff --git a/docs/sources/terms/image.md b/docs/sources/terms/image.md index b10debcc6..40438be63 100644 --- a/docs/sources/terms/image.md +++ b/docs/sources/terms/image.md @@ -8,10 +8,10 @@ page_keywords: containers, lxc, concepts, explanation, image, container ![](/terms/images/docker-filesystems-debian.png) -In Docker terminology, a read-only [*Layer*](../layer/#layer-def) is +In Docker terminology, a read-only [*Layer*](/terms/layer/#layer-def) is called an **image**. An image never changes. -Since Docker uses a [*Union File System*](../layer/#ufs-def), the +Since Docker uses a [*Union File System*](/terms/layer/#ufs-def), the processes think the whole file system is mounted read-write. But all the changes go to the top-most writeable layer, and underneath, the original file in the read-only image is unchanged. Since images don't change, diff --git a/docs/sources/terms/layer.md b/docs/sources/terms/layer.md index b4b2ea4b7..561807fc4 100644 --- a/docs/sources/terms/layer.md +++ b/docs/sources/terms/layer.md @@ -7,7 +7,7 @@ page_keywords: containers, lxc, concepts, explanation, image, container ## Introduction In a traditional Linux boot, the kernel first mounts the root [*File -System*](../filesystem/#filesystem-def) as read-only, checks its +System*](/terms/filesystem/#filesystem-def) as read-only, checks its integrity, and then switches the whole rootfs volume to read-write mode. ## Layer diff --git a/docs/sources/terms/registry.md b/docs/sources/terms/registry.md index 200671060..08f8e8f69 100644 --- a/docs/sources/terms/registry.md +++ b/docs/sources/terms/registry.md @@ -6,9 +6,9 @@ page_keywords: containers, concepts, explanation, image, repository, container ## Introduction -A Registry is a hosted service containing [*repositories*]( -../repository/#repository-def) of [*images*](../image/#image-def) which -responds to the Registry API. +A Registry is a hosted service containing +[*repositories*](/terms/repository/#repository-def) of +[*images*](/terms/image/#image-def) which responds to the Registry API. The default registry can be accessed using a browser at [Docker.io](http://index.docker.io) or using the @@ -16,5 +16,5 @@ The default registry can be accessed using a browser at ## Further Reading -For more information see [*Working with Repositories*]( -../use/workingwithrepository/#working-with-the-repository) +For more information see [*Working with +Repositories*](/userguide/dockerrepos/#working-with-the-repository) diff --git a/docs/sources/terms/repository.md b/docs/sources/terms/repository.md index 1e035c95f..52c83d45d 100644 --- a/docs/sources/terms/repository.md +++ b/docs/sources/terms/repository.md @@ -7,7 +7,7 @@ page_keywords: containers, concepts, explanation, image, repository, container ## Introduction A repository is a set of images either on your local Docker server, or -shared, by pushing it to a [*Registry*](../registry/#registry-def) +shared, by pushing it to a [*Registry*](/terms/registry/#registry-def) server. Images can be associated with a repository (or multiple) by giving them @@ -31,5 +31,5 @@ If you create a new repository which you want to share, you will need to set at least the `user_name`, as the `default` blank `user_name` prefix is reserved for official Docker images. -For more information see [*Working with Repositories*]( -../use/workingwithrepository/#working-with-the-repository) +For more information see [*Working with +Repositories*](/userguide/dockerrepos/#working-with-the-repository) diff --git a/docs/sources/use.md b/docs/sources/use.md deleted file mode 100644 index 5b2524361..000000000 --- a/docs/sources/use.md +++ /dev/null @@ -1,13 +0,0 @@ -# Use - -## Contents: - - - [First steps with Docker](basics/) - - [Share Images via Repositories](workingwithrepository/) - - [Redirect Ports](port_redirection/) - - [Configure Networking](networking/) - - [Automatically Start Containers](host_integration/) - - [Share Directories via Volumes](working_with_volumes/) - - [Link Containers](working_with_links_names/) - - [Link via an Ambassador Container](ambassador_pattern_linking/) - - [Using Puppet](puppet/) \ No newline at end of file diff --git a/docs/sources/use/port_redirection.md b/docs/sources/use/port_redirection.md deleted file mode 100644 index 315ef2650..000000000 --- a/docs/sources/use/port_redirection.md +++ /dev/null @@ -1,127 +0,0 @@ -page_title: Redirect Ports -page_description: usage about port redirection -page_keywords: Usage, basic port, docker, documentation, examples - -# Redirect Ports - -## Introduction - -Interacting with a service is commonly done through a connection to a -port. When this service runs inside a container, one can connect to the -port after finding the IP address of the container as follows: - - # Find IP address of container with ID - $ docker inspect --format='{{.NetworkSettings.IPAddress}}' - -However, this IP address is local to the host system and the container -port is not reachable by the outside world. Furthermore, even if the -port is used locally, e.g. by another container, this method is tedious -as the IP address of the container changes every time it starts. - -Docker addresses these two problems and give a simple and robust way to -access services running inside containers. - -To allow non-local clients to reach the service running inside the -container, Docker provide ways to bind the container port to an -interface of the host system. To simplify communication between -containers, Docker provides the linking mechanism. - -## Auto map all exposed ports on the host - -To bind all the exposed container ports to the host automatically, use -`docker run -P `. The mapped host ports will be auto-selected -from a pool of unused ports (49000..49900), and you will need to use -`docker ps`, `docker inspect ` or `docker port - ` to determine what they are. - -## Binding a port to a host interface - -To bind a port of the container to a specific interface of the host -system, use the `-p` parameter of the `docker run` command: - - # General syntax - $ docker run -p [([:[host_port]])|():][/udp] - -When no host interface is provided, the port is bound to all available -interfaces of the host machine (aka INADDR_ANY, or 0.0.0.0). When no -host port is provided, one is dynamically allocated. The possible -combinations of options for TCP port are the following: - - # Bind TCP port 8080 of the container to TCP port 80 on 127.0.0.1 of the host machine. - $ docker run -p 127.0.0.1:80:8080 - - # Bind TCP port 8080 of the container to a dynamically allocated TCP port on 127.0.0.1 of the host machine. - $ docker run -p 127.0.0.1::8080 - - # Bind TCP port 8080 of the container to TCP port 80 on all available interfaces of the host machine. - $ docker run -p 80:8080 - - # Bind TCP port 8080 of the container to a dynamically allocated TCP port on all available interfaces of the host machine. - $ docker run -p 8080 - -UDP ports can also be bound by adding a trailing `/udp`. All the -combinations described for TCP work. Here is only one example: - - # Bind UDP port 5353 of the container to UDP port 53 on 127.0.0.1 of the host machine. - $ docker run -p 127.0.0.1:53:5353/udp - -The command `docker port` lists the interface and port on the host -machine bound to a given container port. It is useful when using -dynamically allocated ports: - - # Bind to a dynamically allocated port - $ docker run -p 127.0.0.1::8080 --name dyn-bound - - # Lookup the actual port - $ docker port dyn-bound 8080 - 127.0.0.1:49160 - -## Linking a container - -Communication between two containers can also be established in a -Docker-specific way called linking. - -To briefly present the concept of linking, let us consider two -containers: `server`, containing the service, and `client`, accessing -the service. Once `server` is running, `client` is started and links to -server. Linking sets environment variables in `client` giving it some -information about `server`. In this sense, linking is a method of -service discovery. - -Let us now get back to our topic of interest; communication between the -two containers. We mentioned that the tricky part about this -communication was that the IP address of `server` was not fixed. -Therefore, some of the environment variables are going to be used to -inform `client` about this IP address. This process called exposure, is -possible because the `client` is started after the `server` has been started. - -Here is a full example. On `server`, the port of interest is exposed. -The exposure is done either through the `--expose` parameter to the -`docker run` command, or the `EXPOSE` build command in a `Dockerfile`: - - # Expose port 80 - $ docker run --expose 80 --name server - -The `client` then links to the `server`: - - # Link - $ docker run --name client --link server:linked-server - -Here `client` locally refers to `server` as `linked-server`. The following -environment variables, among others, are available on `client`: - - # The default protocol, ip, and port of the service running in the container - $ LINKED-SERVER_PORT=tcp://172.17.0.8:80 - - # A specific protocol, ip, and port of various services - $ LINKED-SERVER_PORT_80_TCP=tcp://172.17.0.8:80 - $ LINKED-SERVER_PORT_80_TCP_PROTO=tcp - $ LINKED-SERVER_PORT_80_TCP_ADDR=172.17.0.8 - $ LINKED-SERVER_PORT_80_TCP_PORT=80 - -This tells `client` that a service is running on port 80 of `server` and -that `server` is accessible at the IP address `172.17.0.8`: - -> **Note:** -> Using the `-p` parameter also exposes the port. - diff --git a/docs/sources/use/working_with_links_names.md b/docs/sources/use/working_with_links_names.md deleted file mode 100644 index d69f3f175..000000000 --- a/docs/sources/use/working_with_links_names.md +++ /dev/null @@ -1,139 +0,0 @@ -page_title: Link Containers -page_description: How to create and use both links and names -page_keywords: Examples, Usage, links, linking, docker, documentation, examples, names, name, container naming - -# Link Containers - -## Introduction - -From version 0.6.5 you are now able to `name` a container and `link` it -to another container by referring to its name. This will create a parent --> child relationship where the parent container can see selected -information about its child. - -## Container Naming - -You can now name your container by using the `--name` flag. If no name -is provided, Docker will automatically generate a name. You can see this -name using the `docker ps` command. - - # format is "sudo docker run --name " - $ sudo docker run --name test ubuntu /bin/bash - - # the flag "-a" Show all containers. Only running containers are shown by default. - $ sudo docker ps -a - CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES - 2522602a0d99 ubuntu:12.04 /bin/bash 14 seconds ago Exit 0 test - -## Links: service discovery for docker - -Links allow containers to discover and securely communicate with each -other by using the flag `--link name:alias`. Inter-container -communication can be disabled with the daemon flag `--icc=false`. With -this flag set to `false`, Container A cannot access Container B unless -explicitly allowed via a link. This is a huge win for securing your -containers. When two containers are linked together Docker creates a -parent child relationship between the containers. The parent container -will be able to access information via environment variables of the -child such as name, exposed ports, IP and other selected environment -variables. - -When linking two containers Docker will use the exposed ports of the -container to create a secure tunnel for the parent to access. If a -database container only exposes port 8080 then the linked container will -only be allowed to access port 8080 and nothing else if inter-container -communication is set to false. - -For example, there is an image called `crosbymichael/redis` that exposes -the port 6379 and starts the Redis server. Let's name the container as -`redis` based on that image and run it as daemon. - - $ sudo docker run -d --name redis crosbymichael/redis - -We can issue all the commands that you would expect using the name -`redis`; start, stop, attach, using the name for our container. The name -also allows us to link other containers into this one. - -Next, we can start a new web application that has a dependency on Redis -and apply a link to connect both containers. If you noticed when running -our Redis server we did not use the `-p` flag to publish the Redis port -to the host system. Redis exposed port 6379 and this is all we need to -establish a link. - - $ sudo docker run -t -i --link redis:db --name webapp ubuntu bash - -When you specified `--link redis:db` you are telling Docker to link the -container named `redis` into this new container with the alias `db`. -Environment variables are prefixed with the alias so that the parent -container can access network and environment information from the -containers that are linked into it. - -If we inspect the environment variables of the second container, we -would see all the information about the child container. - - $ root@4c01db0b339c:/# env - - HOSTNAME=4c01db0b339c - DB_NAME=/webapp/db - TERM=xterm - DB_PORT=tcp://172.17.0.8:6379 - DB_PORT_6379_TCP=tcp://172.17.0.8:6379 - DB_PORT_6379_TCP_PROTO=tcp - DB_PORT_6379_TCP_ADDR=172.17.0.8 - DB_PORT_6379_TCP_PORT=6379 - PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin - PWD=/ - SHLVL=1 - HOME=/ - container=lxc - _=/usr/bin/env - root@4c01db0b339c:/# - -Accessing the network information along with the environment of the -child container allows us to easily connect to the Redis service on the -specific IP and port in the environment. - -> **Note**: -> These Environment variables are only set for the first process in the -> container. Similarly, some daemons (such as `sshd`) -> will scrub them when spawning shells for connection. - -You can work around this by storing the initial `env` in a file, or -looking at `/proc/1/environ`. - -Running `docker ps` shows the 2 containers, and the `webapp/db` alias -name for the Redis container. - - $ docker ps - CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES - 4c01db0b339c ubuntu:12.04 bash 17 seconds ago Up 16 seconds webapp - d7886598dbe2 crosbymichael/redis:latest /redis-server --dir 33 minutes ago Up 33 minutes 6379/tcp redis,webapp/db - -## Resolving Links by Name - -> *Note:* New in version v0.11. - -Linked containers can be accessed by hostname. Hostnames are mapped by -appending entries to '/etc/hosts' using the linked container's alias. - -For example, linking a container using '--link redis:db' will generate -the following '/etc/hosts' file: - - root@6541a75d44a0:/# cat /etc/hosts - 172.17.0.3 6541a75d44a0 - 172.17.0.2 db - - 127.0.0.1 localhost - ::1 localhost ip6-localhost ip6-loopback - fe00::0 ip6-localnet - ff00::0 ip6-mcastprefix - ff02::1 ip6-allnodes - ff02::2 ip6-allrouters - root@6541a75d44a0:/# - -Using this mechanism, you can communicate with the linked container by -name: - - root@6541a75d44a0:/# echo PING | redis-cli -h db - PONG - root@6541a75d44a0:/# diff --git a/docs/sources/use/working_with_volumes.md b/docs/sources/use/working_with_volumes.md deleted file mode 100644 index 4c0a46ff1..000000000 --- a/docs/sources/use/working_with_volumes.md +++ /dev/null @@ -1,171 +0,0 @@ -page_title: Share Directories via Volumes -page_description: How to create and share volumes -page_keywords: Examples, Usage, volume, docker, documentation, examples - -# Share Directories via Volumes - -## Introduction - -A *data volume* is a specially-designated directory within one or more -containers that bypasses the [*Union File -System*](/terms/layer/#ufs-def) to provide several useful features for -persistent or shared data: - - - **Data volumes can be shared and reused between containers:** - This is the feature that makes data volumes so powerful. You can - use it for anything from hot database upgrades to custom backup or - replication tools. See the example below. - - **Changes to a data volume are made directly:** - Without the overhead of a copy-on-write mechanism. This is good for - very large files. - - **Changes to a data volume will not be included at the next commit:** - Because they are not recorded as regular filesystem changes in the - top layer of the [*Union File System*](/terms/layer/#ufs-def) - - **Volumes persist until no containers use them:** - As they are a reference counted resource. The container does not need to be - running to share its volumes, but running it can help protect it - against accidental removal via `docker rm`. - -Each container can have zero or more data volumes. - -## Getting Started - -Using data volumes is as simple as adding a `-v` parameter to the -`docker run` command. The `-v` parameter can be used more than once in -order to create more volumes within the new container. To create a new -container with two new volumes: - - $ docker run -v /var/volume1 -v /var/volume2 busybox true - -This command will create the new container with two new volumes that -exits instantly (`true` is pretty much the smallest, simplest program -that you can run). You can then mount its volumes in any other container -using the `run` `--volumes-from` option; irrespective of whether the -volume container is running or not. - -Or, you can use the `VOLUME` instruction in a `Dockerfile` to add one or -more new volumes to any container created from that image: - - # BUILD-USING: $ docker build -t data . - # RUN-USING: $ docker run --name DATA data - FROM busybox - VOLUME ["/var/volume1", "/var/volume2"] - CMD ["/bin/true"] - -### Creating and mounting a Data Volume Container - -If you have some persistent data that you want to share between -containers, or want to use from non-persistent containers, it's best to -create a named Data Volume Container, and then to mount the data from -it. - -Create a named container with volumes to share (`/var/volume1` and -`/var/volume2`): - - $ docker run -v /var/volume1 -v /var/volume2 --name DATA busybox true - -Then mount those data volumes into your application containers: - - $ docker run -t -i --rm --from DATA --name client1 ubuntu bash - -You can use multiple `-volumes-from` parameters to bring together -multiple data volumes from multiple containers. - -Interestingly, you can mount the volumes that came from the `DATA` -container in yet another container via the `client1` middleman -container: - - $ docker run -t -i --rm --volumes-from client1 --name client2 ubuntu bash - -This allows you to abstract the actual data source from users of that -data, similar to [*Ambassador Pattern Linking*]( -../ambassador_pattern_linking/#ambassador-pattern-linking). - -If you remove containers that mount volumes, including the initial DATA -container, or the middleman, the volumes will not be deleted until there -are no containers still referencing those volumes. This allows you to -upgrade, or effectively migrate data volumes between containers. - -### Mount a Host Directory as a Container Volume: - - -v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. - -You must specify an absolute path for `host-dir`. If `host-dir` is -missing from the command, then Docker creates a new volume. If -`host-dir` is present but points to a non-existent directory on the -host, Docker will automatically create this directory and use it as the -source of the bind-mount. - -Note that this is not available from a `Dockerfile` due the portability -and sharing purpose of it. The `host-dir` volumes are entirely -host-dependent and might not work on any other machine. - -For example: - - # Usage: - # sudo docker run [OPTIONS] -v /(dir. on host):/(dir. in container):(Read-Write or Read-Only) [ARG..] - # Example: - $ sudo docker run -i -t -v /var/log:/logs_from_host:ro ubuntu bash - -The command above mounts the host directory `/var/log` into the -container with *read only* permissions as `/logs_from_host`. - -New in version v0.5.0. - -### Note for OS/X users and remote daemon users: - -OS/X users run `boot2docker` to create a minimalist virtual machine -running the docker daemon. That virtual machine then launches docker -commands on behalf of the OS/X command line. The means that `host -directories` refer to directories in the `boot2docker` virtual machine, -not the OS/X filesystem. - -Similarly, anytime when the docker daemon is on a remote machine, the -`host directories` always refer to directories on the daemon's machine. - -### Backup, restore, or migrate data volumes - -You cannot back up volumes using `docker export`, `docker save` and -`docker cp` because they are external to images. Instead you can use -`--volumes-from` to start a new container that can access the -data-container's volume. For example: - - $ sudo docker run --rm --volumes-from DATA -v $(pwd):/backup busybox tar cvf /backup/backup.tar /data - - - `-rm`: - remove the container when it exits - - `--volumes-from DATA`: - attach to the volumes shared by the `DATA` container - - `-v $(pwd):/backup`: - bind mount the current directory into the container; to write the tar file to - - `busybox`: - a small simpler image - good for quick maintenance - - `tar cvf /backup/backup.tar /data`: - creates an uncompressed tar file of all the files in the `/data` directory - -Then to restore to the same container, or another that you've made -elsewhere: - - # create a new data container - $ sudo docker run -v /data --name DATA2 busybox true - # untar the backup files into the new container᾿s data volume - $ sudo docker run --rm --volumes-from DATA2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar - data/ - data/sven.txt - # compare to the original container - $ sudo docker run --rm --volumes-from DATA -v `pwd`:/backup busybox ls /data - sven.txt - -You can use the basic techniques above to automate backup, migration and -restore testing using your preferred tools. - -## Known Issues - - - [Issue 2702](https://github.com/dotcloud/docker/issues/2702): - "lxc-start: Permission denied - failed to mount" could indicate a - permissions problem with AppArmor. Please see the issue for a - workaround. - - [Issue 2528](https://github.com/dotcloud/docker/issues/2528): the - busybox container is used to make the resulting container as small - and simple as possible - whenever you need to interact with the data - in the volume you mount it into another container. diff --git a/docs/sources/use/workingwithrepository.md b/docs/sources/use/workingwithrepository.md deleted file mode 100644 index 2b4ad613c..000000000 --- a/docs/sources/use/workingwithrepository.md +++ /dev/null @@ -1,251 +0,0 @@ -page_title: Share Images via Repositories -page_description: Repositories allow users to share images. -page_keywords: repo, repositories, usage, pull image, push image, image, documentation - -# Share Images via Repositories - -## Introduction - -Docker is not only a tool for creating and managing your own -[*containers*](/terms/container/#container-def) – **Docker is also a -tool for sharing**. A *repository* is a shareable collection of tagged -[*images*](/terms/image/#image-def) that together create the file -systems for containers. The repository's name is a label that indicates -the provenance of the repository, i.e. who created it and where the -original copy is located. - -You can find one or more repositories hosted on a *registry*. There are -two types of *registry*: public and private. There's also a default -*registry* that Docker uses which is called -[Docker.io](http://index.docker.io). -[Docker.io](http://index.docker.io) is the home of "top-level" -repositories and public "user" repositories. The Docker project -provides [Docker.io](http://index.docker.io) to host public and [private -repositories](https://index.docker.io/plans/), namespaced by user. We -provide user authentication and search over all the public repositories. - -Docker acts as a client for these services via the `docker search`, -`pull`, `login` and `push` commands. - -## Repositories - -### Local Repositories - -Docker images which have been created and labeled on your local Docker -server need to be pushed to a Public (by default they are pushed to -[Docker.io](http://index.docker.io)) or Private registry to be shared. - -### Public Repositories - -There are two types of public repositories: *top-level* repositories -which are controlled by the Docker team, and *user* repositories created -by individual contributors. Anyone can read from these repositories – -they really help people get started quickly! You could also use -[*Trusted Builds*](#trusted-builds) if you need to keep control of who -accesses your images. - -- Top-level repositories can easily be recognized by **not** having a - `/` (slash) in their name. These repositories represent trusted images - provided by the Docker team. -- User repositories always come in the form of `/`. - This is what your published images will look like if you push to the - public [Docker.io](http://index.docker.io) registry. -- Only the authenticated user can push to their *username* namespace on - a [Docker.io](http://index.docker.io) repository. -- User images are not curated, it is therefore up to you whether or not - you trust the creator of this image. - -### Private repositories - -You can also create private repositories on -[Docker.io](https://index.docker.io/plans/). These allow you to store -images that you don't want to share publicly. Only authenticated users -can push to private repositories. - -## Find Public Images on Docker.io - -You can search the [Docker.io](https://index.docker.io) registry or -using the command line interface. Searching can find images by name, -user name or description: - - $ sudo docker help search - Usage: docker search NAME - - Search the docker index for images - - --no-trunc=false: Don᾿t truncate output - $ sudo docker search centos - Found 25 results matching your query ("centos") - NAME DESCRIPTION - centos - slantview/centos-chef-solo CentOS 6.4 with chef-solo. - ... - -There you can see two example results: `centos` and -`slantview/centos-chef-solo`. The second result shows that it comes from -the public repository of a user, `slantview/`, while the first result -(`centos`) doesn't explicitly list a repository so it comes from the -trusted top-level namespace. The `/` character separates a user's -repository and the image name. - -Once you have found the image name, you can download it: - - # sudo docker pull - $ sudo docker pull centos - Pulling repository centos - 539c0211cd76: Download complete - -What can you do with that image? Check out the -[*Examples*](/examples/#example-list) and, when you're ready with your -own image, come back here to learn how to share it. - -## Contributing to Docker.io - -Anyone can pull public images from the -[Docker.io](http://index.docker.io) registry, but if you would like to -share one of your own images, then you must register a unique user name -first. You can create your username and login on -[Docker.io](https://index.docker.io/account/signup/), or by running - - $ sudo docker login - -This will prompt you for a username, which will become a public -namespace for your public repositories. - -If your username is available then `docker` will also prompt you to -enter a password and your e-mail address. It will then automatically log -you in. Now you're ready to commit and push your own images! - -> **Note:** -> Your authentication credentials will be stored in the [`.dockercfg` -> authentication file](#authentication-file). - -## Committing a Container to a Named Image - -When you make changes to an existing image, those changes get saved to a -container's file system. You can then promote that container to become -an image by making a `commit`. In addition to converting the container -to an image, this is also your opportunity to name the image, -specifically a name that includes your user name from -[Docker.io](http://index.docker.io) (as you did a `login` above) and a -meaningful name for the image. - - # format is "sudo docker commit /" - $ sudo docker commit $CONTAINER_ID myname/kickassapp - -## Pushing a repository to its registry - -In order to push an repository to its registry you need to have named an -image, or committed your container to a named image (see above) - -Now you can push this repository to the registry designated by its name -or tag. - - # format is "docker push /" - $ sudo docker push myname/kickassapp - -## Trusted Builds - -Trusted Builds automate the building and updating of images from GitHub -or BitBucket, directly on Docker.io. It works by adding a commit hook to -your selected repository, triggering a build and update when you push a -commit. - -### To setup a trusted build - -1. Create a [Docker.io account](https://index.docker.io/) and login. -2. Link your GitHub or BitBucket account through the [`Link Accounts`](https://index.docker.io/account/accounts/) menu. -3. [Configure a Trusted build](https://index.docker.io/builds/). -4. Pick a GitHub or BitBucket project that has a `Dockerfile` that you want to build. -5. Pick the branch you want to build (the default is the `master` branch). -6. Give the Trusted Build a name. -7. Assign an optional Docker tag to the Build. -8. Specify where the `Dockerfile` is located. The default is `/`. - -Once the Trusted Build is configured it will automatically trigger a -build, and in a few minutes, if there are no errors, you will see your -new trusted build on the [Docker.io](https://index.docker.io) Registry. -It will stay in sync with your GitHub and BitBucket repository until you -deactivate the Trusted Build. - -If you want to see the status of your Trusted Builds you can go to your -[Trusted Builds page](https://index.docker.io/builds/) on the Docker.io, -and it will show you the status of your builds, and the build history. - -Once you've created a Trusted Build you can deactivate or delete it. You -cannot however push to a Trusted Build with the `docker push` command. -You can only manage it by committing code to your GitHub or BitBucket -repository. - -You can create multiple Trusted Builds per repository and configure them -to point to specific `Dockerfile`'s or Git branches. - -## Private Registry - -Private registries are possible by hosting [your own -registry](https://github.com/dotcloud/docker-registry). - -> **Note**: -> You can also use private repositories on -> [Docker.io](https://index.docker.io/plans/). - -To push or pull to a repository on your own registry, you must prefix -the tag with the address of the registry's host (a `.` or `:` is used to -identify a host), like this: - - # Tag to create a repository with the full registry location. - # The location (e.g. localhost.localdomain:5000) becomes - # a permanent part of the repository name - $ sudo docker tag 0u812deadbeef localhost.localdomain:5000/repo_name - - # Push the new repository to its home location on localhost - $ sudo docker push localhost.localdomain:5000/repo_name - -Once a repository has your registry's host name as part of the tag, you -can push and pull it like any other repository, but it will **not** be -searchable (or indexed at all) on [Docker.io](http://index.docker.io), -and there will be no user name checking performed. Your registry will -function completely independently from the -[Docker.io](http://index.docker.io) registry. - - - -See also - -[Docker Blog: How to use your own registry]( -http://blog.docker.io/2013/07/how-to-use-your-own-registry/) - -## Authentication File - -The authentication is stored in a JSON file, `.dockercfg`, located in -your home directory. It supports multiple registry URLs. - -The `docker login` command will create the: - - [https://index.docker.io/v1/](https://index.docker.io/v1/) - -key. - -The `docker login https://my-registry.com` command will create the: - - [https://my-registry.com](https://my-registry.com) - -key. - -For example: - - { - "https://index.docker.io/v1/": { - "auth": "xXxXxXxXxXx=", - "email": "email@example.com" - }, - "https://my-registry.com": { - "auth": "XxXxXxXxXxX=", - "email": "email@my-registry.com" - } - } - -The `auth` field represents - - base64(:) - diff --git a/docs/sources/userguide/dockerimages.md b/docs/sources/userguide/dockerimages.md new file mode 100644 index 000000000..1a69ab9aa --- /dev/null +++ b/docs/sources/userguide/dockerimages.md @@ -0,0 +1,397 @@ +page_title: Working with Docker Images +page_description: How to work with Docker images. +page_keywords: documentation, docs, the docker guide, docker guide, docker, docker platform, virtualization framework, docker.io, Docker images, Docker image, image management, Docker repos, Docker repositories, docker, docker tag, docker tags, Docker.io, collaboration + +# Working with Docker Images + +In the [introduction](/introduction/) we've discovered that Docker +images are the basis of containers. In the +[previous](/userguide/dockerizing/) [sections](/userguide/usingdocker/) +we've used Docker images that already exist, for example the `ubuntu` +image and the `training/webapp` image. + +We've also discovered that Docker stores downloaded images on the Docker +host. If an image isn't already present on the host then it'll be +downloaded from a registry: by default the +[Docker.io](https://index.docker.io) public registry. + +In this section we're going to explore Docker images a bit more +including: + +* Managing and working with images locally on your Docker host; +* Creating basic images; +* Uploading images to [Docker.io](https://index.docker.io). + +## Listing images on the host + +Let's start with listing the images we have locally on our host. You can +do this using the `docker images` command like so: + + $ sudo docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + training/webapp latest fc77f57ad303 3 weeks ago 280.5 MB + ubuntu 13.10 5e019ab7bf6d 4 weeks ago 180 MB + ubuntu saucy 5e019ab7bf6d 4 weeks ago 180 MB + ubuntu 12.04 74fe38d11401 4 weeks ago 209.6 MB + ubuntu precise 74fe38d11401 4 weeks ago 209.6 MB + ubuntu 12.10 a7cf8ae4e998 4 weeks ago 171.3 MB + ubuntu quantal a7cf8ae4e998 4 weeks ago 171.3 MB + ubuntu 14.04 99ec81b80c55 4 weeks ago 266 MB + ubuntu latest 99ec81b80c55 4 weeks ago 266 MB + ubuntu trusty 99ec81b80c55 4 weeks ago 266 MB + ubuntu 13.04 316b678ddf48 4 weeks ago 169.4 MB + ubuntu raring 316b678ddf48 4 weeks ago 169.4 MB + ubuntu 10.04 3db9c44f4520 4 weeks ago 183 MB + ubuntu lucid 3db9c44f4520 4 weeks ago 183 MB + +We can see the images we've previously used in our [user guide](/userguide/). +Each has been downloaded from [Docker.io](https://index.docker.io) when we +launched a container using that image. + +We can see three crucial pieces of information about our images in the listing. + +* What repository they came from, for example `ubuntu`. +* The tags for each image, for example `14.04`. +* The image ID of each image. + +A repository potentially holds multiple variants of an image. In the case of +our `ubuntu` image we can see multiple variants covering Ubuntu 10.04, 12.04, +12.10, 13.04, 13.10 and 14.04. Each variant is identified by a tag and you can +refer to a tagged image like so: + + ubuntu:14.04 + +So when we run a container we refer to a tagged image like so: + + $ sudo docker run -t -i ubuntu:14.04 /bin/bash + +If instead we wanted to build an Ubuntu 12.04 image we'd use: + + $ sudo docker run -t -i ubuntu:12.04 /bin/bash + +If you don't specify a variant, for example you just use `ubuntu`, then Docker +will default to using the `ubunut:latest` image. + +> **Tip:** +> We recommend you always use a specific tagged image, for example +> `ubuntu:12.04`. That way you always know exactly what variant of an image is +> being used. + +## Getting a new image + +So how do we get new images? Well Docker will automatically download any image +we use that isn't already present on the Docker host. But this can potentially +add some time to the launch of a container. If we want to pre-load an image we +can download it using the `docker pull` command. Let's say we'd like to +download the `centos` image. + + $ sudo docker pull centos + Pulling repository centos + b7de3133ff98: Pulling dependent layers + 5cc9e91966f7: Pulling fs layer + 511136ea3c5a: Download complete + ef52fb1fe610: Download complete + . . . + +We can see that each layer of the image has been pulled down and now we +can run a container from this image and we won't have to wait to +download the image. + + $ sudo docker run -t -i centos /bin/bash + bash-4.1# + +## Finding images + +One of the features of Docker is that a lot of people have created Docker +images for a variety of purposes. Many of these have been uploaded to +[Docker.io](https://index.docker.io). We can search these images on the +[Docker.io](https://index.docker.io) website. + +![indexsearch](/userguide/search.png) + +We can also search for images on the command line using the `docker search` +command. Let's say our team wants an image with Ruby and Sinatra installed on +which to do our web application development. We can search for a suitable image +by using the `docker search` command to find all the images that contain the +term `sinatra`. + + $ sudo docker search sinatra + NAME DESCRIPTION STARS OFFICIAL TRUSTED + training/sinatra Sinatra training image 0 [OK] + marceldegraaf/sinatra Sinatra test app 0 + mattwarren/docker-sinatra-demo 0 [OK] + luisbebop/docker-sinatra-hello-world 0 [OK] + bmorearty/handson-sinatra handson-ruby + Sinatra for Hands on with D... 0 + subwiz/sinatra 0 + bmorearty/sinatra 0 + . . . + +We can see we've returned a lot of images that use the term `sinatra`. We've +returned a list of image names, descriptions, Stars (which measure the social +popularity of images - if a user likes an image then they can "star" it), and +the Official and Trusted statuses. Official repositories are XXX and Trusted +repositories are [Trusted Build](/userguide/dockerrepos/) that allow you to +validate the source and content of an image. + +We've reviewed the images available to use and we decided to use the +`training/sinatra` image. So far we've seen two types of images repositories, +images like `ubuntu`, which are called base or root images. These base images +are provided by Docker Inc and are built, validated and supported. These can be +identified by their single word names. + +We've also seen user images, for example the `training/sinatra` image we've +chosen. A user image belongs to a member of the Docker community and is built +and maintained by them. You can identify user images as they are always +prefixed with the user name, here `training`, of the user that created them. + +## Pulling our image + +We've identified a suitable image, `training/sinatra`, and now we can download it using the `docker pull` command. + + $ sudo docker pull training/sinatra + +The team can now use this image by run their own containers. + + $ sudo docker run -t -i training/sinatra /bin/bash + root@a8cb6ce02d85:/# + +## Creating our own images + +The team has found the `training/sinatra` image pretty useful but it's not quite what +they need and we need to make some changes to it. There are two ways we can +update and create images. + +1. We can update a container created from an image and commit the results to an image. +2. We can use a `Dockerfile` to specify instructions to create an image. + +### Updating and committing an image + +To update an image we first need to create a container from the image +we'd like to update. + + $ sudo docker run -t -i training/sinatra /bin/bash + root@0b2616b0e5a8:/# + +> **Note:** +> Take note of the container ID that has been created, `0b2616b0e5a8`, as we'll +> need it in a moment. + +Inside our running container let's add the `json` gem. + + root@0b2616b0e5a8:/# gem install json + +Once this has completed let's exit our container using the `exit` +command. + +Now we have a container with the change we want to make. We can then +commit a copy of this container to an image using the `docker commit` +command. + + $ sudo docker commit -m="Added json gem" -a="Kate Smith" \ + 0b2616b0e5a8 ouruser/sinatra:v2 + 4f177bd27a9ff0f6dc2a830403925b5360bfe0b93d476f7fc3231110e7f71b1c + +Here we've used the `docker commit` command. We've specified two flags: `-m` +and `-a`. The `-m` flag allows us to specify a commit message, much like you +would with a commit on a version control system. The `-a` flag allows us to +specify an author for our update. + +We've also specified the container we want to create this new image from, +`0b2616b0e5a8` (the ID we recorded earlier) and we've specified a target for +the image: + + ouruser/sinatra:v2 + +Let's break this target down. It consists of a new user, `ouruser`, that we're +writing this image to. We've also specified the name of the image, here we're +keeping the original image name `sinatra`. Finally we're specifying a tag for +the image: `v2`. + +We can then look at our new `ouruser/sinatra` image using the `docker images` +command. + + $ sudo docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + training/sinatra latest 5bc342fa0b91 10 hours ago 446.7 MB + ouruser/sinatra v2 3c59e02ddd1a 10 hours ago 446.7 MB + ouruser/sinatra latest 5db5f8471261 10 hours ago 446.7 MB + +To use our new image to create a container we can then: + + $ sudo docker run -t -i ouruser/sinatra:v2 /bin/bash + root@78e82f680994:/# + +### Building an image from a `Dockerfile` + +Using the `docker commit` command is a pretty simple way of extending an image +but it's a bit cumbersome and it's not easy to share a development process for +images amongst a team. Instead we can use a new command, `docker build`, to +build new images from scratch. + +To do this we create a `Dockerfile` that contains a set of instructions that +tell Docker how to build our image. + +Let's create a directory and a `Dockerfile` first. + + $ mkdir sinatra + $ cd sinatra + $ touch Dockerfile + +Each instructions creates a new layer of the image. Let's look at a simple +example now for building our own Sinatra image for our development team. + + # This is a comment + FROM ubuntu:14.04 + MAINTAINER Kate Smith + RUN apt-get -qq update + RUN apt-get -qqy install ruby ruby-dev + RUN gem install sinatra + +Let's look at what our `Dockerfile` does. Each instruction prefixes a statement and is capitalized. + + INSTRUCTION statement + +> **Note:** +> We use `#` to indicate a comment + +The first instruction `FROM` tells Docker what the source of our image is, in +this case we're basing our new image on an Ubuntu 14.04 image. + +Next we use the `MAINTAINER` instruction to specify who maintains our new image. + +Lastly, we've specified three `RUN` instructions. A `RUN` instruction executes +a command inside the image, for example installing a package. Here we're +updating our APT cache, installing Ruby and RubyGems and then installing the +Sinatra gem. + +> **Note:** +> There are [a lot more instructions available to us in a Dockerfile](/reference/builder). + +Now let's take our `Dockerfile` and use the `docker build` command to build an image. + + $ sudo docker build -t="ouruser/sinatra:v2" . + Uploading context 2.56 kB + Uploading context + Step 0 : FROM ubuntu:14.04 + ---> 99ec81b80c55 + Step 1 : MAINTAINER Kate Smith + ---> Running in 7c5664a8a0c1 + ---> 2fa8ca4e2a13 + Removing intermediate container 7c5664a8a0c1 + Step 2 : RUN apt-get -qq update + ---> Running in b07cc3fb4256 + ---> 50d21070ec0c + Removing intermediate container b07cc3fb4256 + Step 3 : RUN apt-get -qqy install ruby ruby-dev + ---> Running in a5b038dd127e + Selecting previously unselected package libasan0:amd64. + (Reading database ... 11518 files and directories currently installed.) + Preparing to unpack .../libasan0_4.8.2-19ubuntu1_amd64.deb ... + . . . + Setting up ruby (1:1.9.3.4) ... + Setting up ruby1.9.1 (1.9.3.484-2ubuntu1) ... + Processing triggers for libc-bin (2.19-0ubuntu6) ... + ---> 2acb20f17878 + Removing intermediate container a5b038dd127e + Step 4 : RUN gem install sinatra + ---> Running in 5e9d0065c1f7 + . . . + Successfully installed rack-protection-1.5.3 + Successfully installed sinatra-1.4.5 + 4 gems installed + ---> 324104cde6ad + Removing intermediate container 5e9d0065c1f7 + Successfully built 324104cde6ad + +We've specified our `docker build` command and used the `-t` flag to identify +our new image as belonging to the user `ouruser`, the repository name `sinatra` +and given it the tag `v2`. + +We've also specified the location of our `Dockerfile` using the `.` to +indicate a `Dockerfile` in the current directory. + +> **Note::** +> You can also specify a path to a `Dockerfile`. + +Now we can see the build process at work. The first thing Docker does is +upload the build context: basically the contents of the directory you're +building in. This is done because the Docker daemon does the actual +build of the image and it needs the local context to do it. + +Next we can see each instruction in the `Dockerfile` being executed +step-by-step. We can see that each step creates a new container, runs +the instruction inside that container and then commits that change - +just like the `docker commit` work flow we saw earlier. When all the +instructions have executed we're left with the `324104cde6ad` image +(also helpfully tagged as `ouruser/sinatra:v2`) and all intermediate +containers will get removed to clean things up. + +We can then create a container from our new image. + + $ sudo docker run -t -i ouruser/sinatra /bin/bash + root@8196968dac35:/# + +> **Note:** +> This is just the briefest introduction to creating images. We've +> skipped a whole bunch of other instructions that you can use. We'll see more of +> those instructions in later sections of the Guide or you can refer to the +> [`Dockerfile`](/reference/builder/) reference for a +> detailed description and examples of every instruction. + +## Setting tags on an image + +You can also add a tag to an existing image after you commit or build it. We +can do this using the `docker tag` command. Let's add a new tag to our +`ouruser/sinatra` image. + + $ sudo docker tag 5db5f8471261 ouruser/sinatra:devel + +The `docker tag` command takes the ID of the image, here `5db5f8471261`, and our +user name, the repository name and the new tag. + +Let's see our new tag using the `docker images` command. + + $ sudo docker images ouruser/sinatra + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + ouruser/sinatra latest 5db5f8471261 11 hours ago 446.7 MB + ouruser/sinatra devel 5db5f8471261 11 hours ago 446.7 MB + ouruser/sinatra v2 5db5f8471261 11 hours ago 446.7 MB + +## Push an image to Docker.io + +Once you've built or created a new image you can push it to [Docker.io]( +https://index.docker.io) using the `docker push` command. This allows you to +share it with others, either publicly, or push it into [a private +repository](https://index.docker.io/plans/). + + $ sudo docker push ouruser/sinatra + The push refers to a repository [ouruser/sinatra] (len: 1) + Sending image list + Pushing repository ouruser/sinatra (3 tags) + . . . + +## Remove an image from the host + +You can also remove images on your Docker host in a way [similar to +containers]( +/userguide/usingdocker) using the `docker rmi` command. + +Let's delete the `training/sinatra` image as we don't need it anymore. + + $ docker rmi training/sinatra + Untagged: training/sinatra:latest + Deleted: 5bc342fa0b91cabf65246837015197eecfa24b2213ed6a51a8974ae250fedd8d + Deleted: ed0fffdcdae5eb2c3a55549857a8be7fc8bc4241fb19ad714364cbfd7a56b22f + Deleted: 5c58979d73ae448df5af1d8142436d81116187a7633082650549c52c3a2418f0 + +> **Note:** In order to remove an image from the host, please make sure +> that there are no containers actively based on it. + +# Next steps + +Until now we've seen how to build individual applications inside Docker +containers. Now learn how to build whole application stacks with Docker +by linking together multiple Docker containers. + +Go to [Linking Containers Together](/userguide/dockerlinks). + diff --git a/docs/sources/userguide/dockerio.md b/docs/sources/userguide/dockerio.md new file mode 100644 index 000000000..e5c6c6dac --- /dev/null +++ b/docs/sources/userguide/dockerio.md @@ -0,0 +1,73 @@ +page_title: Getting started with Docker.io +page_description: Introductory guide to getting an account on Docker.io +page_keywords: documentation, docs, the docker guide, docker guide, docker, docker platform, virtualization framework, docker.io, central service, services, how to, container, containers, automation, collaboration, collaborators, registry, repo, repository, technology, github webhooks, trusted builds + +# Getting Started with Docker.io + +*How do I use Docker.io?* + +In this section we're going to introduce you, very quickly!, to +[Docker.io](https://index.docker.io) and create an account. + +[Docker.io](https://www.docker.io) is the central hub for Docker. It +helps you to manage Docker and its components. It provides services such +as: + +* Hosting images. +* User authentication. +* Automated image builds and work flow tools like build triggers and web + hooks. +* Integration with GitHub and BitBucket. + +Docker.io helps you collaborate with colleagues and get the most out of +Docker. + +In order to use Docker.io you will need to register an account. Don't +panic! It's totally free and really easy. + +## Creating a Docker.io Account + +There are two ways you can create a Docker.io account: + +* Via the web, or +* Via the command line. + +### Sign up via the web! + +Fill in the [sign-up form](https://www.docker.io/account/signup/) and +choose your user name and specify some details such as an email address. + +![Register using the sign-up page](/userguide/register-web.png) + +### Signup via the command line + +You can also create a Docker.io account via the command line using the +`docker login` command. + + $ sudo docker login + +### Confirm your email + +Once you've filled in the form then check your email for a welcome +message and activate your account. + +![Confirm your registration](/userguide/register-confirm.png) + +### Login! + +Then you can login using the web console: + +![Login using the web console](/userguide/login-web.png) + +Or via the command line and the `docker login` command: + + $ sudo docker login + +Now your Docker.io account is active and ready for you to use! + +## Next steps + +Now let's start Dockerizing applications with our "Hello World!" exercise. + +Go to [Dockerizing Applications](/userguide/dockerizing). + diff --git a/docs/sources/userguide/dockerizing.md b/docs/sources/userguide/dockerizing.md new file mode 100644 index 000000000..79a2066c6 --- /dev/null +++ b/docs/sources/userguide/dockerizing.md @@ -0,0 +1,193 @@ +page_title: Dockerizing Applications: A "Hello World!" +page_description: A simple "Hello World!" exercise that introduced you to Docker. +page_keywords: docker guide, docker, docker platform, virtualization framework, how to, dockerize, dockerizing apps, dockerizing applications, container, containers + +# Dockerizing Applications: A "Hello World!" + +*So what's this Docker thing all about?* + +Docker allows you to run applications inside containers. Running an +application inside a container takes a single command: `docker run`. + +## Hello World! + +Let's try it now. + + $ sudo docker run ubuntu:14.04 /bin/echo "Hello World!" + Hello World! + +And you just launched your first container! + +So what just happened? Let's step through what the `docker run` command +did. + +First we specified the `docker` binary and the command we wanted to +execute, `run`. The `docker run` combination *runs* containers. + +Next we specified an image: `ubuntu:14.04`. This is the source of the container +we ran. Docker calls this an image. In this case we used an Ubuntu 14.04 +operating system image. + +When you specify an image, Docker looks first for the image on your +Docker host. If it can't find it then it downloads the image from the public +image registry: [Docker.io](https://index.docker.io). + +Next we told Docker what command to run inside our new container: + + /bin/echo "Hello World!" + +When our container was launched Docker created a new Ubuntu 14.04 +environment and then executed the `/bin/echo` command inside it. We saw +the result on the command line: + + Hello World! + +So what happened to our container after that? Well Docker containers +only run as long as the command you specify is active. Here, as soon as +`Hello World!` was echoed, the container stopped. + +## An Interactive Container + +Let's try the `docker run` command again, this time specifying a new +command to run in our container. + + $ sudo docker run -t -i ubuntu:14.04 /bin/bash + root@af8bae53bdd3:/# + +Here we've again specified the `docker run` command and launched an +`ubuntu:14.04` image. But we've also passed in two flags: `-t` and `-i`. +The `-t` flag assigns a pseudo-tty or terminal inside our new container +and the `-i` flag allows us to make an interactive connection by +grabbing the standard in (`STDIN`) of the container. + +We've also specified a new command for our container to run: +`/bin/bash`. This will launch a Bash shell inside our container. + +So now when our container is launched we can see that we've got a +command prompt inside it: + + root@af8bae53bdd3:/# + +Let's try running some commands inside our container: + + root@af8bae53bdd3:/# pwd + / + root@af8bae53bdd3:/# ls + bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var + +You can see we've run the `pwd` to show our current directory and can +see we're in the `/` root directory. We've also done a directory listing +of the root directory which shows us what looks like a typical Linux +file system. + +You can play around inside this container and when you're done you can +use the `exit` command to finish. + + root@af8bae53bdd3:/# exit + +As with our previous container, once the Bash shell process has +finished, the container is stopped. + +## A Daemonized Hello World! + +Now a container that runs a command and then exits has some uses but +it's not overly helpful. Let's create a container that runs as a daemon, +like most of the applications we're probably going to run with Docker. + +Again we can do this with the `docker run` command: + + $ sudo docker run -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done" + 1e5535038e285177d5214659a068137486f96ee5c2e85a4ac52dc83f2ebe4147 + +Wait what? Where's our "Hello World!" Let's look at what we've run here. +It should look pretty familiar. We ran `docker run` but this time we +specified a flag: `-d`. The `-d` flag tells Docker to run the container +and put it in the background, to daemonize it. + +We also specified the same image: `ubuntu:14.04`. + +Finally, we specified a command to run: + + /bin/sh -c "while true; do echo hello world; sleep 1; done" + +This is the (hello) world's silliest daemon: a shell script that echoes +`hello world` forever. + +So why aren't we seeing any `hello world`'s? Instead Docker has returned +a really long string: + + 1e5535038e285177d5214659a068137486f96ee5c2e85a4ac52dc83f2ebe4147 + +This really long string is called a *container ID*. It uniquely +identifies a container so we can work with it. + +> **Note:** +> The container ID is a bit long and unwieldy and a bit later +> on we'll see a shorter ID and some ways to name our containers to make +> working with them easier. + +We can use this container ID to see what's happening with our `hello +world` daemon. + +Firstly let's make sure our container is running. We can +do that with the `docker ps` command. The `docker ps` command queries +the Docker daemon for information about all the container it knows +about. + + $ docker ps + CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + 1e5535038e28 ubuntu:14.04 /bin/sh -c 'while tr 2 minutes ago Up 1 minute insane_babbage + +Here we can see our daemonized container. The `docker ps` has returned some useful +information about it, starting with a shorter variant of its container ID: +`1e5535038e28`. + +We can also see the image we used to build it, `ubuntu:14.04`, the command it +is running, its status and an automatically assigned name, +`insane_babbage`. + +> **NoteL** +> Docker automatically names any containers you start, a +> little later on we'll see how you can specify your own names. + +Okay, so we now know it's running. But is it doing what we asked it to do? To see this +we're going to look inside the container using the `docker logs` +command. Let's use the container name Docker assigned. + + $ sudo docker logs insane_babbage + hello world + hello world + hello world + . . . + +The `docker logs` command looks inside the container and returns its standard +output: in this case the output of our command `hello world`. + +Awesome! Our daemon is working and we've just created our first +Dockerized application! + +Now we've established we can create our own containers let's tidy up +after ourselves and stop our daemonized container. To do this we use the +`docker stop` command. + + $ sudo docker stop insane_babbage + insane_babbage + +The `docker stop` command tells Docker to politely stop the running +container. If it succeeds it will return the name of the container it +has just stopped. + +Let's check it worked with the `docker ps` command. + + $ docker ps + CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + +Excellent. Our container has been stopped. + +# Next steps + +Now we've seen how simple it is to get started with Docker let's learn how to +do some more advanced tasks. + +Go to [Working With Containers](/userguide/usingdocker). + diff --git a/docs/sources/userguide/dockerlinks.md b/docs/sources/userguide/dockerlinks.md new file mode 100644 index 000000000..5c94879bf --- /dev/null +++ b/docs/sources/userguide/dockerlinks.md @@ -0,0 +1,241 @@ +page_title: Linking Containers Together +page_description: Learn how to connect Docker containers together. +page_keywords: Examples, Usage, user guide, links, linking, docker, documentation, examples, names, name, container naming, port, map, network port, network + +# Linking Containers Together + +In [the Using Docker section](/userguide/usingdocker) we touched on +connecting to a service running inside a Docker container via a network +port. This is one of the ways that you can interact with services and +applications running inside Docker containers. In this section we're +going to give you a refresher on connecting to a Docker container via a +network port as well as introduce you to the concepts of container +linking. + +## Network port mapping refresher + +In [the Using Docker section](/userguide/usingdocker) we created a +container that ran a Python Flask application. + + $ sudo docker run -d -P training/webapp python app.py + +> **Note:** +> Containers have an internal network and an IP address +> (remember we used the `docker inspect` command to show the container's +> IP address in the [Using Docker](/userguide/usingdocker/) section). +> Docker can have a variety of network configurations. You can see more +> information on Docker networking [here](/articles/networking/). + +When we created that container we used the `-P` flag to automatically map any +network ports inside that container to a random high port from the range 49000 +to 49900 on our Docker host. When we subsequently ran `docker ps` we saw that +port 5000 was bound to port 49155. + + $ sudo docker ps nostalgic_morse + CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + bc533791f3f5 training/webapp:latest python app.py 5 seconds ago Up 2 seconds 0.0.0.0:49155->5000/tcp nostalgic_morse + +We also saw how we can bind a container's ports to a specific port using +the `-p` flag. + + $ sudo docker run -d -p 5000:5000 training/webapp python app.py + +And we saw why this isn't such a great idea because it constrains us to +only one container on that specific port. + +There are also a few other ways we can configure the `-p` flag. By +default the `-p` flag will bind the specified port to all interfaces on +the host machine. But we can also specify a binding to a specific +interface, for example only to the `localhost`. + + $ sudo docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py + +This would bind port 5000 inside the container to port 5000 on the +`localhost` or `127.0.0.1` interface on the host machine. + +Or to bind port 5000 of the container to a dynamic port but only on the +`localhost` we could: + + $ sudo docker run -d -p 127.0.0.1::5000 training/webapp python app.py + +We can also bind UDP ports by adding a trailing `/udp`, for example: + + $ sudo docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py + +We also saw the useful `docker port` shortcut which showed us the +current port bindings, this is also useful for showing us specific port +configurations. For example if we've bound the container port to the +`localhost` on the host machine this will be shown in the `docker port` +output. + + $ docker port nostalgic_morse + 127.0.0.1:49155 + +> **Note:** +> The `-p` flag can be used multiple times to configure multiple ports. + +## Docker Container Linking + +Network port mappings are not the only way Docker containers can connect +to one another. Docker also has a linking system that allows you to link +multiple containers together and share connection information between +them. Docker linking will create a parent child relationship where the +parent container can see selected information about its child. + +## Container naming + +To perform this linking Docker relies on the names of your containers. +We've already seen that each container we create has an automatically +created name, indeed we've become familiar with our old friend +`nostalgic_morse` during this guide. You can also name containers +yourself. This naming provides two useful functions: + +1. It's useful to name containers that do specific functions in a way + that makes it easier for you to remember them, for example naming a + container with a web application in it `web`. + +2. It provides Docker with reference point that allows it to refer to other + containers, for example link container `web` to container `db`. + +You can name your container by using the `--name` flag, for example: + + $ sudo docker run -d -P --name web training/webapp python app.py + +You can see we've launched a new container and used the `--name` flag to +call the container `web`. We can see the container's name using the +`docker ps` command. + + $ sudo docker ps -l + CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + aed84ee21bde training/webapp:latest python app.py 12 hours ago Up 2 seconds 0.0.0.0:49154->5000/tcp web + +We can also use `docker inspect` to return the container's name. + + $ sudo docker inspect -f "{{ .Name }}" aed84ee21bde + /web + +> **Note:** +> Container names have to be unique. That means you can only call +> one container `web`. If you want to re-use a container name you must delete the +> old container with the `docker rm` command before you can create a new +> container with the same name. As an alternative you can use the `--rm` +> flag with the `docker run` command. This will delete the container +> immediately after it stops. + +## Container Linking + +Links allow containers to discover and securely communicate with each +other. To create a link you use the `--link` flag. Let's create a new +container, this one a database. + + $ sudo docker run -d --name db training/postgres + +Here we've created a new container called `db` using the `training/postgres` +image, which contains a PostgreSQL database. + +Now let's create a new `web` container and link it with our `db` container. + + $ sudo docker run -d -P --name web --link db:db training/webapp python app.py + +This will link the new `web` container with the `db` container we created +earlier. The `--link` flag takes the form: + + --link name:alias + +Where `name` is the name of the container we're linking to and `alias` is an +alias for the link name. We'll see how that alias gets used shortly. + +Let's look at our linked containers using `docker ps`. + + $ docker ps + CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + 349169744e49 training/postgres:latest su postgres -c '/usr About a minute ago Up About a minute 5432/tcp db + aed84ee21bde training/webapp:latest python app.py 16 hours ago Up 2 minutes 0.0.0.0:49154->5000/tcp db/web,web + +We can see our named containers, `db` and `web`, and we can see that the `web` +containers also shows `db/web` in the `NAMES` column. This tells us that the +`web` container is linked to the `db` container in a parent/child relationship. + +So what does linking the containers do? Well we've discovered the link creates +a parent-child relationship between the two containers. The parent container, +here `db`, can access information on the child container `web`. To do this +Docker creates a secure tunnel between the containers without the need to +expose any ports externally on the container. You'll note when we started the +`db` container we did not use either of the `-P` or `-p` flags. As we're +linking the containers we don't need to expose the PostgreSQL database via the +network. + +Docker exposes connectivity information for the parent container inside the +child container in two ways: + +* Environment variables, +* Updating the `/etc/host` file. + +Let's look first at the environment variables Docker sets. Inside the `web` +container let's run the `env` command to list the container's environment +variables. + + root@aed84ee21bde:/opt/webapp# env + HOSTNAME=aed84ee21bde + . . . + DB_NAME=/web/db + DB_PORT=tcp://172.17.0.5:5432 + DB_PORT_5000_TCP=tcp://172.17.0.5:5432 + DB_PORT_5000_TCP_PROTO=tcp + DB_PORT_5000_TCP_PORT=5432 + DB_PORT_5000_TCP_ADDR=172.17.0.5 + . . . + +> **Note**: +> These Environment variables are only set for the first process in the +> container. Similarly, some daemons (such as `sshd`) +> will scrub them when spawning shells for connection. + +We can see that Docker has created a series of environment variables with +useful information about our `db` container. Each variables is prefixed with +`DB` which is populated from the `alias` we specified above. If our `alias` +were `db1` the variables would be prefixed with `DB1_`. You can use these +environment variables to configure your applications to connect to the database +on the `db` container. The connection will be secure, private and only the +linked `web` container will be able to talk to the `db` container. + +In addition to the environment variables Docker adds a host entry for the +linked parent to the `/etc/hosts` file. Let's look at this file on the `web` +container now. + + root@aed84ee21bde:/opt/webapp# cat /etc/hosts + 172.17.0.7 aed84ee21bde + . . . + 172.17.0.5 db + +We can see two relevant host entries. The first is an entry for the `web` +container that uses the Container ID as a host name. The second entry uses the +link alias to reference the IP address of the `db` container. Let's try to ping +that host now via this host name. + + root@aed84ee21bde:/opt/webapp# apt-get install -yqq inetutils-ping + root@aed84ee21bde:/opt/webapp# ping db + PING db (172.17.0.5): 48 data bytes + 56 bytes from 172.17.0.5: icmp_seq=0 ttl=64 time=0.267 ms + 56 bytes from 172.17.0.5: icmp_seq=1 ttl=64 time=0.250 ms + 56 bytes from 172.17.0.5: icmp_seq=2 ttl=64 time=0.256 ms + +> **Note:** +> We had to install `ping` because our container didn't have it. + +We've used the `ping` command to ping the `db` container using it's host entry +which resolves to `172.17.0.5`. We can make use of this host entry to configure +an application to make use of our `db` container. + +> **Note:** +> You can link multiple child containers to a single parent. For +> example, we could have multiple web containers attached to our `db` +> container. + +# Next step + +Now we know how to link Docker containers together the next step is +learning how to manage data, volumes and mounts inside our containers. + +Go to [Managing Data in Containers](/userguide/dockervolumes). + diff --git a/docs/sources/userguide/dockerrepos.md b/docs/sources/userguide/dockerrepos.md new file mode 100644 index 000000000..d18ec13cc --- /dev/null +++ b/docs/sources/userguide/dockerrepos.md @@ -0,0 +1,176 @@ +page_title: Working with Docker.io +page_description: Learning how to use Docker.io to manage images and work flow +page_keywords: repo, Docker.io, Docker Hub, registry, index, repositories, usage, pull image, push image, image, documentation + +# Working with Docker.io + +So far we've seen a lot about how to use Docker on the command line and +your local host. We've seen [how to pull down +images](/userguide/usingdocker/) that you can run your containers from +and we've seen how to [create your own images](/userguide/dockerimages). + +Now we're going to learn a bit more about +[Docker.io](https://index.docker.io) and how you can use it to enhance +your Docker work flows. + +[Docker.io](https://index.docker.io) is the public registry that Docker +Inc maintains. It contains a huge collection of images, over 15,000, +that you can download and use to build your containers. It also provides +authentication, structure (you can setup teams and organizations), work +flow tools like webhooks and build triggers as well as privacy features +like private repositories for storing images you don't want to publicly +share. + +## Docker commands and Docker.io + +Docker acts as a client for these services via the `docker search`, +`pull`, `login` and `push` commands. + +## Searching for images + +As we've already seen we can search the +[Docker.io](https://index.docker.io) registry via it's search interface +or using the command line interface. Searching can find images by name, +user name or description: + + $ sudo docker search centos + NAME DESCRIPTION STARS OFFICIAL TRUSTED + centos Official CentOS 6 Image as of 12 April 2014 88 + tianon/centos CentOS 5 and 6, created using rinse instea... 21 + ... + +There you can see two example results: `centos` and +`tianon/centos`. The second result shows that it comes from +the public repository of a user, `tianon/`, while the first result, +`centos`, doesn't explicitly list a repository so it comes from the +trusted top-level namespace. The `/` character separates a user's +repository and the image name. + +Once you have found the image you want, you can download it: + + $ sudo docker pull centos + Pulling repository centos + 0b443ba03958: Download complete + 539c0211cd76: Download complete + 511136ea3c5a: Download complete + 7064731afe90: Download complete + +The image is now available to run a container from. + +## Contributing to Docker.io + +Anyone can pull public images from the [Docker.io](http://index.docker.io) +registry, but if you would like to share your own images, then you must +register a user first as we saw in the [first section of the Docker User +Guide](/userguide/dockerio/). + +To refresh your memory, you can create your user name and login to +[Docker.io](https://index.docker.io/account/signup/), or by running: + + $ sudo docker login + +This will prompt you for a user name, which will become a public +namespace for your public repositories, for example: + + training/webapp + +Here `training` is the user name and `webapp` is a repository owned by +that user. + +If your user name is available then `docker` will also prompt you to +enter a password and your e-mail address. It will then automatically log +you in. Now you're ready to commit and push your own images! + +> **Note:** +> Your authentication credentials will be stored in the [`.dockercfg` +> authentication file](#authentication-file) in your home directory. + +## Pushing a repository to Docker.io + +In order to push an repository to its registry you need to have named an image, +or committed your container to a named image as we saw +[here](/userguide/dockerimages). + +Now you can push this repository to the registry designated by its name +or tag. + + $ sudo docker push yourname/newimage + +The image will then be uploaded and available for use. + +## Features of Docker.io + +Now let's look at some of the features of Docker.io. You can find more +information [here](/docker-io/). + +* Private repositories +* Organizations and teams +* Automated Builds +* Webhooks + +## Private Repositories + +Sometimes you have images you don't want to make public and share with +everyone. So Docker.io allows you to have private repositories. You can +sign up for a plan [here](https://index.docker.io/plans/). + +## Organizations and teams + +One of the useful aspects of private repositories is that you can share +them only with members of your organization or team. Docker.io lets you +create organizations where you can collaborate with your colleagues and +manage private repositories. You can create and manage an organization +[here](https://index.docker.io/account/organizations/). + +## Automated Builds + +Automated Builds automate the building and updating of images from [GitHub](https://www.github.com) +or [BitBucket](http://bitbucket.com), directly on Docker.io. It works by adding a commit hook to +your selected GitHub or BitBucket repository, triggering a build and update when you push a +commit. + +### To setup an Automated Build + +1. Create a [Docker.io account](https://index.docker.io/) and login. +2. Link your GitHub or BitBucket account through the [`Link Accounts`](https://index.docker.io/account/accounts/) menu. +3. [Configure an Automated Build](https://index.docker.io/builds/). +4. Pick a GitHub or BitBucket project that has a `Dockerfile` that you want to build. +5. Pick the branch you want to build (the default is the `master` branch). +6. Give the Automated Build a name. +7. Assign an optional Docker tag to the Build. +8. Specify where the `Dockerfile` is located. The default is `/`. + +Once the Automated Build is configured it will automatically trigger a +build, and in a few minutes, if there are no errors, you will see your +new Automated Build on the [Docker.io](https://index.docker.io) Registry. +It will stay in sync with your GitHub and BitBucket repository until you +deactivate the Automated Build. + +If you want to see the status of your Automated Builds you can go to your +[Automated Builds page](https://index.docker.io/builds/) on the Docker.io, +and it will show you the status of your builds, and the build history. + +Once you've created an Automated Build you can deactivate or delete it. You +cannot however push to an Automated Build with the `docker push` command. +You can only manage it by committing code to your GitHub or BitBucket +repository. + +You can create multiple Automated Builds per repository and configure them +to point to specific `Dockerfile`'s or Git branches. + +### Build Triggers + +Automated Builds can also be triggered via a URL on Docker.io. This +allows you to rebuild an Automated build image on demand. + +## Webhooks + +Webhooks are attached to your repositories and allow you to trigger an +event when an image or updated image is pushed to the repository. With +a webhook you can specify a target URL and a JSON payload will be +delivered when the image is pushed. + +## Next steps + +Go and use Docker! + diff --git a/docs/sources/userguide/dockervolumes.md b/docs/sources/userguide/dockervolumes.md new file mode 100644 index 000000000..34cfe05b4 --- /dev/null +++ b/docs/sources/userguide/dockervolumes.md @@ -0,0 +1,142 @@ +page_title: Managing Data in Containers +page_description: How to manage data inside your Docker containers. +page_keywords: Examples, Usage, volume, docker, documentation, user guide, data, volumes + +# Managing Data in Containers + +So far we've been introduced some [basic Docker +concepts](/userguide/usingdocker/), seen how to work with [Docker +images](/userguide/dockerimages/) as well as learned about [networking +and links between containers](/userguide/dockerlinks/). In this section +we're going to discuss how you can manage data inside and between your +Docker containers. + +We're going to look at the two primary ways you can manage data in +Docker. + +* Data volumes, and +* Data volume containers. + +## Data volumes + +A *data volume* is a specially-designated directory within one or more +containers that bypasses the [*Union File +System*](/terms/layer/#ufs-def) to provide several useful features for +persistent or shared data: + +- Data volumes can be shared and reused between containers +- Changes to a data volume are made directly +- Changes to a data volume will not be included when you update an image +- Volumes persist until no containers use them + +### Adding a data volume + +You can add a data volume to a container using the `-v` flag with the +`docker run` command. You can use the `-v` multiple times in a single +`docker run` to mount multiple data volumes. Let's mount a single volume +now in our web application container. + + $ sudo docker run -d -P --name web -v /webapp training/webapp python app.py + +This will create a new volume inside a container at `/webapp`. + +> **Note:** +> You can also use the `VOLUME` instruction in a `Dockerfile` to add one or +> more new volumes to any container created from that image. + +### Mount a Host Directory as a Data Volume + +In addition to creating a volume using the `-v` flag you can also mount a +directory from your own host into a container. + + $ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py + +This will mount the local directory, `/src/webapp`, into the container as the +`/opt/webapp` directory. This is very useful for testing, for example we can +mount our source code inside the container and see our application at work as +we change the source code. The directory on the host must be specified as an +absolute path and if the directory doesn't exist Docker will automatically +create it for you. + +> **Note::** +> This is not available from a `Dockerfile` due the portability +> and sharing purpose of it. As the host directory is, by its nature, +> host-dependent it might not work all hosts. + +Docker defaults to a read-write volume but we can also mount a directory +read-only. + + $ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp:ro training/webapp python app.py + +Here we've mounted the same `/src/webapp` directory but we've added the `ro` +option to specify that the mount should be read-only. + +## Creating and mounting a Data Volume Container + +If you have some persistent data that you want to share between +containers, or want to use from non-persistent containers, it's best to +create a named Data Volume Container, and then to mount the data from +it. + +Let's create a new named container with a volume to share. + + $ docker run -d -v /dbdata --name dbdata training/postgres + +You can then use the `--volumes-from` flag to mount the `/dbdata` volume in another container. + + $ docker run -d --volumes-from dbdata --name db1 training/postgres + +And another: + + $ docker run -d --volumes-from dbdata --name db2 training/postgres + +You can use multiple `-volumes-from` parameters to bring together multiple data +volumes from multiple containers. + +You can also extend the chain by mounting the volume that came from the +`dbdata` container in yet another container via the `db1` or `db2` containers. + + $ docker run -d --name db3 --volumes-from db1 training/postgres + +If you remove containers that mount volumes, including the initial `dbdata` +container, or the subsequent containers `db1` and `db2`, the volumes will not +be deleted until there are no containers still referencing those volumes. This +allows you to upgrade, or effectively migrate data volumes between containers. + +## Backup, restore, or migrate data volumes + +Another useful function we can perform with volumes is use them for +backups, restores or migrations. We do this by using the +`--volumes-from` flag to create a new container that mounts that volume, +like so: + + $ sudo docker run --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata + +Here's we've launched a new container and mounted the volume from the +`dbdata` container. We've then mounted a local host directory as +`/backup`. Finally, we've passed a command that uses `tar` to backup the +contents of the `dbdata` volume to a `backup.tar` file inside our +`/backup` directory. When the command completes and the container stops +we'll be left with a backup of our `dbdata` volume. + +You could then to restore to the same container, or another that you've made +elsewhere. Create a new container. + + $ sudo docker run -v /dbdata --name dbdata2 ubuntu + +Then un-tar the backup file in the new container's data volume. + + $ sudo docker run --volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar + +You can use this techniques above to automate backup, migration and +restore testing using your preferred tools. + +# Next steps + +Now we've learned a bit more about how to use Docker we're going to see how to +combine Docker with the services available on +[Docker.io](https://index.docker.io) including Automated Builds and private +repositories. + +Go to [Working with Docker.io](/userguide/dockerrepos). + diff --git a/docs/sources/userguide/index.md b/docs/sources/userguide/index.md new file mode 100644 index 000000000..7f703b685 --- /dev/null +++ b/docs/sources/userguide/index.md @@ -0,0 +1,98 @@ +page_title: The Docker User Guide +page_description: The Docker User Guide home page +page_keywords: docker, introduction, documentation, about, technology, docker.io, user, guide, user's, manual, platform, framework, virtualization, home, intro + +# Welcome to the Docker User Guide + +In the [Introduction](/) you got a taste of what Docker is and how it +works. In this guide we're going to take you through the fundamentals of +using Docker and integrating it into your environment. + +We’ll teach you how to use Docker to: + +* Dockerizing your applications. +* Run your own containers. +* Build Docker images. +* Share your Docker images with others. +* And a whole lot more! + +We've broken this guide into major sections that take you through +the Docker life cycle: + +## Getting Started with Docker.io + +*How do I use Docker.io?* + +Docker.io is the central hub for Docker. It hosts public Docker images +and provides services to help you build and manage your Docker +environment. To learn more; + +Go to [Using Docker.io](/userguide/dockerio). + +## Dockerizing Applications: A "Hello World!" + +*How do I run applications inside containers?* + +Docker offers a *container-based* virtualization platform to power your +applications. To learn how to Dockerize applications and run them. + +Go to [Dockerizing Applications](/userguide/dockerizing). + +## Working with Containers + +*How do I manage my containers?* + +Once you get a grip on running your applications in Docker containers +we're going to show you how to manage those containers. To find out +about how to inspect, monitor and manage containers: + +Go to [Working With Containers](/userguide/usingdocker). + +## Working with Docker Images + +*How can I access, share and build my own images?* + +Once you've learnt how to use Docker it's time to take the next step and +learn how to build your own application images with Docker. + +Go to [Working with Docker Images](/userguide/dockerimages) + +## Linking Containers Together + +Until now we've seen how to build individual applications inside Docker +containers. Now learn how to build whole application stacks with Docker +by linking together multiple Docker containers. + +Go to [Linking Containers Together](/userguide/dockerlinks). + +## Managing Data in Containers + +Now we know how to link Docker containers together the next step is +learning how to manage data, volumes and mounts inside our containers. + +Go to [Managing Data in Containers](/userguide/dockervolumes). + +## Working with Docker.io + +Now we've learned a bit more about how to use Docker we're going to see +how to combine Docker with the services available on Docker.io including +Trusted Builds and private repositories. + +Go to [Working with Docker.io](/userguide/dockerrepos). + +## Getting help + +* [Docker homepage](http://www.docker.io/) +* [Docker.io](http://index.docker.io) +* [Docker blog](http://blog.docker.io/) +* [Docker documentation](http://docs.docker.io/) +* [Docker Getting Started Guide](http://www.docker.io/gettingstarted/) +* [Docker code on GitHub](https://github.com/dotcloud/docker) +* [Docker mailing + list](https://groups.google.com/forum/#!forum/docker-user) +* Docker on IRC: irc.freenode.net and channel #docker +* [Docker on Twitter](http://twitter.com/docker) +* Get [Docker help](http://stackoverflow.com/search?q=docker) on + StackOverflow +* [Docker.com](http://www.docker.com/) + diff --git a/docs/sources/userguide/login-web.png b/docs/sources/userguide/login-web.png new file mode 100644 index 0000000000000000000000000000000000000000..8fe04d829e1d2ade7c7e9690b6d8bba8af22ce67 GIT binary patch literal 31788 zcmZU41yoeu);J|04Jsfolz>P{HzNW9A|;9--5?CjkOR^nF*G72p%`>`cbC-AHN*@s z#1O*_f8Y0g@BQ9d|GUmz>)d^IoxRRo=bRley4q@#cOTxx!^5N0P*-`4hez-c4-fwl zDe+%O`;U(=@bD;99hH@JHI$Xvbv<0|9Gz|P@YE?w-`T%;GxuPn{*~otrlAf}btZrM zZ}0X@brYZTbUhV&s>~D>qSWg`5o00n!>~d{{E5|!1JR=gDhT3U7XfOC$B#t}EAmgq zSU(vV`Q5IV9%N&dU5~1d>Ms;7N<8su5aD-K0cDhl!osh4R3b@vzI`KSya*wFHG#*q zMy?w1Y3=gT@G@^Mi}|Bo-*4?VCAWL%BVyLdB3+`7yR>b@sP5^hCE`(8PA1r<4$jZ^Svnk z7xP9!>4UUp@opd0pQx~0JnSkwIW6WYiC~|%#R+PoS1WWs%dqG_9s%?bHtH&eF!Aue z*&?(5$%e}_4a&XyM-!4+m@D}91^3rxz%N+e(w@jYJFXVhc*x8HB> zr^P+pUKM∓}9%*j3)!Rlyh@kMEZLL`ue&21c4j`V)(yG1BvzV%OHw)?ti;!Us-y zbWaI;JK^fwlgbUxUo+-L`xfDk4<@UsaK*LVZ~LwCCH2$OsTiqy!>@VRM^qcWJ(WtD zde+FhEi=(}gL}NDemZOKZwY#%Z^cKb;=DrzYHJM@I2iziQd=o6Z1DEf`U(_DCEiVC zoiRP+op?BKgnll>nT+DUONexDw|$( ztmn9c(^X>{@%rMNF{xi zg(%JTPRM-zzTU65-HUBA9zPD1gHk_qu6nR+HBy;CmF{OM8|4ejyom{Zab=Yh!syp~ zGo5moAg!Lwp-lsi=2B50jowrtbWf6gxvn%yyT5Nk-7ctPI%$#`<~-2fINLaUwK*~E zR`$F{_wEnZk2}FhHg>dMf@x$;MKt+_(;tw9PCgN!RT)+-R3kIVF6jemOsn$kfEKxkTjbx- zr>5w&NtjYNrtYLlHRnVs@`!7oh?wx7B9o)#Rb&t*zJN>t$2bx6+N0e0g5 zh~jc1(C)s|Kry#Y^@W-Bn8JUJ^30O4uS1%VTC8W}snZCpXKza-o&Za1FI|PW0O4@o z*5|ufVUYMV>-+P>&in>p*f`ccZ3}pXBtjC6chF^K#pXs}zv6v~lY-FdYAG@_pvzr~%$Da38Y@yAj1$$jUUj@uiGv-mEyJ)%1Da>r#yWas`4 zynDt?)L-l`xl30dz*&+zy18_ zn7>yjqo@D9@tJ4NPS9xe7i#g*>pAv3^FrQz3I1|Ey_Av(`n?As3*tdMn~YrKQ{t z=2z^?>hGms-0INE((2KgclQ3=|IGV*`CR|Ze*fJb+aFgreIRp?I1t*N9sDsk?A9`T zFnWzPnO5@!g4Nx&$2csBSc#Y}Of(EaJV&X>c$cD^(v$Mt!&&APG0u3FIG%^2PZyoN zhJFMTt*Eyr6(remQODgZGfIJre}&T#KBqsuR{O&H&po(bW$tA6D!wosyg@2dwgX6*SYo z)!U|Te;BqK2JnmhMj3dPd?*d7pRGXGrN1w)RkX&=_RaGA*)h)Y&(by~ggL>OKnw`*I0j?Mmj21eB)s2LjGE@zsxgJ*k{| z?N04p?OJW>7pH3d31;F=L1(8T2G&#kB`ltHBlh;xaCta#%*Vvbw69UX!_mucZ-4EH zN2y2aPM>?MTh`I+8oFzF(sZM30h>-TZr##f4;TgH$+ya<TZ%*ChklIv z$VRY8I7%21@+X8kOdzaBNiH;(2u1=YIwA`KY_Jqi$lN=ToRRW!anNU^eZXY!VD@1d zb1q98$4yYUstQ;Iq@q)#wuKn@{`mcyS2IIDvL<;Z3wFQk=DM40?j$(HSoS~abB^|7 zJfQg<2V#Lj*`$L0TsG5}@DA|K^edToE&XZ;K-Hn{pxU*IbmH<1B~b36nqAr9Vlgc} zjxo*jtB)cc3F}AR~i6XHn{VAWK zi=g|sKVQmac>-PuA(plMpUzX+j1IM~#sCS6R zmu0E5GGxmA+O}_cPvst{2+h|f);{%Fca6S0kJWl0Y)| zRMzS>q@*{^!^=4-?TBlt<)E`iR6Qg z-|S+oN2v-~x8)R(sYlMMX%)Y}_5J>CSC2Z}uJtYbRp&ImrH~PP7xUAye0rr6bBP(c z=nndWD+b!%1a3YX3E2r-A-TX=VTb#aTa<0R4VO!ra{V140m|={Ve9TuSY`MRtFb{1-tyX4&W;NAJ@*lN(bK%+ zM0qknv$u#O&o($!-i4xhaOdgsWmRZwRM6=qQunvL(=vCtCS?XhNUAO#BnRd`9*X&G z`A)|?whg>~9ex0kN_JgplDV?O2%<{IV1D)HH)b~ht2uXk9DRll9an$1xCXXg1JSn& zbB7B-XG-&7vDEZzD}Ims&k)cqpdwfxXvELtst2K=RqO;$DvK@~)@k=&M7=_llsBL0 zjQAZ=1Re)h|HO3)bp!?+wG}$k>%hvaPaaH^J>7Y7A~u;-u8%M73qJ3#f?PG zQ0rXV1=<9fTnys$H{qYheK4mO%>C!zbXytNb)ojQ_$^Ch9U963QNwrwhaH)k$PmhG zdfGl>X4P9l>g?gigp0}@W9%Qd?%-8w;brMnzxW(~ID?ksBXyIiUyo$me=_3X`Ia!Q zRK+;O2a7F#g~u3=mzRpgLb9$gK#*F%ISfzh4xzomP?oGRj{WO|YkKwQPt0ohEy3^S zj$d`X`epv!E6Ci`jXd%29&rEb#n*WK==krIBiHebp_if7D_Lt-kdUQ~tCg*gFUajL zHXfe5uk7D0$kxk}-52ES;wkH^!1+HIvVZ&k0t<7p{|}0nlLDuqmM*)ptA{PSgpjz< zQ%=RZ?Ck9F9yWHeuT@_BANk)e1x^PqFE?3XVILnKAs;azR}Xt(5g8d7;isa)qN0L- zF$6vRT)ZrO1zkM3{+G$W`KZ`>T6;LUc{#eeu>Xs%rIo9;eU+?iwHdx{txfJr1JlQ%IZ4$+BzGmID%|lJpam26p@yBD*rzO|KHF* zBL9oj=pRyPNzwnN{I8M!qm&o^*9`t^M*nNB|AGFsm*QP{;s4lP@$S#(PkZq2p5bYz zD82E;N3@cBG*fN7y>(u79{he$wHN=WC^2y&E0q0fD5*h%+4-+Ig1Lh~2jk##pY!1J z@&ZG>@;gL&GCKO;$+D%WJlDwfJwJ4@_z#C~VwD%XA| zTgrMoS3&DLI|1i^+Jr5s4LCFT*VlhQ>;xeniM~DzC5`_0f6YsTfbOGdN!MEwS{nTS zYXgdQq@to`qrWt!smvqtCH@(at|Ll_!E<(Y7C!}wTTBFHW~T22-~Dg?{ka{J^dAqm zh6hF}e(itAlDc(-nd><=`dt~Ao4Ynu=|=Q z;R~K~70<ps`D!`qqSUsgZ!V3!I%q|_2!-83~EApJp=1tL%yhwpSPv?$NZI=`qI-mt&A2S$ve@;A?!=N3R&2Z#tw zMSJr`j4-;}%ad>pFlCyr(A-1Cmiz#Jq&)VMJVL5N1 z$_Uwr`OiWI5$FZ5N=6_MJARNxA%_!>Qvp~+>eYmc^4o8J-o7;id8hrEy9D>*JC#T~ zP`2gjy|X@Ss?PrMizee$CiCZv47lS~;1cj19#YJi(Um@eOnI1td=gerjd4a%iMaCwRI+Z zp6e4dMrHwTek!!BF~zh?t$kDGbZa4V8|7(clU}j?FnaqqL$GwA_W8^Z>QQ3d+z!Xr zKBp%_)U>7D&8x{IG?g`@@Eg!-TQ~f|_I+LOh~M&cZCS@jNnb035|^?+^UJ((xii~Z6lLq%6sYr5qz3dfFzzA7 z=V;kUO3-&@<#71I#H($)nKV8hDdP@=*D_Gu2lg#jc{gK^!gS_46kDASHN;9PzGnTA zYG4(lrJGiG^FGGis>yu;oh|i=d3^FUk;>Xz0uyBusddW}t?<`z!wWG7<=+kRth} zhJm}Mr^o&4EAKEro=n*{O!oO8avAm+$LyQLbeaa|EcT_~Gm`a>?xp)&sM^Md%8 zgHg5B8{fE$hF7n6&5RLFrCjE&PHG!hgND?gkAd<}Au+oBftSiNTG4AL8LmYZbnS&s zC^p76-Z9*k6E3;ecV>J?O|Cy?dcp&e47lpCl?NgmuHfF_M*u{JtY0sH=eH_RT1G|y zvrzp?tN)!`4%ZV0{hdV6MjO?9lJAp?bLqEn{yY<7l@gUJ3w@GkcMuK9dY`222cDsq zrw)YMkEk!qS5JnwptUv9ac!t=4L4-Iin>7OC(aQSnF-{gS6J z;K2rgn~xqf{}bU;nPABBCY<#%HOaecU00^Ok0_|(JBIU`E(PO}7q4nca+q0P)cx7n zdhf0E?Y{RBX!*-SlaZ^4=ht2ZxoXeSl0aeC=aoPC>lYMB=1cq9Ip)h}Tt*=onE?!8 zvR)2A($})_kQEdXw?w_ggyQ*pkv}~DG52}6$ z4{22<@q4fE@)`Zc9rQ6OvQGv>?aOq5K$#o=ti;Fte$wiXCUrj_ujHOs1Uz|y$h6O` zG^(~U3YimRz+ImofT99M7y5evt}nCc_ouaNHF%XtM*QQH`J&ZiZ+f+h3%_w`ovih> zT6Bh580+cjIoWsrp#epMlYt@Vhe$iQoA3um9M*R z_AQKLika)b+oF;4{9fuNgfQ%HQ)7e7Xv?E|W=~YW*=kEYvbH%86eIPw{QgtOOmJ&# z44UBwOts5C4d>eOQ$>n%rN;KgH#>fxV4dZs9ihI6!aF_Z^Xa>U>4L@&_D_pawC{pST4gy)EAspi7RH={ z1OnFBN#4#xq}aH;n28g$!dYVLMe7kIhL9e}o1W>(r~it#icC)Sn0ha<-})2|oB65V zT?;;Hy;$5U&T|PPro4L&z=$&5ZnE8yVigZsmz9XSa~1p-3wmP_Y46M`U6z}SzTI*z zYE;|#w;KZX-O-4JPBc6(c!h2119C=Rr#7BhYO#7;(TSe7$$I$I57o=i?Y%V3w)Wu% zhAQhCx>zY!rgUVe0Ne4EP~TFw{h#}QX=_ZP>v!eh3)=+p zp1!3(;34INLI{&$@HiXv2Z;apDn!fgPI|Y94u7+)qGd_|CB6xhDG8?kogg@ph=N3-p#);Et{3KXtgG8NJFT`Q|AKB;MwhZd@eQf@9i^=!ThBhIySnbI?{vG=UL_(`|& z(@m_HQumgXa$fWOZ-dgRz3JkpgQx>hMQ>}~jnIRzXYd}Gd2Ja!W_;sHa?E2AuxIu& zKiZc*D}MITVR%L@zLm>2#lVHnBGx6rx42v$$C*0tWfB>@zoznzoYe};fqJx5)-tZb zXIe{Y!Lc~Uj-KZgsm{_Y~7zgP}BjWXtIoxd0&Oa$UFB>#R@>N|hrAx8yCc z!g3rR19~78AzUFfe9R#v#EXNP!mT#&oB)@DT~!-AV??AJT;DaI1-Ndf!fuZEHnygn zJyF(THV_5LpC+$_-mp1;eU%DF!(8hD3jXu3Rdn4kUK|al1tHj)@LRjdY#?>&Vb zl)KF&syS~KrNuono&K&togV!G0PthqG^~jM6ZX&7Jtpy9IktI({cp3TAhVn^FU{1=P1tJv?aTIwr84!I(TNSNX*~ z-D_3*jy*3v-}s~l`)&igPzEyYia0RM04+53n;F&()oq>CaRh~=UdeYTkX*n3i|=cF z#GC8q-){3Gsu}XOS&u*C<=piLPm-hAfDN4wK#O+vP`=vnoVg_>A=BzH99;NfzDNlo zD9#AKt?%NFc9s=y?o+;sA3uK8ZWKI+ZF^GP zUd+M}0re}kaBU`6pEa1Z zfF}4hXk&8RzX46|J$Mj_mRZevRNt)j#UI``I{Jy%*d^NwV1l6VJ|-GSVPoxj5Io^~ z*a&}K+%XzH#$yx$4n4MDLR=iAB;*w9^w8>*$R}-d2ChdiJZF*jBMfX4vdtIUeDP9V zR@H@b$%aSk%gho-bAD{-5Jdf18%44m$U~g@0|?> zAFp+CAn)@VgfrhayN~8B3@dgmd_@Y}4;9Dwt(@s#AiK8fT@iJ|3gFseG=sK@--w6VO|ZqI)_C*od_L2n9n zU_!N)KK4V|Tw}3)raR^W&mQL;a(c%H)YBAHtiUmr>e$YVEcx>uEm|ebD?s*z#YEr? z`IY}%V(PQ2ceV4K@rm+{j(Sw-OE-_nVM{&7zc!ePk=AnF12Rj7v`+ABPwnHL*W*un z#VG8D?n?FGAw#?uf!^I$HE+xAm$8<~g+Jb-XW&33&yjTD2UQe6CdX)xCYzrJ%`Z$o zgJh(qN20svsOAT~<1(U|WhJc$F~_8xWa%bzHNcivskE*%D{mNI1JILFYrWj8`^|k*VJJhfW^miPr!OEWxUSo(x@2CfKc8!f|X^EnSJ11m}Eq> z-@K|sq3M}>c5Fwah{@ldo;Cdj<)+Hof4$B~t$tIfZM^n=!_p!DBB@1pDCqhthvH&J zTu^9><%^~wHNT(Kj2@$X)~T$3%EO=S%&sjW^O5#xc;*|{yqw25M}fu5kc;JfL36K3 z)eydm&=)=gx_KtNXAxU!t>LjUMX4>pRc)8O!a-QwLA_nA6`5O%FwTa$WZsIpwP7k3 zR<~N!S|*0Y|JA{)R~cI+AABk`&HrRr^?kaleY20Suvq`yz{d@ry)*n4aCGVh-)>pw z?H4d$JNNt;V_;b z`HK>|$Sp&HNRAXl$4qjAx9`})5-q!xt?e9q4ia+EVa&ny)Pm;Lc_{0-pqqyQS>ycK za-NYnqMlzU9}iT&Jz$DNyV9a}U0mg`Zvw!mSajvl?Tj52lZAQ94k7p)BUG)g{l&7_ zuU`?=W%#>P+Ts=i<-M7fXcXG#X?j{xa}bP`!z^>@Eby-Sm6>A3-@)#{vn{KOb*g2= zzQg2nvTUorYt`oB9yLrV1d!*NI}>U)!8W)xk+d0JU$1Rv$$2MJTT<7-%>xJ9&IP+|!2I@M!n9ux z^I_71y8%wir+r%C%;Gt8tTJdWX}{!c;vUMV%_Wt_pNGfH1X6{~hfVy5;zCu5woTR} z(yrqW5&)^xI~PQe8vK<#RahSGtT>-=n?;^s$)Ulr^}zJ#uKOP1m|~-mLy>i7^%i%* zJ`p|a-ov;@EU}q$nnWb)jcZ7vV3r&qreb02;kPX&G;0Qf#0VMIp3XP>K;zk@6z7L2 z@tSGG5}5NrSmG3(oLL(YesccO(Pho>clO z4P+Z+N)@^c^S*!2t5KUX98z}Syv{V)TTx$M2y9_^>uKt1yK5l%RLl1nYJ&YiCOS2Sl;Fo<(@d>ISez zb!toYp%^$0_)I*&Y&}FRBdQ3b_z04;ilEIW#kD~&kq1%!xro&|ZpuZ@IYp^}9?>~z zTYn5}6|hJQA>7`&{#xcJT%l^g73t`T*z^zMFaZSIh-7(Bm2{-Z)4nPiuZ@FjkrnTb zDrZmmpgNvq()`TuWkoI2!dsTzwG=nJqjXhm6t<8-AB$BNi{@w(ETRZEaunTLfloQB z2IwEuK!&7TSLEexZ%)JJtyq)9gK?YS6GVzKZ0%vUxg z*)5^d-sx?yADSmQLdb}u2b*xEMjXV0Uu125Sr5F%E4zPw@H!NoxS+)zhrNPvd*1#XsfWRZt#MZgu_i z8PCh3Ar@h&o38ur%w~4pwSBzDz;-x#9I2=LDHo8>2U}jwRLq`UgZnQz>kRYWF5F#> zQmdRC|9O$W9+W$G+2;>~7kD%Wo9%3|4kJ&G$@9=P^9A%r;c$kKkgME33kyOJPTqv11A_G^?}&FyN9*?oVqhTg#0pxa+cV)X#Sz zwl$RavPCr##TXf3ofII<(ALspfC=oWenD058i>=E^y~6njac;zJu<0xYXy-W2 znfNuv116Ue8R;z46W5i^J{g%E=8dB|Qs%gHgxp+vWHBoYK+B`x@o$y`8L`lo4V6DM zMz<`U1SZ)gBVHY-NNwIvyfIm{D`K*gvb^#PUNv48OJ8X;f6t&22Ls4q@`0)=SVw$ z=Au}gf6&mGq%)GoESn?GD(|f+KefNNw{g`y+$U3!;5{~4T2muFLw|)*%bAR{7;O<7 zQkJ1Hbly}jIS?4EFXVV#yfkw^L{g)*0t(hH!By=kqo%B(D!y+N0cClO^ZIG;kl)j2g|C1CAg{MeaG>G18syP6-7rd$8*)~ zkwY%JALC)cFn6VLk;^rda^E6^@tZ)j{Gu2@6g?e8V_d_!NRjQpFmI&0maFATo*Gd% zkf*GXZd3{im;K%X!Jx_K_^Iy`oqVJE0X*q5+lXu%xxl8DrOodF` zT%w*=GyV!W=}>9y{Ci4iX5dw|sgIz0VX>)ww^M(n>g{jjoYd`tq_6eQ@{4*9`6q$$ z4mqg|CIzr_g$sWk`6~MN(aP|{PXR`S?Yoong?Yl*6ak^#6RGm5jXOR@V`ZhYYo0{i zD7vGb8)2?t@SJqukLFeO&7DkFQ zPI8h9QECqseu!+AcHt``T{&;j=`iQR-Wjh-K!jyCm$QR#94_>3-4|H#OuFw3D&>2g3mAR#9oh=7Wg)A!sM*}w`nkfG|=_HL? zddK9vwT}-cIVMSGK}*i7SEz9WFvvS4c8CCT8XP#wlfcY;Vvg*OOn7L++gxAS!Ax$O zdK_B=y-lW)SqU`cWN~0K*(GI>myhP#|B{ePbw?>gEgGyuGQft6+pGZ*;jtqxBaghz#rkqEO$ef6#WE}#8QKYsZO>|Mtw&Qo-60^$7d~wH!4?B0A4FRdhSycWg~2VsFCv#a_=E2v@f{a)Dvk+f7B zZrN3|Q4HYSBlRp0g57j1W;+(60R@kW;4H!{*7{^SThl3}0m+ONX*WNDT)1tYj1*Zj`P*%k~C!eK+`jAj*~pEY3!cU@%> z#k|UD%rdEybU#Ur91dOYCMbr8&#BOO8Q@yU#W*uX^^*i3{lmCrQV*|-2RB*X5w9p# z=RVaTG&E2f|ItQUggVrBl#jv8Xi;V(x2Xu>YX~LS*DW%jW`9u%t64}dGqHsNPqSOM zgp~z6-Dj$e6VCZgi|1R7OJG5H)>WW1+jLOfQc`tE(3*pjfB>0U`^c}bbR)lsDxdOt z1oC%l@vjQRH>lC!kCc+i;@cgq4=IxD|CEt%lG6)tnEqRy)%}=}4`)C9^d4(*b zX4x|-I%oG9_hw}@&YOTFlzEIQi)$^X)G^pR*LrV7-u?Ua0cvbBJg1=MM0 zcil>TEH85{9_QRn*U_FKr-z9z1juqcTUT4aeh<3?$JnI!7>PP};1a^7G#J&H>gM*C z=GAtMT?R5?qt9;_Cqf9jq=+^n9-KNgj|>jgNE~7Vjw1fLJ2mLP1o!sjQ8B{He|U*x zW`Pt;Se~*a`BbNu7&-#4N7)eL<82o3Jn2ascJm#qhW<<5y>6{OVMI(Av0>6`#pk%; zlov43x>_YzeNk{Z#D; z@;s`9v5PMJm zCI!Q%Y_-hFOmSLJrPh>Q6?81lDb|zJmt}s(F*hN5aLOy2LMiRiM`18((L}XR-@oA^P#4I zdkQ(apZW#jHqmF(w;F%8O&^Ty5L2HS29q4?uu6?#YfxHngpQas*~t2wFpq;S_82<1HSt5q+Muz@iq&9 zB3e@xQEc`@%}lWXcbsgc?`p3Gdo-R9>C2k*pk*+tW?cvN&6ESz?S14OTUfcQqU^ahheyEb3g|9-W&M?s%aBpX>SFNo=nTBe=T<{G{p^R@sIXJL^4w= zRxWpM|N32a7s3XfEIxVGr!0gepCiYzfs&soxKW;dea>?Ic0XP=UopX8;FuR`_tNZ# z2g4(h)}*O5H;HanI2wcwcFnTCxDP#VbmlfV?CK5(8a~lxuWp-F@TFR;7rQF?6a~o) z$_+SgvTrt?>9i5MhajVcN(bI+>QulBa;kY1?7uA+l8F5hFGVP_U{`^+r3&biyUo`(VhDP+hN-@U54r5G z!ZySt%&{xyij^%xZ5Xz^`gP?Lu}O)=Qrmt~-6JntKEyxZtCm~!7F0VtAx1`PIWT7| z71?uXvRlI}6g2A??6nb1AS*rfwNS&L!|Y`J`!0jyW%~fq>b9*4qO|5-2$!`}7xiUr z5lH>r2z3BRerG}L>$@QSTHlyMGip%A_ zmo1KZkv4Y_>^P0<(R ziv;cseHBpYr?!eh=bG}2i9xWSQ0x-<1ro-VFiB;-@H60V#J>Xgol>qPzK7QoR}m}w z$SvE$0!Woy1+mSi?t0;@^-8P~pix{Ha{ckXfCjXwmkI%wwPZr_3Q;Sa#YYS6bXjpx zFGRlp@T9!$FH&wzDWBx9WpgJ3?D=6{W1^@66c9S=SWLRF}Y=w6s; z3K8}DoNg+%G`4iPS{!1#s{7rK&|$h>1UNnT4nYLZCVXN(CpCZ)BAMT=<%A1?cdm3u z9WGktF(o@PWM9;|(e^j`MOP>CTwz{@QQwMsjnDnJVRD10W z0cTPUrb-diM$`}IS9ubc9D{F1LFk@P`);3$yEhvkFq@Rm7CDwXNiEmZhLD#6)u;jK zrYt=}IHK#L*y@d@bttZM2Z^2twER3+Y5mxJq1Q(&d^#1Mg&YRnWI{02w;Oq-nd^Xd z3I;dj(*B=?AYAqMoq8Mu3UnNt@&~I(KL=9_>TwV@XtYg-u$vkM&)8&UXPM$P_t)TrM&Pf8JI3Q+jr@p1)Y zMM@p4*fp<$e+%A^qN^B-=OsquL%+sVj>OomHEA#L0H2RY1(ikfF4w(UdluBA>gLXa zvR35Ce{@5*>T8|g*Y(lEJ&ePG1VWu7hIY#qRpaT2@gAjPURPm>OwTM*NkAS#kA5B*m2hUBDkICzv@R-SG1l_ajmfYJwY1Bxi|E^GVv*3dB^7jP# zT~Sfxvw`KTb5xBjRMnkH^af5o(zurNgs=v?v!Qv+fR(xPI`Yoy@=@ZXDB@V%zSDlm z(_0#-J87GYod>R$YCnk^A5$P5N;0PqH_uj9{-7BZ=SlT9AU0yKX_t8BbP<+|ZEq|wN-;?$y|9ACBNm{L$~?QQz{2IH8xL)mwe=9RP-_H~ zMT0k;kaj_m=E5OjuH{ag5=ywMSY+n1*c^9uBFlev71Yv_1Yc>DkIFj|cD&cXc(CX3 zsGnR!b{ZI4Vc8b{WT+Ni{V7m-u6=K-U+i)zwm4@SRsDVzg|oeqG4jR3c8z#(=k0Y| z+N~ubSYU{hVd3SADlF{n5e0+DOJ?fl)sQx4Q`ew{P^!P7kO`_wmC39fpmjtpgH<|l zr_6yAs1Ame3l0vlX^Ur(oKQRzHch2sVhQ@>vO+F=^=R)Lw^K!Z;a@!LCeYm)YaY}a z^fx_sp&Ms_Di)F*A5-)$J4f>_eyUOcPZyGxbgudep{TOte?byB08O^PXTyhV+SYk) zpPenA=GEyJ=UNm-QB?K*o{Ovx;f-UoQ(p2Oop9akBt*wX?HFe^CsP|3z`hla*ItFD6O@g4NwvNGhLs7Q02U&L@ zB88^6Qkz7#FqRi;o@m^}*7e_5T{{Or8Or3)wY74&49Em{?Xva~+jxv$FVa_gX-9hF z$bsmDz1@M(@6ir32$fTsO$Vh1_Qhx>Ob4Gx*$hm$5q5@!Tv|x;=R)y5UG+hVI@w@) z$I05SvOFuecbGS;Sf{OWPOaZ(2a6Nlr=!*0GbNZS;*WQlsu zi#+b)n6uNAo{@>Uss?2CoNl<+Sq49Drk{2#?fojJ{FMvM~lh8Y?EI9_d!W{z#X->p!%xoSH`a= zfE4u25B<;MTX&JC;$&JvmtTGbMt}ZR+U+|1LjPgjFZC+NYe)8ruR{@c0*IFsV;O2h5ZPWya88Z)9Bp1qN0>=%6bjWSpF zcm#Q@(GnbVBGcS|XTg9C6TH&9VSX4e(T0jBU+B>5zUfV-cOI@of?I>Hw>K8VZsM0U zOD1{iSJ7Lib>nb4t%qW=z5*nTt@oRK7CjH7j9qU_Er_=1@=5}`N9z|Z+ON5p#jaH3 zESKilUsAIO)JNs{_dS6Wp!|wQ$dp$rCyBmZ!yD+o3f57^V62!E+!q0IGq^f`< zpVy}7_qmli^N;HfA5ryNOV9Yjd_{s_SmaI`O(PN&uB>4czsH--wfMO~xGy%d&l;40 zGD$x(_RWvHHp%-Npd+SvPCo9dh@m@;K5%CmCmWV@U%2?>9}}8w#NT`?_(WFL^>tt1;$nuy)WJRvDx0w!c?B9y zVib1Ami9SijUPB`{D@_g5@~P1HOV?DOjM?Qh93<5v3j@RUdGMxbmWdrn>Ya*Dr4kW zR*sI&H9%l7orb#x*#LCE`Xb$y^w|Jrt>%Q0AUO8Uly)N`2K=yMTWB3@H7dT?wYw8g zcO~qHsLhukaW)(clH>Ah&&ds#crXJudYe~S$6a2G^icY|36Nl;bZKk&DKD$2L%j}v z(#P8=u0zMWN_X@a)zn~P1sJ1os(?dMVfed0BC$97M{Mf%{l(Z{TGJAy(BYq`&Z@vW~COu^Sb zUSoP}k7}SMC;&4;7;?O&#B`}O%7?qkQ0iU2QJ$|w&kspR2LJKrN7al$Fh*dMpM4ay zg6oAwPJ26ymt*|P2m6ss5Pg3F+FbW%)hZTC+k8Bd(w!(~*20J5)D&jB7V;GCEYy6r z*e%@D3RyPG<39u6)+Q=ag!o+@iz=S=Os{|U=rk&hkP6>ydBXVQZ$7%0A_0Y^2+O@| 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 literal 0 HcmV?d00001 diff --git a/docs/sources/userguide/register-confirm.png b/docs/sources/userguide/register-confirm.png new file mode 100644 index 0000000000000000000000000000000000000000..4057cbe965412f5402303a8e35cc5bbc92647e55 GIT binary patch literal 65441 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

UVUGuSRHi;C|ljP9%( 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

HV#vpVSNkc^cSk>(*T69*4xD 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 **Tip:** +> Another way to learn about `docker` commands is our +> [interactive tutorial](https://www.docker.io/gettingstarted). + +The `docker` client is pretty simple. Each action you can take +with Docker is a command and each command can take a series of +flags and arguments. + + # Usage: [sudo] docker [flags] [command] [arguments] .. + # Example: + $ docker run -i -t ubuntu /bin/bash + +Let's see this in action by using the `docker version` command to return +version information on the currently installed Docker client and daemon. + + $ sudo docker version + +This command will not only provide you the version of Docker client and +daemon you are using, but also the version of Go (the programming +language powering Docker). + + Client version: 0.8.0 + Go version (client): go1.2 + + Git commit (client): cc3a8c8 + Server version: 0.8.0 + + Git commit (server): cc3a8c8 + Go version (server): go1.2 + + Last stable version: 0.8.0 + +### Seeing what the Docker client can do + +We can see all of the commands available to us with the Docker client by +running the `docker` binary without any options. + + $ sudo docker + +You will see a list of all currently available commands. + + Commands: + attach Attach to a running container + build Build an image from a Dockerfile + commit Create a new image from a container's changes + . . . + +### Seeing Docker command usage + +You can also zoom in and review the usage for specific Docker commands. + +Try typing Docker followed with a `[command]` to see the usage for that +command: + + $ sudo docker attach + Help output . . . + +Or you can also pass the `--help` flag to the `docker` binary. + + $ sudo docker images --help + +This will display the help text and all available flags: + + Usage: docker attach [OPTIONS] CONTAINER + + Attach to a running container + + --no-stdin=false: Do not attach stdin + --sig-proxy=true: Proxify all received signal to the process (even in non-tty mode) + + +None of the containers we've run did anything particularly useful +though. So let's build on that experience by running an example web +application in Docker. + +> **Note:** +> You can see a full list of Docker's commands +> [here](/reference/commandline/cli/). + +## Running a Web Application in Docker + +So now we've learnt a bit more about the `docker` client let's move onto +the important stuff: running more containers. So far none of the +containers we've run did anything particularly useful though. So let's +build on that experience by running an example web application in +Docker. + +For our web application we're going to run a Python Flask application. +Let's start with a `docker run` command. + + $ sudo docker run -d -P training/webapp python app.py + +Let's review what our command did. We've specified two flags: `-d` and +`-P`. We've already seen the `-d` flag which tells Docker to run the +container in the background. The `-P` flag is new and tells Docker to +map any required network ports inside our container to our host. This +lets us view our web application. + +We've specified an image: `training/webapp`. This image is a +pre-built image we've created that contains a simple Python Flask web +application. + +Lastly, we've specified a command for our container to run: `python +app.py`. This launches our web application. + +> **Note:** +> You can see more detail on the `docker run` command in the [command +> reference](/reference/commandline/cli/#run) and the [Docker Run +> Reference](/reference/run/). + +## Viewing our Web Application Container + +Now let's see our running container using the `docker ps` command. + + $ sudo docker ps -l + CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + bc533791f3f5 training/webapp:latest python app.py 5 seconds ago Up 2 seconds 0.0.0.0:49155->5000/tcp nostalgic_morse + +You can see we've specified a new flag, `-l`, for the `docker ps` +command. This tells the `docker ps` command to return the details of the +*last* container started. + +> **Note:** +> The `docker ps` command only shows running containers. If you want to +> see stopped containers too use the `-a` flag. + +We can see the same details we saw [when we first Dockerized a +container](/userguide/dockerizing) with one important addition in the `PORTS` +column. + + PORTS + 0.0.0.0:49155->5000/tcp + +When we passed the `-P` flag to the `docker run` command Docker mapped any +ports exposed in our image to our host. + +> **Note:** +> We'll learn more about how to expose ports in Docker images when +> [we learn how to build images](/userguide/dockerimages). + +In this case Docker has exposed port 5000 (the default Python Flask +port) on port 49155. + +Network port bindings are very configurable in Docker. In our last +example the `-P` flag is a shortcut for `-p 5000` that makes port 5000 +inside the container to a high port (from the range 49000 to 49900) on +the local Docker host. We can also bind Docker container's to specific +ports using the `-p` flag, for example: + + $ sudo docker run -d -p 5000:5000 training/webapp python app.py + +This would map port 5000 inside our container to port 5000 on our local +host. You might be asking about now: why wouldn't we just want to always +use 1:1 port mappings in Docker containers rather than mapping to high +ports? Well 1:1 mappings have the constraint of only being able to map +one of each port on your local host. Let's say you want to test two +Python applications: both bound to port 5000 inside your container. +Without Docker's port mapping you could only access one at a time. + +So let's now browse to port 49155 in a web browser to +see the application. + +![Viewing the web application](/userguide/webapp1.png). + +Our Python application is live! + +## A Network Port Shortcut + +Using the `docker ps` command to return the mapped port is a bit clumsy so +Docker has a useful shortcut we can use: `docker port`. To use `docker port` we +specify the ID or name of our container and then the port for which we need the +corresponding public-facing port. + + $ sudo docker port nostalgic_morse 5000 + 0.0.0.0:49155 + +In this case we've looked up what port is mapped externally to port 5000 inside +the container. + +## Viewing the Web Application's Logs + +Let's also find out a bit more about what's happening with our application and +use another of the commands we've learnt, `docker logs`. + + $ sudo docker logs -f nostalgic_morse + * Running on http://0.0.0.0:5000/ + 10.0.2.2 - - [23/May/2014 20:16:31] "GET / HTTP/1.1" 200 - + 10.0.2.2 - - [23/May/2014 20:16:31] "GET /favicon.ico HTTP/1.1" 404 - + +This time though we've added a new flag, `-f`. This causes the `docker +logs` command to act like the `tail -f` command and watch the +container's standard out. We can see here the logs from Flask showing +the application running on port 5000 and the access log entries for it. + +## Looking at our Web Application Container's processes + +In addition to the container's logs we can also examine the processes +running inside it using the `docker top` command. + + $ sudo docker top nostalgic_morse + PID USER COMMAND + 854 root python app.py + +Here we can see our `python app.py` command is the only process running inside +the container. + +## Inspecting our Web Application Container + +Lastly, we can take a low-level dive into our Docker container using the +`docker inspect` command. It returns a JSON hash of useful configuration +and status information about Docker containers. + + $ docker inspect nostalgic_morse + +Let's see a sample of that JSON output. + + [{ + "ID": "bc533791f3f500b280a9626688bc79e342e3ea0d528efe3a86a51ecb28ea20", + "Created": "2014-05-26T05:52:40.808952951Z", + "Path": "python", + "Args": [ + "app.py" + ], + "Config": { + "Hostname": "bc533791f3f5", + "Domainname": "", + "User": "", + . . . + +We can also narrow down the information we want to return by requesting a +specific element, for example to return the container's IP address we would: + + $ sudo docker inspect -f '{{ .NetworkSettings.IPAddress }}' + 172.17.0.5 + +## Stopping our Web Application Container + +Okay we've seen web application working. Now let's stop it using the +`docker stop` command and the name of our container: `nostalgic_morse`. + + $ sudo docker stop nostalgic_morse + nostalgic_morse + +We can now use the `docker ps` command to check if the container has +been stopped. + + $ sudo docker ps -l + +## Restarting our Web Application Container + +Oops! Just after you stopped the container you get a call to say another +developer needs the container back. From here you have two choices: you +can create a new container or restart the old one. Let's look at +starting our previous container back up. + + $ sudo docker start nostalgic_morse + nostalgic_morse + +Now quickly run `docker ps -l` again to see the running container is +back up or browse to the container's URL to see if the application +responds. + +> **Note:** +> Also available is the `docker restart` command that runs a stop and +> then start on the container. + +## Removing our Web Application Container + +Your colleague has let you know that they've now finished with the container +and won't need it again. So let's remove it using the `docker rm` command. + + $ sudo docker rm nostalgic_morse + Error: Impossible to remove a running container, please stop it first or use -f + 2014/05/24 08:12:56 Error: failed to remove one or more containers + +What's happened? We can't actually remove a running container. This protects +you from accidentally removing a running container you might need. Let's try +this again by stopping the container first. + + $ sudo docker stop nostalgic_morse + nostalgic_morse + $ sudo docker rm nostalgic_morse + nostalgic_morse + +And now our container is stopped and deleted. + +> **Note:** +> Always remember that deleting a container is final! + +# Next steps + +Until now we've only used images that we've downloaded from +[Docker.io](https://index.docker.io) now let's get introduced to +building and sharing our own images. + +Go to [Working with Docker Images](/userguide/dockerimages). + diff --git a/docs/sources/userguide/webapp1.png b/docs/sources/userguide/webapp1.png new file mode 100644 index 0000000000000000000000000000000000000000..5653497f17e1bbf2fbbdc5d0d7bbe7240eb7b040 GIT binary patch literal 51848 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¬2ku(OfH52DJc^(U6M> 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 z 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 Wi+s1pVWNjvADxGW4=V53zxaQX0JYr! literal 0 HcmV?d00001 From 2858180a9a05dad999df0f4d9adc9750b0fb1c66 Mon Sep 17 00:00:00 2001 From: William Henry Date: Sun, 1 Jun 2014 17:16:14 -0600 Subject: [PATCH 316/400] Changed the term rename to alias etc. Docker-DCO-1.1-Signed-off-by: William Henry (github: ipbabble) Changes to be committed: modified: contrib/man/md/docker-tag.1.md --- contrib/man/md/docker-tag.1.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/man/md/docker-tag.1.md b/contrib/man/md/docker-tag.1.md index eca821ebf..0c4276990 100644 --- a/contrib/man/md/docker-tag.1.md +++ b/contrib/man/md/docker-tag.1.md @@ -9,12 +9,12 @@ docker-tag - Tag an image in the repository IMAGE [REGISTRYHOST/][USERNAME/]NAME[:TAG] # DESCRIPTION -This will rename an image in the repository. This refers to the +This will give a new alias to an image in the repository. This refers to the entire image name including the optional TAG after the ':'. # "OPTIONS" **-f**, **--force**=*true*|*false* - When set to true, force the tag name. The default is *false*. + When set to true, force the alias. The default is *false*. **REGISTRYHOST** The hostname of the registry if required. This may also include the port @@ -33,9 +33,9 @@ Note that here TAG is a part of the overall name or "tag". # EXAMPLES -## Tagging an image +## Giving an image a new alias -Here is an example of renaming an image (e.g. 0e5574283393) as "httpd" and +Here is an example of aliasing an image (e.g. 0e5574283393) as "httpd" and tagging it into the "fedora" repository with "version1.0": docker tag 0e5574283393 fedora/httpd:version1.0 From ca231b3de52f718d96c0ff6e7af40c7c0fade623 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 9 May 2014 21:11:24 -0700 Subject: [PATCH 317/400] pkg/testutils: utility functions to facilitate writing Go tests Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- engine/remote_test.go | 17 ++--------------- pkg/testutils/MAINTAINERS | 1 + pkg/testutils/README.md | 2 ++ pkg/testutils/testutils.go | 23 +++++++++++++++++++++++ 4 files changed, 28 insertions(+), 15 deletions(-) create mode 100644 pkg/testutils/MAINTAINERS create mode 100644 pkg/testutils/README.md create mode 100644 pkg/testutils/testutils.go diff --git a/engine/remote_test.go b/engine/remote_test.go index e59ac78cc..1563660c9 100644 --- a/engine/remote_test.go +++ b/engine/remote_test.go @@ -5,6 +5,7 @@ import ( "bytes" "fmt" "github.com/dotcloud/docker/pkg/beam" + "github.com/dotcloud/docker/pkg/testutils" "io" "strings" "testing" @@ -143,21 +144,7 @@ func testRemote(t *testing.T, senderSide, receiverSide func(*Engine)) { receiverSide(receiver.Engine) go receiver.Run() - timeout(t, func() { + testutils.Timeout(t, func() { senderSide(eng) }) } - -func timeout(t *testing.T, f func()) { - onTimeout := time.After(100 * time.Millisecond) - onDone := make(chan bool) - go func() { - f() - close(onDone) - }() - select { - case <-onTimeout: - t.Fatalf("timeout") - case <-onDone: - } -} diff --git a/pkg/testutils/MAINTAINERS b/pkg/testutils/MAINTAINERS new file mode 100644 index 000000000..012d27a2e --- /dev/null +++ b/pkg/testutils/MAINTAINERS @@ -0,0 +1 @@ +Solomon Hykes (@shykes) diff --git a/pkg/testutils/README.md b/pkg/testutils/README.md new file mode 100644 index 000000000..a208a90e6 --- /dev/null +++ b/pkg/testutils/README.md @@ -0,0 +1,2 @@ +`testutils` is a collection of utility functions to facilitate the writing +of tests. It is used in various places by the Docker test suite. diff --git a/pkg/testutils/testutils.go b/pkg/testutils/testutils.go new file mode 100644 index 000000000..4655e5844 --- /dev/null +++ b/pkg/testutils/testutils.go @@ -0,0 +1,23 @@ +package testutils + +import ( + "testing" + "time" +) + +// Timeout calls f and waits for 100ms for it to complete. +// If it doesn't, it causes the tests to fail. +// t must be a valid testing context. +func Timeout(t *testing.T, f func()) { + onTimeout := time.After(100 * time.Millisecond) + onDone := make(chan bool) + go func() { + f() + close(onDone) + }() + select { + case <-onTimeout: + t.Fatalf("timeout") + case <-onDone: + } +} From 0e9c40eb8243fa437bc6c3e93aaff64a10cb856e Mon Sep 17 00:00:00 2001 From: cyphar Date: Mon, 2 Jun 2014 12:13:34 +1000 Subject: [PATCH 318/400] pkg: mflag: flag: make mflag strip quotes in -flag="var" forms This patch improves the mflag package to ensure that things arguments to mflag such as `-flag="var"` or `-flag='var'` have the quotes stripped from the value (to mirror the getopt functionality for similar flags). Docker-DCO-1.1-Signed-off-by: Aleksa Sarai (github: cyphar) --- pkg/mflag/flag.go | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/pkg/mflag/flag.go b/pkg/mflag/flag.go index ed85a4a4c..2cfef331f 100644 --- a/pkg/mflag/flag.go +++ b/pkg/mflag/flag.go @@ -51,6 +51,8 @@ Command line flag syntax: -flag -flag=x + -flag="x" + -flag='x' -flag x // non-boolean flags only One or two minus signs may be used; they are equivalent. The last form is not permitted for boolean flags because the @@ -775,6 +777,37 @@ func (f *FlagSet) usage() { } } +func trimQuotes(str string) string { + type quote struct { + start, end byte + } + + // All valid quote types. + quotes := []quote{ + // Double quotes + { + start: '"', + end: '"', + }, + + // Single quotes + { + start: '\'', + end: '\'', + }, + } + + for _, quote := range quotes { + // Only strip if outermost match. + if str[0] == quote.start && str[len(str)-1] == quote.end { + str = str[1 : len(str)-1] + break + } + } + + return str +} + // parseOne parses one flag. It reports whether a flag was seen. func (f *FlagSet) parseOne() (bool, string, error) { if len(f.args) == 0 { @@ -799,7 +832,7 @@ func (f *FlagSet) parseOne() (bool, string, error) { value := "" for i := 1; i < len(name); i++ { // equals cannot be first if name[i] == '=' { - value = name[i+1:] + value = trimQuotes(name[i+1:]) has_value = true name = name[0:i] break From e4497feaba31dd33bad790f77f783afc0c695020 Mon Sep 17 00:00:00 2001 From: cyphar Date: Mon, 2 Jun 2014 15:02:16 +1000 Subject: [PATCH 319/400] pkg: mflag: flag: added tests for quote-stripped flags This patch adds some tests to ensure that quoted flags are properly handled by the mflag package. Docker-DCO-1.1-Signed-off-by: Aleksa Sarai (github: cyphar) --- pkg/mflag/flag_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pkg/mflag/flag_test.go b/pkg/mflag/flag_test.go index b9e8a0ef3..4c2222e54 100644 --- a/pkg/mflag/flag_test.go +++ b/pkg/mflag/flag_test.go @@ -173,6 +173,12 @@ func testParse(f *FlagSet, t *testing.T) { uintFlag := f.Uint([]string{"uint"}, 0, "uint value") uint64Flag := f.Uint64([]string{"-uint64"}, 0, "uint64 value") stringFlag := f.String([]string{"string"}, "0", "string value") + singleQuoteFlag := f.String([]string{"squote"}, "", "single quoted value") + doubleQuoteFlag := f.String([]string{"dquote"}, "", "double quoted value") + mixedQuoteFlag := f.String([]string{"mquote"}, "", "mixed quoted value") + mixed2QuoteFlag := f.String([]string{"mquote2"}, "", "mixed2 quoted value") + nestedQuoteFlag := f.String([]string{"nquote"}, "", "nested quoted value") + nested2QuoteFlag := f.String([]string{"nquote2"}, "", "nested2 quoted value") float64Flag := f.Float64([]string{"float64"}, 0, "float64 value") durationFlag := f.Duration([]string{"duration"}, 5*time.Second, "time.Duration value") extra := "one-extra-argument" @@ -184,6 +190,12 @@ func testParse(f *FlagSet, t *testing.T) { "-uint", "24", "--uint64", "25", "-string", "hello", + "-squote='single'", + `-dquote="double"`, + `-mquote='mixed"`, + `-mquote2="mixed2'`, + `-nquote="'single nested'"`, + `-nquote2='"double nested"'`, "-float64", "2718e28", "-duration", "2m", extra, @@ -215,6 +227,24 @@ func testParse(f *FlagSet, t *testing.T) { if *stringFlag != "hello" { t.Error("string flag should be `hello`, is ", *stringFlag) } + if *singleQuoteFlag != "single" { + t.Error("single quote string flag should be `single`, is ", *singleQuoteFlag) + } + if *doubleQuoteFlag != "double" { + t.Error("double quote string flag should be `double`, is ", *doubleQuoteFlag) + } + if *mixedQuoteFlag != `'mixed"` { + t.Error("mixed quote string flag should be `'mixed\"`, is ", *mixedQuoteFlag) + } + if *mixed2QuoteFlag != `"mixed2'` { + t.Error("mixed2 quote string flag should be `\"mixed2'`, is ", *mixed2QuoteFlag) + } + if *nestedQuoteFlag != "'single nested'" { + t.Error("nested quote string flag should be `'single nested'`, is ", *nestedQuoteFlag) + } + if *nested2QuoteFlag != `"double nested"` { + t.Error("double quote string flag should be `\"double nested\"`, is ", *nested2QuoteFlag) + } if *float64Flag != 2718e28 { t.Error("float64 flag should be 2718e28, is ", *float64Flag) } From f7b82be0dd1dc0d9b0fa6c9c194dc2c90af3d133 Mon Sep 17 00:00:00 2001 From: Vishnu Kannan Date: Fri, 30 May 2014 00:23:18 +0000 Subject: [PATCH 320/400] Adding "stats" and "spec" option to nsinit binary which will print the stats and spec respectively. Docker-DCO-1.1-Signed-off-by: Vishnu Kannan (github: vishh) --- pkg/libcontainer/cgroups/fs/cpu.go | 4 +++ pkg/libcontainer/nsinit/nsinit/main.go | 44 ++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/pkg/libcontainer/cgroups/fs/cpu.go b/pkg/libcontainer/cgroups/fs/cpu.go index 1c266f4a1..2e9d588f4 100644 --- a/pkg/libcontainer/cgroups/fs/cpu.go +++ b/pkg/libcontainer/cgroups/fs/cpu.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "strconv" + "syscall" "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) @@ -49,6 +50,9 @@ func (s *cpuGroup) GetStats(d *data, stats *cgroups.Stats) error { f, err := os.Open(filepath.Join(path, "cpu.stat")) if err != nil { + if pathErr, ok := err.(*os.PathError); ok && pathErr.Err == syscall.ENOENT { + return nil + } return err } defer f.Close() diff --git a/pkg/libcontainer/nsinit/nsinit/main.go b/pkg/libcontainer/nsinit/nsinit/main.go index b076b5c55..5d968375e 100644 --- a/pkg/libcontainer/nsinit/nsinit/main.go +++ b/pkg/libcontainer/nsinit/nsinit/main.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "fmt" "io/ioutil" "log" "os" @@ -11,6 +12,7 @@ import ( "strconv" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/cgroups/fs" "github.com/dotcloud/docker/pkg/libcontainer/nsinit" ) @@ -67,6 +69,26 @@ func main() { if err := nsinit.Init(container, rootfs, console, syncPipe, os.Args[2:]); err != nil { log.Fatalf("unable to initialize for container: %s", err) } + case "stats": + // returns the stats of the current container. + stats, err := getContainerStats(container) + if err != nil { + log.Printf("Failed to get stats - %v\n", err) + os.Exit(1) + } + fmt.Printf("Stats:\n%v\n", stats) + os.Exit(0) + + case "spec": + // returns the spec of the current container. + spec, err := getContainerSpec(container) + if err != nil { + log.Printf("Failed to get spec - %v\n", err) + os.Exit(1) + } + fmt.Printf("Spec:\n%v\n", spec) + os.Exit(0) + default: log.Fatalf("command not supported for nsinit %s", os.Args[0]) } @@ -125,3 +147,25 @@ func startContainer(container *libcontainer.Container, term nsinit.Terminal, dat return nsinit.Exec(container, term, "", dataPath, args, createCommand, startCallback) } + +// returns the container stats in json format. +func getContainerStats(container *libcontainer.Container) (string, error) { + stats, err := fs.GetStats(container.Cgroups) + if err != nil { + return "", err + } + out, err := json.MarshalIndent(stats, "", "\t") + if err != nil { + return "", err + } + return string(out), nil +} + +// returns the container spec in json format. +func getContainerSpec(container *libcontainer.Container) (string, error) { + spec, err := json.MarshalIndent(container, "", "\t") + if err != nil { + return "", err + } + return string(spec), nil +} From 3723d6341effa7698ccbb05ab708b0c25ee1af02 Mon Sep 17 00:00:00 2001 From: Vishnu Kannan Date: Mon, 2 Jun 2014 06:56:15 +0000 Subject: [PATCH 321/400] Adding percpu usage to cgroup stats reported by libcontainer. Docker-DCO-1.1-Signed-off-by: Vishnu Kannan (github: vishh) --- pkg/libcontainer/cgroups/fs/cpuacct.go | 22 ++++++++++++++++++++++ pkg/libcontainer/cgroups/fs/utils.go | 2 +- pkg/libcontainer/cgroups/stats.go | 3 ++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/pkg/libcontainer/cgroups/fs/cpuacct.go b/pkg/libcontainer/cgroups/fs/cpuacct.go index 36a50b8d4..dc17fdf83 100644 --- a/pkg/libcontainer/cgroups/fs/cpuacct.go +++ b/pkg/libcontainer/cgroups/fs/cpuacct.go @@ -3,6 +3,7 @@ package fs import ( "bufio" "fmt" + "io/ioutil" "os" "path/filepath" "runtime" @@ -76,6 +77,11 @@ func (s *cpuacctGroup) GetStats(d *data, stats *cgroups.Stats) error { stats.CpuStats.CpuUsage.PercentUsage = percentage // Delta usage is in nanoseconds of CPU time so get the usage (in cores) over the sample time. stats.CpuStats.CpuUsage.CurrentUsage = deltaUsage / uint64(usageSampleDuration.Nanoseconds()) + percpuUsage, err := s.getPercpuUsage(path) + if err != nil { + return err + } + stats.CpuStats.CpuUsage.PercpuUsage = percpuUsage return nil } @@ -132,3 +138,19 @@ func (s *cpuacctGroup) getCpuUsage(d *data, path string) (uint64, error) { } return cpuTotal, nil } + +func (s *cpuacctGroup) getPercpuUsage(path string) ([]uint64, error) { + percpuUsage := []uint64{} + data, err := ioutil.ReadFile(filepath.Join(path, "cpuacct.usage_percpu")) + if err != nil { + return percpuUsage, err + } + for _, value := range strings.Fields(string(data)) { + value, err := strconv.ParseUint(value, 10, 64) + if err != nil { + return percpuUsage, fmt.Errorf("Unable to convert param value to uint64: %s", err) + } + percpuUsage = append(percpuUsage, value) + } + return percpuUsage, nil +} diff --git a/pkg/libcontainer/cgroups/fs/utils.go b/pkg/libcontainer/cgroups/fs/utils.go index ff0586345..f65622a80 100644 --- a/pkg/libcontainer/cgroups/fs/utils.go +++ b/pkg/libcontainer/cgroups/fs/utils.go @@ -22,7 +22,7 @@ func getCgroupParamKeyValue(t string) (string, uint64, error) { case 2: value, err := strconv.ParseUint(parts[1], 10, 64) if err != nil { - return "", 0, fmt.Errorf("Unable to convert param value to int: %s", err) + return "", 0, fmt.Errorf("Unable to convert param value to uint64: %s", err) } return parts[0], value, nil default: diff --git a/pkg/libcontainer/cgroups/stats.go b/pkg/libcontainer/cgroups/stats.go index 7918d78fd..eddf0ee0b 100644 --- a/pkg/libcontainer/cgroups/stats.go +++ b/pkg/libcontainer/cgroups/stats.go @@ -13,7 +13,8 @@ type CpuUsage struct { // percentage of available CPUs currently being used. PercentUsage uint64 `json:"percent_usage,omitempty"` // nanoseconds of cpu time consumed over the last 100 ms. - CurrentUsage uint64 `json:"current_usage,omitempty"` + CurrentUsage uint64 `json:"current_usage,omitempty"` + PercpuUsage []uint64 `json:"percpu_usage,omitempty"` } type CpuStats struct { From 1a93d3b054751f6783064e5adb91f196687400b2 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 2 Jun 2014 07:01:17 +0000 Subject: [PATCH 322/400] Add Cristian as maintainer to pkg/testutils Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- pkg/testutils/MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/testutils/MAINTAINERS b/pkg/testutils/MAINTAINERS index 012d27a2e..1c41a6744 100644 --- a/pkg/testutils/MAINTAINERS +++ b/pkg/testutils/MAINTAINERS @@ -1 +1,2 @@ Solomon Hykes (@shykes) +Cristian Staretu (github: unclejack) From 0a06e9bd91faff2c129fd5a7b30b85351c96601f Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 2 Jun 2014 07:05:06 +0000 Subject: [PATCH 323/400] Fix format in maintainers files Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- integration-cli/MAINTAINERS | 2 +- pkg/testutils/MAINTAINERS | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-cli/MAINTAINERS b/integration-cli/MAINTAINERS index 53c8a1185..6dde4769d 100644 --- a/integration-cli/MAINTAINERS +++ b/integration-cli/MAINTAINERS @@ -1 +1 @@ -Cristian Staretu (github: unclejack) +Cristian Staretu (@unclejack) diff --git a/pkg/testutils/MAINTAINERS b/pkg/testutils/MAINTAINERS index 1c41a6744..f2e8c52e5 100644 --- a/pkg/testutils/MAINTAINERS +++ b/pkg/testutils/MAINTAINERS @@ -1,2 +1,2 @@ Solomon Hykes (@shykes) -Cristian Staretu (github: unclejack) +Cristian Staretu (@unclejack) From 4ac216d8d3441b8546798711320cb5679d33984d Mon Sep 17 00:00:00 2001 From: Andre Dublin <81dublin@gmail.com> Date: Mon, 2 Jun 2014 10:36:21 -0400 Subject: [PATCH 324/400] Update networking.md grammar Docker-DCO-1.1-Signed-off-by: Andre Dublin <81dublin@gmail.com> (github: andredublin) rebased by Docker-DCO-1.1-Signed-off-by: SvenDowideit (github: SvenDowideit) --- docs/sources/articles/networking.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/articles/networking.md b/docs/sources/articles/networking.md index aedc5aaa3..a4640f366 100644 --- a/docs/sources/articles/networking.md +++ b/docs/sources/articles/networking.md @@ -615,7 +615,7 @@ Before diving into the following sections on custom network topologies, you might be interested in glancing at a few external tools or examples of the same kinds of configuration. Here are two: - * Jérôme Petazzoni has create a `pipework` shell script to help you + * Jérôme Petazzoni has created a `pipework` shell script to help you connect together containers in arbitrarily complex scenarios: From 1ae37cef9189976c7d3838ba0899e874a0758379 Mon Sep 17 00:00:00 2001 From: Alexandr Morozov Date: Mon, 2 Jun 2014 14:41:47 +0400 Subject: [PATCH 325/400] Remove unused and racy "used" param from streamformatter Also tests written Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- utils/streamformatter.go | 11 +----- utils/streamformatter_test.go | 67 +++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 utils/streamformatter_test.go diff --git a/utils/streamformatter.go b/utils/streamformatter.go index d2758d3ca..d0bc295bb 100644 --- a/utils/streamformatter.go +++ b/utils/streamformatter.go @@ -8,11 +8,10 @@ import ( type StreamFormatter struct { json bool - used bool } func NewStreamFormatter(json bool) *StreamFormatter { - return &StreamFormatter{json, false} + return &StreamFormatter{json} } const streamNewline = "\r\n" @@ -20,7 +19,6 @@ const streamNewline = "\r\n" var streamNewlineBytes = []byte(streamNewline) func (sf *StreamFormatter) FormatStream(str string) []byte { - sf.used = true if sf.json { b, err := json.Marshal(&JSONMessage{Stream: str}) if err != nil { @@ -32,7 +30,6 @@ func (sf *StreamFormatter) FormatStream(str string) []byte { } func (sf *StreamFormatter) FormatStatus(id, format string, a ...interface{}) []byte { - sf.used = true str := fmt.Sprintf(format, a...) if sf.json { b, err := json.Marshal(&JSONMessage{ID: id, Status: str}) @@ -45,7 +42,6 @@ func (sf *StreamFormatter) FormatStatus(id, format string, a ...interface{}) []b } func (sf *StreamFormatter) FormatError(err error) []byte { - sf.used = true if sf.json { jsonError, ok := err.(*JSONError) if !ok { @@ -63,7 +59,6 @@ func (sf *StreamFormatter) FormatProgress(id, action string, progress *JSONProgr if progress == nil { progress = &JSONProgress{} } - sf.used = true if sf.json { b, err := json.Marshal(&JSONMessage{ @@ -84,10 +79,6 @@ func (sf *StreamFormatter) FormatProgress(id, action string, progress *JSONProgr return []byte(action + " " + progress.String() + endl) } -func (sf *StreamFormatter) Used() bool { - return sf.used -} - func (sf *StreamFormatter) Json() bool { return sf.json } diff --git a/utils/streamformatter_test.go b/utils/streamformatter_test.go new file mode 100644 index 000000000..20610f6c0 --- /dev/null +++ b/utils/streamformatter_test.go @@ -0,0 +1,67 @@ +package utils + +import ( + "encoding/json" + "errors" + "reflect" + "testing" +) + +func TestFormatStream(t *testing.T) { + sf := NewStreamFormatter(true) + res := sf.FormatStream("stream") + if string(res) != `{"stream":"stream"}`+"\r\n" { + t.Fatalf("%q", res) + } +} + +func TestFormatStatus(t *testing.T) { + sf := NewStreamFormatter(true) + res := sf.FormatStatus("ID", "%s%d", "a", 1) + if string(res) != `{"status":"a1","id":"ID"}`+"\r\n" { + t.Fatalf("%q", res) + } +} + +func TestFormatSimpleError(t *testing.T) { + sf := NewStreamFormatter(true) + res := sf.FormatError(errors.New("Error for formatter")) + if string(res) != `{"errorDetail":{"message":"Error for formatter"},"error":"Error for formatter"}`+"\r\n" { + t.Fatalf("%q", res) + } +} + +func TestFormatJSONError(t *testing.T) { + sf := NewStreamFormatter(true) + err := &JSONError{Code: 50, Message: "Json error"} + res := sf.FormatError(err) + if string(res) != `{"errorDetail":{"code":50,"message":"Json error"},"error":"Json error"}`+"\r\n" { + t.Fatalf("%q", res) + } +} + +func TestFormatProgress(t *testing.T) { + sf := NewStreamFormatter(true) + progress := &JSONProgress{ + Current: 15, + Total: 30, + Start: 1, + } + res := sf.FormatProgress("id", "action", progress) + msg := &JSONMessage{} + if err := json.Unmarshal(res, msg); err != nil { + t.Fatal(err) + } + if msg.ID != "id" { + t.Fatalf("ID must be 'id', got: %s", msg.ID) + } + if msg.Status != "action" { + t.Fatalf("Status must be 'action', got: %s", msg.Status) + } + if msg.ProgressMessage != progress.String() { + t.Fatalf("ProgressMessage must be %s, got: %s", progress.String(), msg.ProgressMessage) + } + if !reflect.DeepEqual(msg.Progress, progress) { + t.Fatal("Original progress not equals progress from FormatProgress") + } +} From cac24bbfaf59570863a75e34a21b70ce1cc5c51f Mon Sep 17 00:00:00 2001 From: SvenDowideit Date: Tue, 3 Jun 2014 04:30:32 +1000 Subject: [PATCH 326/400] aws json is not javascript style json. trailing comma breaks it :( Docker-DCO-1.1-Signed-off-by: SvenDowideit (github: SvenDowideit) --- docs/s3_website.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/s3_website.json b/docs/s3_website.json index 35c86f462..eab6ae820 100644 --- a/docs/s3_website.json +++ b/docs/s3_website.json @@ -18,7 +18,7 @@ { "Condition": { "KeyPrefixEquals": "use/working_with_links_names/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "userguide/dockerlinks/" } }, { "Condition": { "KeyPrefixEquals": "use/workingwithrepository/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "userguide/dockerrepos/" } }, { "Condition": { "KeyPrefixEquals": "use/port_redirection" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "userguide/dockerlinks/" } }, - { "Condition": { "KeyPrefixEquals": "use/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "examples/" } }, + { "Condition": { "KeyPrefixEquals": "use/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "examples/" } } ] } From 2a1b7f222a5eff596cabb5ebd88a481a83b24402 Mon Sep 17 00:00:00 2001 From: unclejack Date: Wed, 26 Mar 2014 02:33:17 +0200 Subject: [PATCH 327/400] resume pulling the layer on disconnect Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- registry/registry.go | 45 +++++++++++++++-- registry/registry_test.go | 4 +- server/server.go | 2 +- utils/resumablerequestreader.go | 87 +++++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 utils/resumablerequestreader.go diff --git a/registry/registry.go b/registry/registry.go index 3d0a3ed2d..7bcf06601 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -256,12 +256,43 @@ func (r *Registry) GetRemoteImageJSON(imgID, registry string, token []string) ([ return jsonString, imageSize, nil } -func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string) (io.ReadCloser, error) { - req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/layer", nil) +func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string, imgSize int64) (io.ReadCloser, error) { + var ( + retries = 5 + headRes *http.Response + hasResume bool = false + imageURL = fmt.Sprintf("%simages/%s/layer", registry, imgID) + ) + headReq, err := r.reqFactory.NewRequest("HEAD", imageURL, nil) + if err != nil { + return nil, fmt.Errorf("Error while getting from the server: %s\n", err) + } + setTokenAuth(headReq, token) + for i := 1; i <= retries; i++ { + headRes, err = r.client.Do(headReq) + if err != nil && i == retries { + return nil, fmt.Errorf("Eror while making head request: %s\n", err) + } else if err != nil { + time.Sleep(time.Duration(i) * 5 * time.Second) + continue + } + break + } + + if headRes.Header.Get("Accept-Ranges") == "bytes" && imgSize > 0 { + hasResume = true + } + + req, err := r.reqFactory.NewRequest("GET", imageURL, nil) if err != nil { return nil, fmt.Errorf("Error while getting from the server: %s\n", err) } setTokenAuth(req, token) + if hasResume { + utils.Debugf("server supports resume") + return utils.ResumableRequestReader(r.client, req, 5, imgSize), nil + } + utils.Debugf("server doesn't support resume") res, err := r.client.Do(req) if err != nil { return nil, err @@ -725,6 +756,13 @@ type Registry struct { indexEndpoint string } +func AddRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error { + if via != nil && via[0] != nil { + req.Header = via[0].Header + } + return nil +} + func NewRegistry(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, indexEndpoint string) (r *Registry, err error) { httpDial := func(proto string, addr string) (net.Conn, error) { conn, err := net.Dial(proto, addr) @@ -744,7 +782,8 @@ func NewRegistry(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, inde r = &Registry{ authConfig: authConfig, client: &http.Client{ - Transport: httpTransport, + Transport: httpTransport, + CheckRedirect: AddRequiredHeadersToRedirectedRequests, }, indexEndpoint: indexEndpoint, } diff --git a/registry/registry_test.go b/registry/registry_test.go index 0a5be5e54..e207359e6 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -70,7 +70,7 @@ func TestGetRemoteImageJSON(t *testing.T) { func TestGetRemoteImageLayer(t *testing.T) { r := spawnTestRegistry(t) - data, err := r.GetRemoteImageLayer(IMAGE_ID, makeURL("/v1/"), TOKEN) + data, err := r.GetRemoteImageLayer(IMAGE_ID, makeURL("/v1/"), TOKEN, 0) if err != nil { t.Fatal(err) } @@ -78,7 +78,7 @@ func TestGetRemoteImageLayer(t *testing.T) { t.Fatal("Expected non-nil data result") } - _, err = r.GetRemoteImageLayer("abcdef", makeURL("/v1/"), TOKEN) + _, err = r.GetRemoteImageLayer("abcdef", makeURL("/v1/"), TOKEN, 0) if err == nil { t.Fatal("Expected image not found error") } diff --git a/server/server.go b/server/server.go index 85f1125ba..600e6fc5b 100644 --- a/server/server.go +++ b/server/server.go @@ -1137,7 +1137,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin status = fmt.Sprintf("Pulling fs layer [retries: %d]", j) } out.Write(sf.FormatProgress(utils.TruncateID(id), status, nil)) - layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token) + layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token, int64(imgSize)) if uerr, ok := err.(*url.Error); ok { err = uerr.Err } diff --git a/utils/resumablerequestreader.go b/utils/resumablerequestreader.go new file mode 100644 index 000000000..e01f4e6d7 --- /dev/null +++ b/utils/resumablerequestreader.go @@ -0,0 +1,87 @@ +package utils + +import ( + "fmt" + "io" + "net/http" + "time" +) + +type resumableRequestReader struct { + client *http.Client + request *http.Request + lastRange int64 + totalSize int64 + currentResponse *http.Response + failures uint32 + maxFailures uint32 +} + +// ResumableRequestReader makes it possible to resume reading a request's body transparently +// maxfail is the number of times we retry to make requests again (not resumes) +// totalsize is the total length of the body; auto detect if not provided +func ResumableRequestReader(c *http.Client, r *http.Request, maxfail uint32, totalsize int64) io.ReadCloser { + return &resumableRequestReader{client: c, request: r, maxFailures: maxfail, totalSize: totalsize} +} + +func (r *resumableRequestReader) Read(p []byte) (n int, err error) { + if r.client == nil || r.request == nil { + return 0, fmt.Errorf("client and request can't be nil\n") + } + isFreshRequest := false + if r.lastRange != 0 && r.currentResponse == nil { + readRange := fmt.Sprintf("bytes=%d-%d", r.lastRange, r.totalSize) + r.request.Header.Set("Range", readRange) + time.Sleep(5 * time.Second) + } + if r.currentResponse == nil { + r.currentResponse, err = r.client.Do(r.request) + isFreshRequest = true + } + if err != nil && r.failures+1 != r.maxFailures { + r.cleanUpResponse() + r.failures += 1 + time.Sleep(5 * time.Duration(r.failures) * time.Second) + return 0, nil + } else if err != nil { + r.cleanUpResponse() + return 0, err + } + if r.currentResponse.StatusCode == 416 && r.lastRange == r.totalSize && r.currentResponse.ContentLength == 0 { + r.cleanUpResponse() + return 0, io.EOF + } else if r.currentResponse.StatusCode != 206 && r.lastRange != 0 && isFreshRequest { + r.cleanUpResponse() + return 0, fmt.Errorf("the server doesn't support byte ranges") + } + if r.totalSize == 0 { + r.totalSize = r.currentResponse.ContentLength + } else if r.totalSize <= 0 { + r.cleanUpResponse() + return 0, fmt.Errorf("failed to auto detect content length") + } + n, err = r.currentResponse.Body.Read(p) + r.lastRange += int64(n) + if err != nil { + r.cleanUpResponse() + } + if err != nil && err != io.EOF { + Debugf("encountered error during pull and clearing it before resume: %s", err) + err = nil + } + return n, err +} + +func (r *resumableRequestReader) Close() error { + r.cleanUpResponse() + r.client = nil + r.request = nil + return nil +} + +func (r *resumableRequestReader) cleanUpResponse() { + if r.currentResponse != nil { + r.currentResponse.Body.Close() + r.currentResponse = nil + } +} From 30fdac2cb1bd5461972cef6efa3d67270db23491 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 2 Jun 2014 11:54:23 -0700 Subject: [PATCH 328/400] Update cpu stat test for no error Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/cgroups/fs/cpu_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/libcontainer/cgroups/fs/cpu_test.go b/pkg/libcontainer/cgroups/fs/cpu_test.go index ad0674083..c5bfc5040 100644 --- a/pkg/libcontainer/cgroups/fs/cpu_test.go +++ b/pkg/libcontainer/cgroups/fs/cpu_test.go @@ -43,8 +43,8 @@ func TestNoCpuStatFile(t *testing.T) { cpu := &cpuGroup{} err := cpu.GetStats(helper.CgroupData, &actualStats) - if err == nil { - t.Fatal("Expected to fail, but did not.") + if err != nil { + t.Fatal("Expected not to fail, but did") } } From 2e85568816315a6544ce1c379a5b2b714fb16524 Mon Sep 17 00:00:00 2001 From: LK4D4 Date: Sat, 31 May 2014 16:42:49 +0400 Subject: [PATCH 329/400] Aux functions for build testing Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- integration-cli/docker_utils.go | 122 ++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 660d509e7..c1e306f2e 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -2,7 +2,12 @@ package main import ( "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "os" "os/exec" + "path" "strconv" "strings" "testing" @@ -97,3 +102,120 @@ func getContainerCount() (int, error) { } return 0, fmt.Errorf("couldn't find the Container count in the output") } + +type FakeContext struct { + Dir string +} + +func (f *FakeContext) Add(file, content string) error { + filepath := path.Join(f.Dir, file) + dirpath := path.Dir(filepath) + if dirpath != "." { + if err := os.MkdirAll(dirpath, 0755); err != nil { + return err + } + } + return ioutil.WriteFile(filepath, []byte(content), 0644) +} + +func (f *FakeContext) Delete(file string) error { + filepath := path.Join(f.Dir, file) + return os.RemoveAll(filepath) +} + +func (f *FakeContext) Close() error { + return os.RemoveAll(f.Dir) +} + +func fakeContext(dockerfile string, files map[string]string) (*FakeContext, error) { + tmp, err := ioutil.TempDir("", "fake-context") + if err != nil { + return nil, err + } + ctx := &FakeContext{tmp} + for file, content := range files { + if err := ctx.Add(file, content); err != nil { + ctx.Close() + return nil, err + } + } + if err := ctx.Add("Dockerfile", dockerfile); err != nil { + ctx.Close() + return nil, err + } + return ctx, nil +} + +type FakeStorage struct { + *FakeContext + *httptest.Server +} + +func (f *FakeStorage) Close() error { + f.Server.Close() + return f.FakeContext.Close() +} + +func fakeStorage(files map[string]string) (*FakeStorage, error) { + tmp, err := ioutil.TempDir("", "fake-storage") + if err != nil { + return nil, err + } + ctx := &FakeContext{tmp} + for file, content := range files { + if err := ctx.Add(file, content); err != nil { + ctx.Close() + return nil, err + } + } + handler := http.FileServer(http.Dir(ctx.Dir)) + server := httptest.NewServer(handler) + return &FakeStorage{ + FakeContext: ctx, + Server: server, + }, nil +} + +func inspectField(name, field string) (string, error) { + format := fmt.Sprintf("{{.%s}}", field) + inspectCmd := exec.Command(dockerBinary, "inspect", "-f", format, name) + out, exitCode, err := runCommandWithOutput(inspectCmd) + if err != nil || exitCode != 0 { + return "", fmt.Errorf("failed to inspect %s: %s", name, out) + } + return strings.TrimSpace(out), nil +} + +func getIDByName(name string) (string, error) { + return inspectField(name, "Id") +} + +func buildImage(name, dockerfile string, useCache bool) (string, error) { + args := []string{"build", "-t", name} + if !useCache { + args = append(args, "--no-cache") + } + args = append(args, "-") + buildCmd := exec.Command(dockerBinary, args...) + buildCmd.Stdin = strings.NewReader(dockerfile) + out, exitCode, err := runCommandWithOutput(buildCmd) + if err != nil || exitCode != 0 { + return "", fmt.Errorf("failed to build the image: %s", out) + } + return getIDByName(name) +} + +func buildImageFromContext(name string, ctx *FakeContext, useCache bool) (string, error) { + args := []string{"build", "-t", name} + if !useCache { + args = append(args, "--no-cache") + } + args = append(args, ".") + buildCmd := exec.Command(dockerBinary, args...) + buildCmd.Dir = ctx.Dir + out, exitCode, err := runCommandWithOutput(buildCmd) + if err != nil || exitCode != 0 { + return "", fmt.Errorf("failed to build the image: %s", out) + } + return getIDByName(name) +} From bf4d907092a81bf8c6bd8a6a2ce2105aa3c8c11d Mon Sep 17 00:00:00 2001 From: LK4D4 Date: Sat, 31 May 2014 16:43:32 +0400 Subject: [PATCH 330/400] More verbose build tests I've decided that custom asserts only hide the meaning of tests Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- integration-cli/docker_cli_build_test.go | 242 +++++++++++++---------- 1 file changed, 139 insertions(+), 103 deletions(-) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index dbec07fab..b44285957 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -10,25 +10,6 @@ import ( "time" ) -func checkSimpleBuild(t *testing.T, dockerfile, name, inspectFormat, expected string) { - buildCmd := exec.Command(dockerBinary, "build", "-t", name, "-") - buildCmd.Stdin = strings.NewReader(dockerfile) - out, exitCode, err := runCommandWithOutput(buildCmd) - errorOut(err, t, fmt.Sprintf("build failed to complete: %v %v", out, err)) - if err != nil || exitCode != 0 { - t.Fatal("failed to build the image") - } - inspectCmd := exec.Command(dockerBinary, "inspect", "-f", inspectFormat, name) - out, exitCode, err = runCommandWithOutput(inspectCmd) - if err != nil || exitCode != 0 { - t.Fatalf("failed to inspect the image: %s", out) - } - out = strings.TrimSpace(out) - if out != expected { - t.Fatalf("From format %s expected %s, got %s", inspectFormat, expected, out) - } -} - func TestBuildCacheADD(t *testing.T) { buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestBuildCacheADD", "1") buildCmd := exec.Command(dockerBinary, "build", "-t", "testcacheadd1", ".") @@ -606,126 +587,181 @@ func TestBuildRm(t *testing.T) { logDone("build - ensure --rm doesn't leave containers behind and that --rm=true is the default") logDone("build - ensure --rm=false overrides the default") } - -func TestBuildWithVolume(t *testing.T) { - checkSimpleBuild(t, - ` - FROM scratch - VOLUME /test - `, - "testbuildimg", - "{{json .Config.Volumes}}", - `{"/test":{}}`) - - deleteImages("testbuildimg") - logDone("build - with volume") +func TestBuildWithVolumes(t *testing.T) { + name := "testbuildvolumes" + expected := "map[/test1:map[] /test2:map[]]" + defer deleteImages(name) + _, err := buildImage(name, + `FROM scratch + VOLUME /test1 + VOLUME /test2`, + true) + if err != nil { + t.Fatal(err) + } + res, err := inspectField(name, "Config.Volumes") + if err != nil { + t.Fatal(err) + } + if res != expected { + t.Fatalf("Volumes %s, expected %s", res, expected) + } + logDone("build - with volumes") } func TestBuildMaintainer(t *testing.T) { - checkSimpleBuild(t, - ` - FROM scratch - MAINTAINER dockerio - `, - "testbuildimg", - "{{json .Author}}", - `"dockerio"`) - - deleteImages("testbuildimg") + name := "testbuildmaintainer" + expected := "dockerio" + defer deleteImages(name) + _, err := buildImage(name, + `FROM scratch + MAINTAINER dockerio`, + true) + if err != nil { + t.Fatal(err) + } + res, err := inspectField(name, "Author") + if err != nil { + t.Fatal(err) + } + if res != expected { + t.Fatalf("Maintainer %s, expected %s", res, expected) + } logDone("build - maintainer") } func TestBuildUser(t *testing.T) { - checkSimpleBuild(t, - ` - FROM busybox + name := "testbuilduser" + expected := "dockerio" + defer deleteImages(name) + _, err := buildImage(name, + `FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd USER dockerio - RUN [ $(whoami) = 'dockerio' ] - `, - "testbuildimg", - "{{json .Config.User}}", - `"dockerio"`) - - deleteImages("testbuildimg") + RUN [ $(whoami) = 'dockerio' ]`, + true) + if err != nil { + t.Fatal(err) + } + res, err := inspectField(name, "Config.User") + if err != nil { + t.Fatal(err) + } + if res != expected { + t.Fatalf("User %s, expected %s", res, expected) + } logDone("build - user") } func TestBuildRelativeWorkdir(t *testing.T) { - checkSimpleBuild(t, - ` - FROM busybox + name := "testbuildrelativeworkdir" + expected := "/test2/test3" + defer deleteImages(name) + _, err := buildImage(name, + `FROM busybox RUN [ "$PWD" = '/' ] WORKDIR test1 RUN [ "$PWD" = '/test1' ] WORKDIR /test2 RUN [ "$PWD" = '/test2' ] WORKDIR test3 - RUN [ "$PWD" = '/test2/test3' ] - `, - "testbuildimg", - "{{json .Config.WorkingDir}}", - `"/test2/test3"`) - - deleteImages("testbuildimg") + RUN [ "$PWD" = '/test2/test3' ]`, + true) + if err != nil { + t.Fatal(err) + } + res, err := inspectField(name, "Config.WorkingDir") + if err != nil { + t.Fatal(err) + } + if res != expected { + t.Fatalf("Workdir %s, expected %s", res, expected) + } logDone("build - relative workdir") } func TestBuildEnv(t *testing.T) { - checkSimpleBuild(t, - ` - FROM busybox + name := "testbuildenv" + expected := "[HOME=/ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PORT=4243]" + defer deleteImages(name) + _, err := buildImage(name, + `FROM busybox ENV PORT 4243 - RUN [ $(env | grep PORT) = 'PORT=4243' ] - `, - "testbuildimg", - "{{json .Config.Env}}", - `["HOME=/","PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","PORT=4243"]`) - - deleteImages("testbuildimg") + RUN [ $(env | grep PORT) = 'PORT=4243' ]`, + true) + if err != nil { + t.Fatal(err) + } + res, err := inspectField(name, "Config.Env") + if err != nil { + t.Fatal(err) + } + if res != expected { + t.Fatalf("Env %s, expected %s", res, expected) + } logDone("build - env") } func TestBuildCmd(t *testing.T) { - checkSimpleBuild(t, - ` - FROM scratch - CMD ["/bin/echo", "Hello World"] - `, - "testbuildimg", - "{{json .Config.Cmd}}", - `["/bin/echo","Hello World"]`) - - deleteImages("testbuildimg") + name := "testbuildcmd" + expected := "[/bin/echo Hello World]" + defer deleteImages(name) + _, err := buildImage(name, + `FROM scratch + CMD ["/bin/echo", "Hello World"]`, + true) + if err != nil { + t.Fatal(err) + } + res, err := inspectField(name, "Config.Cmd") + if err != nil { + t.Fatal(err) + } + if res != expected { + t.Fatalf("Cmd %s, expected %s", res, expected) + } logDone("build - cmd") } func TestBuildExpose(t *testing.T) { - checkSimpleBuild(t, - ` - FROM scratch - EXPOSE 4243 - `, - - "testbuildimg", - "{{json .Config.ExposedPorts}}", - `{"4243/tcp":{}}`) - - deleteImages("testbuildimg") + name := "testbuildexpose" + expected := "map[4243/tcp:map[]]" + defer deleteImages(name) + _, err := buildImage(name, + `FROM scratch + EXPOSE 4243`, + true) + if err != nil { + t.Fatal(err) + } + res, err := inspectField(name, "Config.ExposedPorts") + if err != nil { + t.Fatal(err) + } + if res != expected { + t.Fatalf("Exposed ports %s, expected %s", res, expected) + } logDone("build - expose") } func TestBuildEntrypoint(t *testing.T) { - checkSimpleBuild(t, - ` - FROM scratch - ENTRYPOINT ["/bin/echo"] - `, - "testbuildimg", - "{{json .Config.Entrypoint}}", - `["/bin/echo"]`) - - deleteImages("testbuildimg") + name := "testbuildentrypoint" + expected := "[/bin/echo]" + defer deleteImages(name) + _, err := buildImage(name, + `FROM scratch + ENTRYPOINT ["/bin/echo"]`, + true) + if err != nil { + t.Fatal(err) + } + res, err := inspectField(name, "Config.Entrypoint") + if err != nil { + t.Fatal(err) + } + if res != expected { + t.Fatalf("Entrypoint %s, expected %s", res, expected) + } logDone("build - entrypoint") } From ae128437cefa93bba082a0231e2cd05d33991c81 Mon Sep 17 00:00:00 2001 From: LK4D4 Date: Sun, 1 Jun 2014 00:49:50 +0400 Subject: [PATCH 331/400] Move build cache tests to integration-cli Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- integration-cli/docker_cli_build_test.go | 337 ++++++++++++++++++++++- integration/buildfile_test.go | 220 --------------- 2 files changed, 335 insertions(+), 222 deletions(-) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index b44285957..0a698308b 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "os" "os/exec" "path/filepath" @@ -765,6 +766,338 @@ func TestBuildEntrypoint(t *testing.T) { logDone("build - entrypoint") } -// TODO: TestCaching +func TestBuildWithCache(t *testing.T) { + name := "testbuildwithcache" + defer deleteImages(name) + id1, err := buildImage(name, + `FROM scratch + MAINTAINER dockerio + EXPOSE 5432 + ENTRYPOINT ["/bin/echo"]`, + true) + if err != nil { + t.Fatal(err) + } + id2, err := buildImage(name, + `FROM scratch + MAINTAINER dockerio + EXPOSE 5432 + ENTRYPOINT ["/bin/echo"]`, + true) + if err != nil { + t.Fatal(err) + } + if id1 != id2 { + t.Fatal("The cache should have been used but hasn't.") + } + logDone("build - with cache") +} -// TODO: TestADDCacheInvalidation +func TestBuildWithoutCache(t *testing.T) { + name := "testbuildwithoutcache" + defer deleteImages(name) + id1, err := buildImage(name, + `FROM scratch + MAINTAINER dockerio + EXPOSE 5432 + ENTRYPOINT ["/bin/echo"]`, + true) + if err != nil { + t.Fatal(err) + } + id2, err := buildImage(name, + `FROM scratch + MAINTAINER dockerio + EXPOSE 5432 + ENTRYPOINT ["/bin/echo"]`, + false) + if err != nil { + t.Fatal(err) + } + if id1 == id2 { + t.Fatal("The cache should have been invalided but hasn't.") + } + logDone("build - without cache") +} + +func TestBuildADDLocalFileWithCache(t *testing.T) { + name := "testbuildaddlocalfilewithcache" + defer deleteImages(name) + dockerfile := ` + FROM busybox + MAINTAINER dockerio + ADD foo /usr/lib/bla/bar + RUN [ "$(cat /usr/lib/bla/bar)" = "hello" ]` + ctx, err := fakeContext(dockerfile, map[string]string{ + "foo": "hello", + }) + defer ctx.Close() + if err != nil { + t.Fatal(err) + } + id1, err := buildImageFromContext(name, ctx, true) + if err != nil { + t.Fatal(err) + } + id2, err := buildImageFromContext(name, ctx, true) + if err != nil { + t.Fatal(err) + } + if id1 != id2 { + t.Fatal("The cache should have been used but hasn't.") + } + logDone("build - add local file with cache") +} + +func TestBuildADDLocalFileWithoutCache(t *testing.T) { + name := "testbuildaddlocalfilewithoutcache" + defer deleteImages(name) + dockerfile := ` + FROM busybox + MAINTAINER dockerio + ADD foo /usr/lib/bla/bar + RUN [ "$(cat /usr/lib/bla/bar)" = "hello" ]` + ctx, err := fakeContext(dockerfile, map[string]string{ + "foo": "hello", + }) + defer ctx.Close() + if err != nil { + t.Fatal(err) + } + id1, err := buildImageFromContext(name, ctx, true) + if err != nil { + t.Fatal(err) + } + id2, err := buildImageFromContext(name, ctx, false) + if err != nil { + t.Fatal(err) + } + if id1 == id2 { + t.Fatal("The cache should have been invalided but hasn't.") + } + logDone("build - add local file without cache") +} + +func TestBuildADDCurrentDirWithCache(t *testing.T) { + name := "testbuildaddcurrentdirwithcache" + defer deleteImages(name) + dockerfile := ` + FROM scratch + MAINTAINER dockerio + ADD . /usr/lib/bla` + ctx, err := fakeContext(dockerfile, map[string]string{ + "foo": "hello", + }) + defer ctx.Close() + if err != nil { + t.Fatal(err) + } + id1, err := buildImageFromContext(name, ctx, true) + if err != nil { + t.Fatal(err) + } + // Check that adding file invalidate cache of "ADD ." + if err := ctx.Add("bar", "hello2"); err != nil { + t.Fatal(err) + } + id2, err := buildImageFromContext(name, ctx, true) + if err != nil { + t.Fatal(err) + } + if id1 == id2 { + t.Fatal("The cache should have been invalided but hasn't.") + } + // Check that changing file invalidate cache of "ADD ." + if err := ctx.Add("foo", "hello1"); err != nil { + t.Fatal(err) + } + id3, err := buildImageFromContext(name, ctx, true) + if err != nil { + t.Fatal(err) + } + if id2 == id3 { + t.Fatal("The cache should have been invalided but hasn't.") + } + // Check that changing file to same content invalidate cache of "ADD ." + time.Sleep(1 * time.Second) // wait second because of mtime precision + if err := ctx.Add("foo", "hello1"); err != nil { + t.Fatal(err) + } + id4, err := buildImageFromContext(name, ctx, true) + if err != nil { + t.Fatal(err) + } + if id3 == id4 { + t.Fatal("The cache should have been invalided but hasn't.") + } + id5, err := buildImageFromContext(name, ctx, true) + if err != nil { + t.Fatal(err) + } + if id4 != id5 { + t.Fatal("The cache should have been used but hasn't.") + } + logDone("build - add current directory with cache") +} + +func TestBuildADDCurrentDirWithoutCache(t *testing.T) { + name := "testbuildaddcurrentdirwithoutcache" + defer deleteImages(name) + dockerfile := ` + FROM scratch + MAINTAINER dockerio + ADD . /usr/lib/bla` + ctx, err := fakeContext(dockerfile, map[string]string{ + "foo": "hello", + }) + defer ctx.Close() + if err != nil { + t.Fatal(err) + } + id1, err := buildImageFromContext(name, ctx, true) + if err != nil { + t.Fatal(err) + } + id2, err := buildImageFromContext(name, ctx, false) + if err != nil { + t.Fatal(err) + } + if id1 == id2 { + t.Fatal("The cache should have been invalided but hasn't.") + } + logDone("build - add current directory without cache") +} + +func TestBuildADDRemoteFileWithCache(t *testing.T) { + name := "testbuildaddremotefilewithcache" + defer deleteImages(name) + server, err := fakeStorage(map[string]string{ + "baz": "hello", + }) + if err != nil { + t.Fatal(err) + } + defer server.Close() + id1, err := buildImage(name, + fmt.Sprintf(`FROM scratch + MAINTAINER dockerio + ADD %s/baz /usr/lib/baz/quux`, server.URL), + true) + if err != nil { + t.Fatal(err) + } + id2, err := buildImage(name, + fmt.Sprintf(`FROM scratch + MAINTAINER dockerio + ADD %s/baz /usr/lib/baz/quux`, server.URL), + true) + if err != nil { + t.Fatal(err) + } + if id1 != id2 { + t.Fatal("The cache should have been used but hasn't.") + } + logDone("build - add remote file with cache") +} + +func TestBuildADDRemoteFileWithoutCache(t *testing.T) { + name := "testbuildaddremotefilewithoutcache" + defer deleteImages(name) + server, err := fakeStorage(map[string]string{ + "baz": "hello", + }) + if err != nil { + t.Fatal(err) + } + defer server.Close() + id1, err := buildImage(name, + fmt.Sprintf(`FROM scratch + MAINTAINER dockerio + ADD %s/baz /usr/lib/baz/quux`, server.URL), + true) + if err != nil { + t.Fatal(err) + } + id2, err := buildImage(name, + fmt.Sprintf(`FROM scratch + MAINTAINER dockerio + ADD %s/baz /usr/lib/baz/quux`, server.URL), + false) + if err != nil { + t.Fatal(err) + } + if id1 == id2 { + t.Fatal("The cache should have been invalided but hasn't.") + } + logDone("build - add remote file without cache") +} + +func TestBuildADDLocalAndRemoteFilesWithCache(t *testing.T) { + name := "testbuildaddlocalandremotefilewithcache" + defer deleteImages(name) + server, err := fakeStorage(map[string]string{ + "baz": "hello", + }) + if err != nil { + t.Fatal(err) + } + defer server.Close() + ctx, err := fakeContext(fmt.Sprintf(`FROM scratch + MAINTAINER dockerio + ADD foo /usr/lib/bla/bar + ADD %s/baz /usr/lib/baz/quux`, server.URL), + map[string]string{ + "foo": "hello world", + }) + if err != nil { + t.Fatal(err) + } + defer ctx.Close() + id1, err := buildImageFromContext(name, ctx, true) + if err != nil { + t.Fatal(err) + } + id2, err := buildImageFromContext(name, ctx, true) + if err != nil { + t.Fatal(err) + } + if id1 != id2 { + t.Fatal("The cache should have been used but hasn't.") + } + logDone("build - add local and remote file with cache") +} + +func TestBuildADDLocalAndRemoteFilesWithoutCache(t *testing.T) { + name := "testbuildaddlocalandremotefilewithoutcache" + defer deleteImages(name) + server, err := fakeStorage(map[string]string{ + "baz": "hello", + }) + if err != nil { + t.Fatal(err) + } + defer server.Close() + ctx, err := fakeContext(fmt.Sprintf(`FROM scratch + MAINTAINER dockerio + ADD foo /usr/lib/bla/bar + ADD %s/baz /usr/lib/baz/quux`, server.URL), + map[string]string{ + "foo": "hello world", + }) + if err != nil { + t.Fatal(err) + } + defer ctx.Close() + id1, err := buildImageFromContext(name, ctx, true) + if err != nil { + t.Fatal(err) + } + id2, err := buildImageFromContext(name, ctx, false) + if err != nil { + t.Fatal(err) + } + if id1 == id2 { + t.Fatal("The cache should have been invalided but hasn't.") + } + logDone("build - add local and remote file without cache") +} diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index c60bb64c7..f91d5c2a6 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -445,226 +445,6 @@ func TestBuildEntrypointRunCleanup(t *testing.T) { } } -func checkCacheBehavior(t *testing.T, template testContextTemplate, expectHit bool) (imageId string) { - eng := NewTestEngine(t) - defer nuke(mkDaemonFromEngine(eng, t)) - - img, err := buildImage(template, t, eng, true) - if err != nil { - t.Fatal(err) - } - - imageId = img.ID - - img, err = buildImage(template, t, eng, expectHit) - if err != nil { - t.Fatal(err) - } - - if hit := imageId == img.ID; hit != expectHit { - t.Fatalf("Cache misbehavior, got hit=%t, expected hit=%t: (first: %s, second %s)", hit, expectHit, imageId, img.ID) - } - return -} - -func checkCacheBehaviorFromEngime(t *testing.T, template testContextTemplate, expectHit bool, eng *engine.Engine) (imageId string) { - img, err := buildImage(template, t, eng, true) - if err != nil { - t.Fatal(err) - } - - imageId = img.ID - - img, err = buildImage(template, t, eng, expectHit) - if err != nil { - t.Fatal(err) - } - - if hit := imageId == img.ID; hit != expectHit { - t.Fatalf("Cache misbehavior, got hit=%t, expected hit=%t: (first: %s, second %s)", hit, expectHit, imageId, img.ID) - } - return -} - -func TestBuildImageWithCache(t *testing.T) { - template := testContextTemplate{` - from {IMAGE} - maintainer dockerio - `, - nil, nil} - checkCacheBehavior(t, template, true) -} - -func TestBuildExposeWithCache(t *testing.T) { - template := testContextTemplate{` - from {IMAGE} - maintainer dockerio - expose 80 - run echo hello - `, - nil, nil} - checkCacheBehavior(t, template, true) -} - -func TestBuildImageWithoutCache(t *testing.T) { - template := testContextTemplate{` - from {IMAGE} - maintainer dockerio - `, - nil, nil} - checkCacheBehavior(t, template, false) -} - -func TestBuildADDLocalFileWithCache(t *testing.T) { - template := testContextTemplate{` - from {IMAGE} - maintainer dockerio - run echo "first" - add foo /usr/lib/bla/bar - run [ "$(cat /usr/lib/bla/bar)" = "hello" ] - run echo "second" - add . /src/ - run [ "$(cat /src/foo)" = "hello" ] - `, - [][2]string{ - {"foo", "hello"}, - }, - nil} - eng := NewTestEngine(t) - defer nuke(mkDaemonFromEngine(eng, t)) - - id1 := checkCacheBehaviorFromEngime(t, template, true, eng) - template.files = append(template.files, [2]string{"bar", "hello2"}) - id2 := checkCacheBehaviorFromEngime(t, template, true, eng) - if id1 == id2 { - t.Fatal("The cache should have been invalided but hasn't.") - } - id3 := checkCacheBehaviorFromEngime(t, template, true, eng) - if id2 != id3 { - t.Fatal("The cache should have been used but hasn't.") - } - template.files[1][1] = "hello3" - id4 := checkCacheBehaviorFromEngime(t, template, true, eng) - if id3 == id4 { - t.Fatal("The cache should have been invalided but hasn't.") - } - template.dockerfile += ` - add ./bar /src2/ - run ls /src2/bar - ` - id5 := checkCacheBehaviorFromEngime(t, template, true, eng) - if id4 == id5 { - t.Fatal("The cache should have been invalided but hasn't.") - } - template.files[1][1] = "hello4" - id6 := checkCacheBehaviorFromEngime(t, template, true, eng) - if id5 == id6 { - t.Fatal("The cache should have been invalided but hasn't.") - } - - template.dockerfile += ` - add bar /src2/bar2 - add /bar /src2/bar3 - run ls /src2/bar2 /src2/bar3 - ` - id7 := checkCacheBehaviorFromEngime(t, template, true, eng) - if id6 == id7 { - t.Fatal("The cache should have been invalided but hasn't.") - } - template.files[1][1] = "hello5" - id8 := checkCacheBehaviorFromEngime(t, template, true, eng) - if id7 == id8 { - t.Fatal("The cache should have been invalided but hasn't.") - } -} - -func TestBuildADDLocalFileWithoutCache(t *testing.T) { - template := testContextTemplate{` - from {IMAGE} - maintainer dockerio - run echo "first" - add foo /usr/lib/bla/bar - run echo "second" - `, - [][2]string{{"foo", "hello"}}, - nil} - checkCacheBehavior(t, template, false) -} - -func TestBuildADDCurrentDirectoryWithCache(t *testing.T) { - template := testContextTemplate{` - from {IMAGE} - maintainer dockerio - add . /usr/lib/bla - `, - nil, nil} - checkCacheBehavior(t, template, true) -} - -func TestBuildADDCurrentDirectoryWithoutCache(t *testing.T) { - template := testContextTemplate{` - from {IMAGE} - maintainer dockerio - add . /usr/lib/bla - `, - nil, nil} - checkCacheBehavior(t, template, false) -} - -func TestBuildADDRemoteFileWithCache(t *testing.T) { - template := testContextTemplate{` - from {IMAGE} - maintainer dockerio - run echo "first" - add http://{SERVERADDR}/baz /usr/lib/baz/quux - run echo "second" - `, - nil, - [][2]string{{"/baz", "world!"}}} - checkCacheBehavior(t, template, true) -} - -func TestBuildADDRemoteFileWithoutCache(t *testing.T) { - template := testContextTemplate{` - from {IMAGE} - maintainer dockerio - run echo "first" - add http://{SERVERADDR}/baz /usr/lib/baz/quux - run echo "second" - `, - nil, - [][2]string{{"/baz", "world!"}}} - checkCacheBehavior(t, template, false) -} - -func TestBuildADDLocalAndRemoteFilesWithCache(t *testing.T) { - template := testContextTemplate{` - from {IMAGE} - maintainer dockerio - run echo "first" - add foo /usr/lib/bla/bar - add http://{SERVERADDR}/baz /usr/lib/baz/quux - run echo "second" - `, - [][2]string{{"foo", "hello"}}, - [][2]string{{"/baz", "world!"}}} - checkCacheBehavior(t, template, true) -} - -func TestBuildADDLocalAndRemoteFilesWithoutCache(t *testing.T) { - template := testContextTemplate{` - from {IMAGE} - maintainer dockerio - run echo "first" - add foo /usr/lib/bla/bar - add http://{SERVERADDR}/baz /usr/lib/baz/quux - run echo "second" - `, - [][2]string{{"foo", "hello"}}, - [][2]string{{"/baz", "world!"}}} - checkCacheBehavior(t, template, false) -} - func TestForbiddenContextPath(t *testing.T) { eng := NewTestEngine(t) defer nuke(mkDaemonFromEngine(eng, t)) From b8932abcd3f3932b6b56aa859b4c0762a95f7179 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 2 Jun 2014 19:54:17 +0000 Subject: [PATCH 332/400] pull only busybox:latest Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- integration-cli/docker_cli_pull_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go index 13b443f3d..fa7d2be44 100644 --- a/integration-cli/docker_cli_pull_test.go +++ b/integration-cli/docker_cli_pull_test.go @@ -8,7 +8,7 @@ import ( // pulling an image from the central registry should work func TestPullImageFromCentralRegistry(t *testing.T) { - pullCmd := exec.Command(dockerBinary, "pull", "busybox") + pullCmd := exec.Command(dockerBinary, "pull", "busybox:latest") out, exitCode, err := runCommandWithOutput(pullCmd) errorOut(err, t, fmt.Sprintf("%s %s", out, err)) From 730d9ba17421e82012664c44aef7f64600b756cb Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 2 Jun 2014 19:54:33 +0000 Subject: [PATCH 333/400] fix busybox image detection Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- integration/runtime_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 9c59d38e0..96df15be6 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -133,10 +133,10 @@ func init() { func setupBaseImage() { eng := newTestEngine(log.New(os.Stderr, "", 0), false, unitTestStoreBase) - job := eng.Job("inspect", unitTestImageName, "image") + job := eng.Job("image_inspect", unitTestImageName) img, _ := job.Stdout.AddEnv() // If the unit test is not found, try to download it. - if err := job.Run(); err != nil || img.Get("id") != unitTestImageID { + if err := job.Run(); err != nil || img.Get("Id") != unitTestImageID { // Retrieve the Image job = eng.Job("pull", unitTestImageName) job.Stdout.Add(utils.NopWriteCloser(os.Stdout)) From 5f3812ec97f9a951bceb482fdcd355b6cbc1b113 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 26 Feb 2014 17:04:11 -0500 Subject: [PATCH 334/400] filters, for images: start with untagged/tagged boolean This is a new feature and flag. (replaces the suggestion of a flag for --untagged images). The concept is to have a syntax to filter. This begins with this filtering for the 'images' subcommand, and at that only filtering for whether images are untagged. example like: docker rmi $(docker images -q --filter 'untagged=true') Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- api/client/commands.go | 54 +- api/server/server.go | 2 + docs/sources/reference/commandline/cli.rst | 1499 ++++++++++++++++++++ server/server.go | 43 +- utils/filters/filter.go | 53 + utils/filters/parse.go | 47 + 6 files changed, 1678 insertions(+), 20 deletions(-) create mode 100644 docs/sources/reference/commandline/cli.rst create mode 100644 utils/filters/filter.go create mode 100644 utils/filters/parse.go diff --git a/api/client/commands.go b/api/client/commands.go index 608059584..836fe3720 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -26,12 +26,14 @@ import ( "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/nat" + "github.com/dotcloud/docker/opts" "github.com/dotcloud/docker/pkg/signal" "github.com/dotcloud/docker/pkg/term" "github.com/dotcloud/docker/pkg/units" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/utils/filters" ) func (cli *DockerCli) CmdHelp(args ...string) error { @@ -1145,6 +1147,9 @@ func (cli *DockerCli) CmdImages(args ...string) error { flViz := cmd.Bool([]string{"#v", "#viz", "#-viz"}, false, "Output graph in graphviz format") flTree := cmd.Bool([]string{"#t", "#tree", "#-tree"}, false, "Output graph in tree format") + var flFilter opts.ListOpts + cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'tagged=false')") + if err := cmd.Parse(args); err != nil { return nil } @@ -1153,11 +1158,41 @@ func (cli *DockerCli) CmdImages(args ...string) error { return nil } - filter := cmd.Arg(0) + // Consolidate all filter flags, and sanity check them early. + // They'll get process in the daemon/server. + imageFilters := map[string]string{} + for _, f := range flFilter.GetAll() { + var err error + imageFilters, err = filters.ParseFlag(f, imageFilters) + if err != nil { + return err + } + } + /* + var ( + untagged bool + ) + for k,v := range imageFilters { + } + */ + + // seeing -all untagged images is redundant, and no point in seeing a visualization of that + /* + if *flUntagged && (*all || *flViz || *flTree) { + fmt.Fprintln(cli.err, "Notice: --untagged is not to be used with --all, --tree or --viz") + *flUntagged = false + } + */ + + matchName := cmd.Arg(0) // FIXME: --viz and --tree are deprecated. Remove them in a future version. if *flViz || *flTree { - body, _, err := readBody(cli.call("GET", "/images/json?all=1", nil, false)) + v := url.Values{ + "all": []string{"1"}, + "filters": []string{filters.ToParam(imageFilters)}, + } + body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false)) if err != nil { return err } @@ -1187,13 +1222,13 @@ func (cli *DockerCli) CmdImages(args ...string) error { } } - if filter != "" { - if filter == image.Get("Id") || filter == utils.TruncateID(image.Get("Id")) { + if matchName != "" { + if matchName == image.Get("Id") || matchName == utils.TruncateID(image.Get("Id")) { startImage = image } for _, repotag := range image.GetList("RepoTags") { - if repotag == filter { + if repotag == matchName { startImage = image } } @@ -1211,16 +1246,19 @@ func (cli *DockerCli) CmdImages(args ...string) error { root := engine.NewTable("Created", 1) root.Add(startImage) cli.WalkTree(*noTrunc, root, byParent, "", printNode) - } else if filter == "" { + } else if matchName == "" { cli.WalkTree(*noTrunc, roots, byParent, "", printNode) } if *flViz { fmt.Fprintf(cli.out, " base [style=invisible]\n}\n") } } else { - v := url.Values{} + v := url.Values{ + "filters": []string{filters.ToParam(imageFilters)}, + } if cmd.NArg() == 1 { - v.Set("filter", filter) + // FIXME rename this parameter, to not be confused with the filters flag + v.Set("filter", matchName) } if *all { v.Set("all", "1") diff --git a/api/server/server.go b/api/server/server.go index 25b377ffd..0bed58226 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -188,6 +188,8 @@ func getImagesJSON(eng *engine.Engine, version version.Version, w http.ResponseW job = eng.Job("images") ) + job.Setenv("filters", r.Form.Get("filters")) + // FIXME rename this parameter, to not be confused with the filters flag job.Setenv("filter", r.Form.Get("filter")) job.Setenv("all", r.Form.Get("all")) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst new file mode 100644 index 000000000..6af6a85c8 --- /dev/null +++ b/docs/sources/reference/commandline/cli.rst @@ -0,0 +1,1499 @@ +:title: Command Line Interface +:description: Docker's CLI command description and usage +:keywords: Docker, Docker documentation, CLI, command line + +.. _cli: + +Command Line Help +----------------- + +To list available commands, either run ``docker`` with no parameters or execute +``docker help``:: + + $ sudo docker + Usage: docker [OPTIONS] COMMAND [arg...] + -H=[unix:///var/run/docker.sock]: tcp://[host]:port to bind/connect to or unix://[/path/to/socket] to use. When host=[127.0.0.1] is omitted for tcp or path=[/var/run/docker.sock] is omitted for unix sockets, default values are used. + + A self-sufficient runtime for linux containers. + + ... + +.. _cli_options: + +Options +------- + +Single character commandline options can be combined, so rather than typing +``docker run -t -i --name test busybox sh``, you can write +``docker run -ti --name test busybox sh``. + +Boolean +~~~~~~~ + +Boolean options look like ``-d=false``. The value you see is the +default value which gets set if you do **not** use the boolean +flag. If you do call ``run -d``, that sets the opposite boolean value, +so in this case, ``true``, and so ``docker run -d`` **will** run in +"detached" mode, in the background. Other boolean options are similar +-- specifying them will set the value to the opposite of the default +value. + +Multi +~~~~~ + +Options like ``-a=[]`` indicate they can be specified multiple times:: + + docker run -a stdin -a stdout -a stderr -i -t ubuntu /bin/bash + +Sometimes this can use a more complex value string, as for ``-v``:: + + docker run -v /host:/container example/mysql + +Strings and Integers +~~~~~~~~~~~~~~~~~~~~ + +Options like ``--name=""`` expect a string, and they can only be +specified once. Options like ``-c=0`` expect an integer, and they can +only be specified once. + +---- + +Commands +-------- + +.. _cli_daemon: + +``daemon`` +---------- + +:: + + Usage of docker: + -D, --debug=false: Enable debug mode + -H, --host=[]: Multiple tcp://host:port or unix://path/to/socket to bind in daemon mode, single connection otherwise. systemd socket activation can be used with fd://[socketfd]. + -G, --group="docker": Group to assign the unix socket specified by -H when running in daemon mode; use '' (the empty string) to disable setting of a group + --api-enable-cors=false: Enable CORS headers in the remote API + -b, --bridge="": Attach containers to a pre-existing network bridge; use 'none' to disable container networking + --bip="": Use this CIDR notation address for the network bridge's IP, not compatible with -b + -d, --daemon=false: Enable daemon mode + --dns=[]: Force docker to use specific DNS servers + --dns-search=[]: Force Docker to use specific DNS search domains + -g, --graph="/var/lib/docker": Path to use as the root of the docker runtime + --icc=true: Enable inter-container communication + --ip="0.0.0.0": Default IP address to use when binding container ports + --ip-forward=true: Enable net.ipv4.ip_forward + --iptables=true: Enable Docker's addition of iptables rules + -p, --pidfile="/var/run/docker.pid": Path to use for daemon PID file + -r, --restart=true: Restart previously running containers + -s, --storage-driver="": Force the docker runtime to use a specific storage driver + -e, --exec-driver="native": Force the docker runtime to use a specific exec driver + -v, --version=false: Print version information and quit + --tls=false: Use TLS; implied by tls-verify flags + --tlscacert="~/.docker/ca.pem": Trust only remotes providing a certificate signed by the CA given here + --tlscert="~/.docker/cert.pem": Path to TLS certificate file + --tlskey="~/.docker/key.pem": Path to TLS key file + --tlsverify=false: Use TLS and verify the remote (daemon: verify client, client: verify daemon) + --mtu=0: Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if no default route is available + +The Docker daemon is the persistent process that manages containers. Docker uses the same binary for both the +daemon and client. To run the daemon you provide the ``-d`` flag. + +To force Docker to use devicemapper as the storage driver, use ``docker -d -s devicemapper``. + +To set the DNS server for all Docker containers, use ``docker -d --dns 8.8.8.8``. + +To set the DNS search domain for all Docker containers, use ``docker -d --dns-search example.com``. + +To run the daemon with debug output, use ``docker -d -D``. + +To use lxc as the execution driver, use ``docker -d -e lxc``. + +The docker client will also honor the ``DOCKER_HOST`` environment variable to set +the ``-H`` flag for the client. + +:: + + docker -H tcp://0.0.0.0:4243 ps + # or + export DOCKER_HOST="tcp://0.0.0.0:4243" + docker ps + # both are equal + +To run the daemon with `systemd socket activation `_, use ``docker -d -H fd://``. +Using ``fd://`` will work perfectly for most setups but you can also specify individual sockets too ``docker -d -H fd://3``. +If the specified socket activated files aren't found then docker will exit. +You can find examples of using systemd socket activation with docker and systemd in the `docker source tree `_. + +Docker supports softlinks for the Docker data directory (``/var/lib/docker``) and for ``/tmp``. +TMPDIR and the data directory can be set like this: + +:: + + TMPDIR=/mnt/disk2/tmp /usr/local/bin/docker -d -D -g /var/lib/docker -H unix:// > /var/lib/boot2docker/docker.log 2>&1 + # or + export TMPDIR=/mnt/disk2/tmp + /usr/local/bin/docker -d -D -g /var/lib/docker -H unix:// > /var/lib/boot2docker/docker.log 2>&1 + +.. _cli_attach: + +``attach`` +---------- + +:: + + Usage: docker attach CONTAINER + + Attach to a running container. + + --no-stdin=false: Do not attach stdin + --sig-proxy=true: Proxify all received signal to the process (even in non-tty mode) + +You can detach from the container again (and leave it running) with +``CTRL-c`` (for a quiet exit) or ``CTRL-\`` to get a stacktrace of +the Docker client when it quits. When you detach from the container's +process the exit code will be returned to the client. + +To stop a container, use ``docker stop``. + +To kill the container, use ``docker kill``. + +.. _cli_attach_examples: + +Examples: +~~~~~~~~~ + +.. code-block:: bash + + $ ID=$(sudo docker run -d ubuntu /usr/bin/top -b) + $ sudo docker attach $ID + top - 02:05:52 up 3:05, 0 users, load average: 0.01, 0.02, 0.05 + Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie + Cpu(s): 0.1%us, 0.2%sy, 0.0%ni, 99.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st + Mem: 373572k total, 355560k used, 18012k free, 27872k buffers + Swap: 786428k total, 0k used, 786428k free, 221740k cached + + PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND + 1 root 20 0 17200 1116 912 R 0 0.3 0:00.03 top + + top - 02:05:55 up 3:05, 0 users, load average: 0.01, 0.02, 0.05 + Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie + Cpu(s): 0.0%us, 0.2%sy, 0.0%ni, 99.8%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st + Mem: 373572k total, 355244k used, 18328k free, 27872k buffers + Swap: 786428k total, 0k used, 786428k free, 221776k cached + + PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND + 1 root 20 0 17208 1144 932 R 0 0.3 0:00.03 top + + + top - 02:05:58 up 3:06, 0 users, load average: 0.01, 0.02, 0.05 + Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie + Cpu(s): 0.2%us, 0.3%sy, 0.0%ni, 99.5%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st + Mem: 373572k total, 355780k used, 17792k free, 27880k buffers + Swap: 786428k total, 0k used, 786428k free, 221776k cached + + PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND + 1 root 20 0 17208 1144 932 R 0 0.3 0:00.03 top + ^C$ + $ sudo docker stop $ID + +.. _cli_build: + +``build`` +--------- + +:: + + Usage: docker build [OPTIONS] PATH | URL | - + Build a new container image from the source code at PATH + -t, --tag="": Repository name (and optionally a tag) to be applied + to the resulting image in case of success. + -q, --quiet=false: Suppress the verbose output generated by the containers. + --no-cache: Do not use the cache when building the image. + --rm=true: Remove intermediate containers after a successful build + +The files at ``PATH`` or ``URL`` are called the "context" of the build. +The build process may refer to any of the files in the context, for example when +using an :ref:`ADD ` instruction. +When a single ``Dockerfile`` is given as ``URL``, then no context is set. + +When a Git repository is set as ``URL``, then the repository is used as the context. +The Git repository is cloned with its submodules (`git clone --recursive`). +A fresh git clone occurs in a temporary directory on your local host, and then this +is sent to the Docker daemon as the context. +This way, your local user credentials and vpn's etc can be used to access private repositories + +.. _cli_build_examples: + +.. seealso:: :ref:`dockerbuilder`. + +Examples: +~~~~~~~~~ + +.. code-block:: bash + + $ sudo docker build . + Uploading context 10240 bytes + Step 1 : FROM busybox + Pulling repository busybox + ---> e9aa60c60128MB/2.284 MB (100%) endpoint: https://cdn-registry-1.docker.io/v1/ + Step 2 : RUN ls -lh / + ---> Running in 9c9e81692ae9 + total 24 + drwxr-xr-x 2 root root 4.0K Mar 12 2013 bin + drwxr-xr-x 5 root root 4.0K Oct 19 00:19 dev + drwxr-xr-x 2 root root 4.0K Oct 19 00:19 etc + drwxr-xr-x 2 root root 4.0K Nov 15 23:34 lib + lrwxrwxrwx 1 root root 3 Mar 12 2013 lib64 -> lib + dr-xr-xr-x 116 root root 0 Nov 15 23:34 proc + lrwxrwxrwx 1 root root 3 Mar 12 2013 sbin -> bin + dr-xr-xr-x 13 root root 0 Nov 15 23:34 sys + drwxr-xr-x 2 root root 4.0K Mar 12 2013 tmp + drwxr-xr-x 2 root root 4.0K Nov 15 23:34 usr + ---> b35f4035db3f + Step 3 : CMD echo Hello World + ---> Running in 02071fceb21b + ---> f52f38b7823e + Successfully built f52f38b7823e + Removing intermediate container 9c9e81692ae9 + Removing intermediate container 02071fceb21b + + +This example specifies that the ``PATH`` is ``.``, and so all the files in +the local directory get tar'd and sent to the Docker daemon. The ``PATH`` +specifies where to find the files for the "context" of the build on +the Docker daemon. Remember that the daemon could be running on a +remote machine and that no parsing of the ``Dockerfile`` happens at the +client side (where you're running ``docker build``). That means that +*all* the files at ``PATH`` get sent, not just the ones listed to +:ref:`ADD ` in the ``Dockerfile``. + +The transfer of context from the local machine to the Docker daemon is +what the ``docker`` client means when you see the "Uploading context" +message. + +If you wish to keep the intermediate containers after the build is complete, +you must use ``--rm=false``. This does not affect the build cache. + + +.. code-block:: bash + + $ sudo docker build -t vieux/apache:2.0 . + +This will build like the previous example, but it will then tag the +resulting image. The repository name will be ``vieux/apache`` and the +tag will be ``2.0`` + + +.. code-block:: bash + + $ sudo docker build - < Dockerfile + +This will read a ``Dockerfile`` from *stdin* without context. Due to +the lack of a context, no contents of any local directory will be sent +to the ``docker`` daemon. Since there is no context, a ``Dockerfile`` +``ADD`` only works if it refers to a remote URL. + +.. code-block:: bash + + $ sudo docker build github.com/creack/docker-firefox + +This will clone the GitHub repository and use the cloned repository as +context. The ``Dockerfile`` at the root of the repository is used as +``Dockerfile``. Note that you can specify an arbitrary Git repository +by using the ``git://`` schema. + + +.. _cli_commit: + +``commit`` +---------- + +:: + + Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]] + + Create a new image from a container's changes + + -m, --message="": Commit message + -a, --author="": Author (eg. "John Hannibal Smith " + --run="": Configuration changes to be applied when the image is launched with `docker run`. + (ex: --run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}') + +.. _cli_commit_examples: + +Commit an existing container +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: bash + + $ sudo docker ps + ID IMAGE COMMAND CREATED STATUS PORTS + c3f279d17e0a ubuntu:12.04 /bin/bash 7 days ago Up 25 hours + 197387f1b436 ubuntu:12.04 /bin/bash 7 days ago Up 25 hours + $ docker commit c3f279d17e0a SvenDowideit/testimage:version3 + f5283438590d + $ docker images | head + REPOSITORY TAG ID CREATED VIRTUAL SIZE + SvenDowideit/testimage version3 f5283438590d 16 seconds ago 335.7 MB + +Change the command that a container runs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes you have an application container running just a service and you need +to make a quick change and then change it back. + +In this example, we run a container with ``ls`` and then change the image to +run ``ls /etc``. + +.. code-block:: bash + + $ docker run -t --name test ubuntu ls + bin boot dev etc home lib lib64 media mnt opt proc root run sbin selinux srv sys tmp usr var + $ docker commit --run='{"Cmd": ["ls","/etc"]}' test test2 + 933d16de9e70005304c1717b5c6f2f39d6fd50752834c6f34a155c70790011eb + $ docker run -t test2 + adduser.conf gshadow login.defs rc0.d + alternatives gshadow- logrotate.d rc1.d + apt host.conf lsb-base rc2.d + ... + +Merged configs example +...................... + +Say you have a Dockerfile like so: + +.. code-block:: bash + + ENV MYVAR foobar + RUN apt-get install openssh + EXPOSE 22 + CMD ["/usr/sbin/sshd -D"] + ... + +If you run that, make some changes, and then commit, Docker will merge the environment variable and exposed port configuration settings with any that you specify in the --run= option. This is a change from Docker 0.8.0 and prior where no attempt was made to preserve any existing configuration on commit. + +.. code-block:: bash + + $ docker build -t me/foo . + $ docker run -t -i me/foo /bin/bash + foo-container$ [make changes in the container] + foo-container$ exit + $ docker commit --run='{"Cmd": ["ls"]}' [container-id] me/bar + ... + +The me/bar image will now have port 22 exposed, MYVAR env var set to 'foobar', and its default command will be ["ls"]. + +Note that this is currently a shallow merge. So, for example, if you had specified a new port spec in the --run= config above, that would have clobbered the 'EXPOSE 22' setting from the parent container. + +Full --run example +.................. + +The ``--run`` JSON hash changes the ``Config`` section when running ``docker inspect CONTAINERID`` +or ``config`` when running ``docker inspect IMAGEID``. Existing configuration key-values that are +not overridden in the JSON hash will be merged in. + +(Multiline is okay within a single quote ``'``) + +.. code-block:: bash + + $ sudo docker commit --run=' + { + "Entrypoint" : null, + "Privileged" : false, + "User" : "", + "VolumesFrom" : "", + "Cmd" : ["cat", "-e", "/etc/resolv.conf"], + "Dns" : ["8.8.8.8", "8.8.4.4"], + "DnsSearch" : ["example.com"], + "MemorySwap" : 0, + "AttachStdin" : false, + "AttachStderr" : false, + "CpuShares" : 0, + "OpenStdin" : false, + "Volumes" : null, + "Hostname" : "122612f45831", + "PortSpecs" : ["22", "80", "443"], + "Image" : "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "Tty" : false, + "Env" : [ + "HOME=/", + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "StdinOnce" : false, + "Domainname" : "", + "WorkingDir" : "/", + "NetworkDisabled" : false, + "Memory" : 0, + "AttachStdout" : false + }' $CONTAINER_ID + +.. _cli_cp: + +``cp`` +------ + +:: + + Usage: docker cp CONTAINER:PATH HOSTPATH + + Copy files/folders from the containers filesystem to the host + path. Paths are relative to the root of the filesystem. + +.. code-block:: bash + + $ sudo docker cp 7bb0e258aefe:/etc/debian_version . + $ sudo docker cp blue_frog:/etc/hosts . + +.. _cli_diff: + +``diff`` +-------- + +:: + + Usage: docker diff CONTAINER + + List the changed files and directories in a container's filesystem + +There are 3 events that are listed in the 'diff': + +1. ```A``` - Add +2. ```D``` - Delete +3. ```C``` - Change + +For example: + +.. code-block:: bash + + $ sudo docker diff 7bb0e258aefe + + C /dev + A /dev/kmsg + C /etc + A /etc/mtab + A /go + A /go/src + A /go/src/github.com + A /go/src/github.com/dotcloud + A /go/src/github.com/dotcloud/docker + A /go/src/github.com/dotcloud/docker/.git + .... + +.. _cli_events: + +``events`` +---------- + +:: + + Usage: docker events + + Get real time events from the server + + --since="": Show previously created events and then stream. + (either seconds since epoch, or date string as below) + +.. _cli_events_example: + +Examples +~~~~~~~~ + +You'll need two shells for this example. + +Shell 1: Listening for events +............................. + +.. code-block:: bash + + $ sudo docker events + +Shell 2: Start and Stop a Container +................................... + +.. code-block:: bash + + $ sudo docker start 4386fb97867d + $ sudo docker stop 4386fb97867d + +Shell 1: (Again .. now showing events) +...................................... + +.. code-block:: bash + + [2013-09-03 15:49:26 +0200 CEST] 4386fb97867d: (from 12de384bfb10) start + [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die + [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop + +Show events in the past from a specified time +............................................. + +.. code-block:: bash + + $ sudo docker events --since 1378216169 + [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die + [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop + + $ sudo docker events --since '2013-09-03' + [2013-09-03 15:49:26 +0200 CEST] 4386fb97867d: (from 12de384bfb10) start + [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die + [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop + + $ sudo docker events --since '2013-09-03 15:49:29 +0200 CEST' + [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die + [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop + +.. _cli_export: + +``export`` +---------- + +:: + + Usage: docker export CONTAINER + + Export the contents of a filesystem as a tar archive to STDOUT + +For example: + +.. code-block:: bash + + $ sudo docker export red_panda > latest.tar + +.. _cli_history: + +``history`` +----------- + +:: + + Usage: docker history [OPTIONS] IMAGE + + Show the history of an image + + --no-trunc=false: Don't truncate output + -q, --quiet=false: Only show numeric IDs + +To see how the ``docker:latest`` image was built: + +.. code-block:: bash + + $ docker history docker + IMAGE CREATED CREATED BY SIZE + 3e23a5875458790b7a806f95f7ec0d0b2a5c1659bfc899c89f939f6d5b8f7094 8 days ago /bin/sh -c #(nop) ENV LC_ALL=C.UTF-8 0 B + 8578938dd17054dce7993d21de79e96a037400e8d28e15e7290fea4f65128a36 8 days ago /bin/sh -c dpkg-reconfigure locales && locale-gen C.UTF-8 && /usr/sbin/update-locale LANG=C.UTF-8 1.245 MB + be51b77efb42f67a5e96437b3e102f81e0a1399038f77bf28cea0ed23a65cf60 8 days ago /bin/sh -c apt-get update && apt-get install -y git libxml2-dev python build-essential make gcc python-dev locales python-pip 338.3 MB + 4b137612be55ca69776c7f30c2d2dd0aa2e7d72059820abf3e25b629f887a084 6 weeks ago /bin/sh -c #(nop) ADD jessie.tar.xz in / 121 MB + 750d58736b4b6cc0f9a9abe8f258cef269e3e9dceced1146503522be9f985ada 6 weeks ago /bin/sh -c #(nop) MAINTAINER Tianon Gravi - mkimage-debootstrap.sh -t jessie.tar.xz jessie http://http.debian.net/debian 0 B + 511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158 9 months ago 0 B + +.. _cli_images: + +``images`` +---------- + +:: + + Usage: docker images [OPTIONS] [NAME] + + List images + + -a, --all=false: Show all images (by default filter out the intermediate images used to build) + --no-trunc=false: Don't truncate output + -q, --quiet=false: Only show numeric IDs + -t, --tree=false: Output graph in tree format + -u, --untagged show only untagged images + -v, --viz=false: Output graph in graphviz format + +Listing the most recently created images +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: bash + + $ sudo docker images | head + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + 77af4d6b9913 19 hours ago 1.089 GB + committest latest b6fa739cedf5 19 hours ago 1.089 GB + 78a85c484f71 19 hours ago 1.089 GB + docker latest 30557a29d5ab 20 hours ago 1.089 GB + 0124422dd9f9 20 hours ago 1.089 GB + 18ad6fad3402 22 hours ago 1.082 GB + f9f1e26352f0 23 hours ago 1.089 GB + tryout latest 2629d1fa0b81 23 hours ago 131.5 MB + 5ed6274db6ce 24 hours ago 1.089 GB + +Listing the full length image IDs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: bash + + $ sudo docker images --no-trunc | head + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + 77af4d6b9913e693e8d0b4b294fa62ade6054e6b2f1ffb617ac955dd63fb0182 19 hours ago 1.089 GB + committest latest b6fa739cedf5ea12a620a439402b6004d057da800f91c7524b5086a5e4749c9f 19 hours ago 1.089 GB + 78a85c484f71509adeaace20e72e941f6bdd2b25b4c75da8693efd9f61a37921 19 hours ago 1.089 GB + docker latest 30557a29d5abc51e5f1d5b472e79b7e296f595abcf19fe6b9199dbbc809c6ff4 20 hours ago 1.089 GB + 0124422dd9f9cf7ef15c0617cda3931ee68346455441d66ab8bdc5b05e9fdce5 20 hours ago 1.089 GB + 18ad6fad340262ac2a636efd98a6d1f0ea775ae3d45240d3418466495a19a81b 22 hours ago 1.082 GB + f9f1e26352f0a3ba6a0ff68167559f64f3e21ff7ada60366e2d44a04befd1d3a 23 hours ago 1.089 GB + tryout latest 2629d1fa0b81b222fca63371ca16cbf6a0772d07759ff80e8d1369b926940074 23 hours ago 131.5 MB + 5ed6274db6ceb2397844896966ea239290555e74ef307030ebb01ff91b1914df 24 hours ago 1.089 GB + +Displaying images visually +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: bash + + $ sudo docker images --viz | dot -Tpng -o docker.png + +.. image:: docker_images.gif + :alt: Example inheritance graph of Docker images. + + +Displaying image hierarchy +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: bash + + $ sudo docker images --tree + + ├─8dbd9e392a96 Size: 131.5 MB (virtual 131.5 MB) Tags: ubuntu:12.04,ubuntu:latest,ubuntu:precise + └─27cf78414709 Size: 180.1 MB (virtual 180.1 MB) + └─b750fe79269d Size: 24.65 kB (virtual 180.1 MB) Tags: ubuntu:12.10,ubuntu:quantal + ├─f98de3b610d5 Size: 12.29 kB (virtual 180.1 MB) + │ └─7da80deb7dbf Size: 16.38 kB (virtual 180.1 MB) + │ └─65ed2fee0a34 Size: 20.66 kB (virtual 180.2 MB) + │ └─a2b9ea53dddc Size: 819.7 MB (virtual 999.8 MB) + │ └─a29b932eaba8 Size: 28.67 kB (virtual 999.9 MB) + │ └─e270a44f124d Size: 12.29 kB (virtual 999.9 MB) Tags: progrium/buildstep:latest + └─17e74ac162d8 Size: 53.93 kB (virtual 180.2 MB) + └─339a3f56b760 Size: 24.65 kB (virtual 180.2 MB) + └─904fcc40e34d Size: 96.7 MB (virtual 276.9 MB) + └─b1b0235328dd Size: 363.3 MB (virtual 640.2 MB) + └─7cb05d1acb3b Size: 20.48 kB (virtual 640.2 MB) + └─47bf6f34832d Size: 20.48 kB (virtual 640.2 MB) + └─f165104e82ed Size: 12.29 kB (virtual 640.2 MB) + └─d9cf85a47b7e Size: 1.911 MB (virtual 642.2 MB) + └─3ee562df86ca Size: 17.07 kB (virtual 642.2 MB) + └─b05fc2d00e4a Size: 24.96 kB (virtual 642.2 MB) + └─c96a99614930 Size: 12.29 kB (virtual 642.2 MB) + └─a6a357a48c49 Size: 12.29 kB (virtual 642.2 MB) Tags: ndj/mongodb:latest + +Displaying untagged (orphan) images +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: bash + + $ sudo docker images --untagged + + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + 8abc22fbb042 4 weeks ago 0 B + 48e5f45168b9 4 weeks ago 2.489 MB + bf747efa0e2f 4 weeks ago 0 B + 980fe10e5736 12 weeks ago 101.4 MB + dea752e4e117 12 weeks ago 101.4 MB + 511136ea3c5a 8 months ago 0 B + +This will display untagged images, that are the leaves of the images tree (not +intermediary layers). These images occur when a new build of an image takes the +repo:tag away from the IMAGE ID, leaving it untagged. A warning will be issued +if trying to remove an image when a container is presently using it. +By having this flag it allows for batch cleanup. + + +.. code-block:: bash + + $ sudo docker images --untagged -q + + 8abc22fbb042 + 48e5f45168b9 + bf747efa0e2f + 980fe10e5736 + dea752e4e117 + 511136ea3c5a + + +Ready for use by `docker rmi ...`, like: + +.. code-block:: bash + + $ sudo docker rmi $(sudo docker images --untagged -q) + + 8abc22fbb042 + 48e5f45168b9 + bf747efa0e2f + 980fe10e5736 + dea752e4e117 + 511136ea3c5a + +NOTE: Docker will warn you if any containers exist that are using these untagged images. + +.. _cli_import: + +``import`` +---------- + +:: + + Usage: docker import URL|- [REPOSITORY[:TAG]] + + Create an empty filesystem image and import the contents of the tarball + (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then optionally tag it. + +At this time, the URL must start with ``http`` and point to a single +file archive (.tar, .tar.gz, .tgz, .bzip, .tar.xz, or .txz) containing a +root filesystem. If you would like to import from a local directory or +archive, you can use the ``-`` parameter to take the data from *stdin*. + +Examples +~~~~~~~~ + +Import from a remote location +............................. + +This will create a new untagged image. + +.. code-block:: bash + + $ sudo docker import http://example.com/exampleimage.tgz + +Import from a local file +........................ + +Import to docker via pipe and *stdin*. + +.. code-block:: bash + + $ cat exampleimage.tgz | sudo docker import - exampleimagelocal:new + +Import from a local directory +............................. + +.. code-block:: bash + + $ sudo tar -c . | docker import - exampleimagedir + +Note the ``sudo`` in this example -- you must preserve the ownership of the +files (especially root ownership) during the archiving with tar. If you are not +root (or the sudo command) when you tar, then the ownerships might not get +preserved. + +.. _cli_info: + +``info`` +-------- + +:: + + Usage: docker info + + Display system-wide information. + +.. code-block:: bash + + $ sudo docker info + Containers: 292 + Images: 194 + Debug mode (server): false + Debug mode (client): false + Fds: 22 + Goroutines: 67 + LXC Version: 0.9.0 + EventsListeners: 115 + Kernel Version: 3.8.0-33-generic + WARNING: No swap limit support + + +.. _cli_insert: + +``insert`` +---------- + +:: + + Usage: docker insert IMAGE URL PATH + + Insert a file from URL in the IMAGE at PATH + +Use the specified ``IMAGE`` as the parent for a new image which adds a +:ref:`layer ` containing the new file. The ``insert`` command does +not modify the original image, and the new image has the contents of the parent +image, plus the new file. + + +Examples +~~~~~~~~ + +Insert file from GitHub +....................... + +.. code-block:: bash + + $ sudo docker insert 8283e18b24bc https://raw.github.com/metalivedev/django/master/postinstall /tmp/postinstall.sh + 06fd35556d7b + +.. _cli_inspect: + +``inspect`` +----------- + +:: + + Usage: docker inspect CONTAINER|IMAGE [CONTAINER|IMAGE...] + + Return low-level information on a container/image + + -f, --format="": Format the output using the given go template. + +By default, this will render all results in a JSON array. If a format +is specified, the given template will be executed for each result. + +Go's `text/template `_ package +describes all the details of the format. + +Examples +~~~~~~~~ + +Get an instance's IP Address +............................ + +For the most part, you can pick out any field from the JSON in a +fairly straightforward manner. + +.. code-block:: bash + + $ sudo docker inspect --format='{{.NetworkSettings.IPAddress}}' $INSTANCE_ID + +List All Port Bindings +...................... + +One can loop over arrays and maps in the results to produce simple +text output: + +.. code-block:: bash + + $ sudo docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}} {{$p}} -> {{(index $conf 0).HostPort}} {{end}}' $INSTANCE_ID + +Find a Specific Port Mapping +............................ + +The ``.Field`` syntax doesn't work when the field name begins with a +number, but the template language's ``index`` function does. The +``.NetworkSettings.Ports`` section contains a map of the internal port +mappings to a list of external address/port objects, so to grab just +the numeric public port, you use ``index`` to find the specific port +map, and then ``index`` 0 contains first object inside of that. Then +we ask for the ``HostPort`` field to get the public address. + +.. code-block:: bash + + $ sudo docker inspect --format='{{(index (index .NetworkSettings.Ports "8787/tcp") 0).HostPort}}' $INSTANCE_ID + +Get config +.......... + +The ``.Field`` syntax doesn't work when the field contains JSON data, +but the template language's custom ``json`` function does. The ``.config`` +section contains complex json object, so to grab it as JSON, you use ``json`` +to convert config object into JSON + +.. code-block:: bash + + $ sudo docker inspect --format='{{json .config}}' $INSTANCE_ID + + +.. _cli_kill: + +``kill`` +-------- + +:: + + Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...] + + Kill a running container (send SIGKILL, or specified signal) + + -s, --signal="KILL": Signal to send to the container + +The main process inside the container will be sent SIGKILL, or any signal specified with option ``--signal``. + +Known Issues (kill) +~~~~~~~~~~~~~~~~~~~ + +* :issue:`197` indicates that ``docker kill`` may leave directories + behind and make it difficult to remove the container. +* :issue:`3844` lxc 1.0.0 beta3 removed ``lcx-kill`` which is used by Docker versions before 0.8.0; + see the issue for a workaround. + +.. _cli_load: + +``load`` +-------- + +:: + + Usage: docker load + + Load an image from a tar archive on STDIN + + -i, --input="": Read from a tar archive file, instead of STDIN + +Loads a tarred repository from a file or the standard input stream. +Restores both images and tags. + +.. code-block:: bash + + $ sudo docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + $ sudo docker load < busybox.tar + $ sudo docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + busybox latest 769b9341d937 7 weeks ago 2.489 MB + $ sudo docker load --input fedora.tar + $ sudo docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + busybox latest 769b9341d937 7 weeks ago 2.489 MB + fedora rawhide 0d20aec6529d 7 weeks ago 387 MB + fedora 20 58394af37342 7 weeks ago 385.5 MB + fedora heisenbug 58394af37342 7 weeks ago 385.5 MB + fedora latest 58394af37342 7 weeks ago 385.5 MB + + +.. _cli_login: + +``login`` +--------- + +:: + + Usage: docker login [OPTIONS] [SERVER] + + Register or Login to the docker registry server + + -e, --email="": Email + -p, --password="": Password + -u, --username="": Username + + If you want to login to a private registry you can + specify this by adding the server name. + + example: + docker login localhost:8080 + + +.. _cli_logs: + +``logs`` +-------- + +:: + + Usage: docker logs [OPTIONS] CONTAINER + + Fetch the logs of a container + + -f, --follow=false: Follow log output + +The ``docker logs`` command is a convenience which batch-retrieves whatever +logs are present at the time of execution. This does not guarantee execution +order when combined with a ``docker run`` (i.e. your run may not have generated +any logs at the time you execute ``docker logs``). + +The ``docker logs --follow`` command combines ``docker logs`` and ``docker attach``: +it will first return all logs from the beginning and then continue streaming +new output from the container's stdout and stderr. + + +.. _cli_port: + +``port`` +-------- + +:: + + Usage: docker port [OPTIONS] CONTAINER PRIVATE_PORT + + Lookup the public-facing port which is NAT-ed to PRIVATE_PORT + + +.. _cli_ps: + +``ps`` +------ + +:: + + Usage: docker ps [OPTIONS] + + List containers + + -a, --all=false: Show all containers. Only running containers are shown by default. + --before="": Show only container created before Id or Name, include non-running ones. + -l, --latest=false: Show only the latest created container, include non-running ones. + -n=-1: Show n last created containers, include non-running ones. + --no-trunc=false: Don't truncate output + -q, --quiet=false: Only display numeric IDs + -s, --size=false: Display sizes, not to be used with -q + --since="": Show only containers created since Id or Name, include non-running ones. + + +Running ``docker ps`` showing 2 linked containers. + +.. code-block:: bash + + $ docker ps + CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + 4c01db0b339c ubuntu:12.04 bash 17 seconds ago Up 16 seconds webapp + d7886598dbe2 crosbymichael/redis:latest /redis-server --dir 33 minutes ago Up 33 minutes 6379/tcp redis,webapp/db + fd2645e2e2b5 busybox:latest top 10 days ago Ghost insane_ptolemy + +The last container is marked as a ``Ghost`` container. It is a container that was running when the docker daemon was restarted (upgraded, or ``-H`` settings changed). The container is still running, but as this docker daemon process is not able to manage it, you can't attach to it. To bring them out of ``Ghost`` Status, you need to use ``docker kill`` or ``docker restart``. + +``docker ps`` will show only running containers by default. To see all containers: ``docker ps -a`` + +.. _cli_pull: + +``pull`` +-------- + +:: + + Usage: docker pull NAME + + Pull an image or a repository from the registry + + -t, --tag="": Download tagged image in repository + + +.. _cli_push: + +``push`` +-------- + +:: + + Usage: docker push NAME + + Push an image or a repository to the registry + + +.. _cli_restart: + +``restart`` +----------- + +:: + + Usage: docker restart [OPTIONS] NAME + + Restart a running container + + -t, --time=10: Number of seconds to try to stop for before killing the container. Once killed it will then be restarted. Default=10 + +.. _cli_rm: + +``rm`` +------ + +:: + + Usage: docker rm [OPTIONS] CONTAINER + + Remove one or more containers + -l, --link="": Remove the link instead of the actual container + -f, --force=false: Force removal of running container + -v, --volumes=false: Remove the volumes associated to the container + +Known Issues (rm) +~~~~~~~~~~~~~~~~~ + +* :issue:`197` indicates that ``docker kill`` may leave directories + behind and make it difficult to remove the container. + + +Examples: +~~~~~~~~~ + +.. code-block:: bash + + $ sudo docker rm /redis + /redis + + +This will remove the container referenced under the link ``/redis``. + + +.. code-block:: bash + + $ sudo docker rm --link /webapp/redis + /webapp/redis + + +This will remove the underlying link between ``/webapp`` and the ``/redis`` containers removing all +network communication. + +.. code-block:: bash + + $ sudo docker rm `docker ps -a -q` + + +This command will delete all stopped containers. The command ``docker ps -a -q`` will return all +existing container IDs and pass them to the ``rm`` command which will delete them. Any running +containers will not be deleted. + +.. _cli_rmi: + +``rmi`` +------- + +:: + + Usage: docker rmi IMAGE [IMAGE...] + + Remove one or more images + + -f, --force=false: Force + --no-prune=false: Do not delete untagged parents + +Removing tagged images +~~~~~~~~~~~~~~~~~~~~~~ + +Images can be removed either by their short or long ID's, or their image names. +If an image has more than one name, each of them needs to be removed before the +image is removed. + +.. code-block:: bash + + $ sudo docker images + REPOSITORY TAG IMAGE ID CREATED SIZE + test1 latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB) + test latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB) + test2 latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB) + + $ sudo docker rmi fd484f19954f + Error: Conflict, cannot delete image fd484f19954f because it is tagged in multiple repositories + 2013/12/11 05:47:16 Error: failed to remove one or more images + + $ sudo docker rmi test1 + Untagged: fd484f19954f4920da7ff372b5067f5b7ddb2fd3830cecd17b96ea9e286ba5b8 + $ sudo docker rmi test2 + Untagged: fd484f19954f4920da7ff372b5067f5b7ddb2fd3830cecd17b96ea9e286ba5b8 + + $ sudo docker images + REPOSITORY TAG IMAGE ID CREATED SIZE + test1 latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB) + $ sudo docker rmi test + Untagged: fd484f19954f4920da7ff372b5067f5b7ddb2fd3830cecd17b96ea9e286ba5b8 + Deleted: fd484f19954f4920da7ff372b5067f5b7ddb2fd3830cecd17b96ea9e286ba5b8 + + +.. _cli_run: + +``run`` +------- + +:: + + Usage: docker run [OPTIONS] IMAGE[:TAG] [COMMAND] [ARG...] + + Run a command in a new container + + -a, --attach=map[]: Attach to stdin, stdout or stderr + -c, --cpu-shares=0: CPU shares (relative weight) + --cidfile="": Write the container ID to the file + -d, --detach=false: Detached mode: Run container in the background, print new container id + -e, --env=[]: Set environment variables + -h, --hostname="": Container host name + -i, --interactive=false: Keep stdin open even if not attached + --privileged=false: Give extended privileges to this container + -m, --memory="": Memory limit (format: , where unit = b, k, m or g) + -n, --networking=true: Enable networking for this container + -p, --publish=[]: Map a network port to the container + --rm=false: Automatically remove the container when it exits (incompatible with -d) + -t, --tty=false: Allocate a pseudo-tty + -u, --user="": Username or UID + --dns=[]: Set custom dns servers for the container + --dns-search=[]: Set custom DNS search domains for the container + -v, --volume=[]: Create a bind mount to a directory or file with: [host-path]:[container-path]:[rw|ro]. If a directory "container-path" is missing, then docker creates a new volume. + --volumes-from="": Mount all volumes from the given container(s) + --entrypoint="": Overwrite the default entrypoint set by the image + -w, --workdir="": Working directory inside the container + --lxc-conf=[]: (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" + --sig-proxy=true: Proxify all received signal to the process (even in non-tty mode) + --expose=[]: Expose a port from the container without publishing it to your host + --link="": Add link to another container (name:alias) + --name="": Assign the specified name to the container. If no name is specific docker will generate a random name + -P, --publish-all=false: Publish all exposed ports to the host interfaces + +The ``docker run`` command first ``creates`` a writeable container layer over +the specified image, and then ``starts`` it using the specified command. That +is, ``docker run`` is equivalent to the API ``/containers/create`` then +``/containers/(id)/start``. +Once the container is stopped it still exists and can be started back up. See ``docker ps -a`` to view a list of all containers. + +The ``docker run`` command can be used in combination with ``docker commit`` to +:ref:`change the command that a container runs `. + +See :ref:`port_redirection` for more detailed information about the ``--expose``, +``-p``, ``-P`` and ``--link`` parameters, and :ref:`working_with_links_names` for +specific examples using ``--link``. + +Known Issues (run --volumes-from) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* :issue:`2702`: "lxc-start: Permission denied - failed to mount" + could indicate a permissions problem with AppArmor. Please see the + issue for a workaround. + +Examples: +~~~~~~~~~ + +.. code-block:: bash + + $ sudo docker run --cidfile /tmp/docker_test.cid ubuntu echo "test" + +This will create a container and print ``test`` to the console. The +``cidfile`` flag makes Docker attempt to create a new file and write the +container ID to it. If the file exists already, Docker will return an +error. Docker will close this file when ``docker run`` exits. + +.. code-block:: bash + + $ sudo docker run -t -i --rm ubuntu bash + root@bc338942ef20:/# mount -t tmpfs none /mnt + mount: permission denied + + +This will *not* work, because by default, most potentially dangerous +kernel capabilities are dropped; including ``cap_sys_admin`` (which is +required to mount filesystems). However, the ``--privileged`` flag will +allow it to run: + +.. code-block:: bash + + $ sudo docker run --privileged ubuntu bash + root@50e3f57e16e6:/# mount -t tmpfs none /mnt + root@50e3f57e16e6:/# df -h + Filesystem Size Used Avail Use% Mounted on + none 1.9G 0 1.9G 0% /mnt + + +The ``--privileged`` flag gives *all* capabilities to the container, +and it also lifts all the limitations enforced by the ``device`` +cgroup controller. In other words, the container can then do almost +everything that the host can do. This flag exists to allow special +use-cases, like running Docker within Docker. + +.. code-block:: bash + + $ sudo docker run -w /path/to/dir/ -i -t ubuntu pwd + +The ``-w`` lets the command being executed inside directory given, +here ``/path/to/dir/``. If the path does not exists it is created inside the +container. + +.. code-block:: bash + + $ sudo docker run -v `pwd`:`pwd` -w `pwd` -i -t ubuntu pwd + +The ``-v`` flag mounts the current working directory into the container. +The ``-w`` lets the command being executed inside the current +working directory, by changing into the directory to the value +returned by ``pwd``. So this combination executes the command +using the container, but inside the current working directory. + +.. code-block:: bash + + $ sudo docker run -v /doesnt/exist:/foo -w /foo -i -t ubuntu bash + +When the host directory of a bind-mounted volume doesn't exist, Docker +will automatically create this directory on the host for you. In the +example above, Docker will create the ``/doesnt/exist`` folder before +starting your container. + +.. code-block:: bash + + $ sudo docker run -t -i -v /var/run/docker.sock:/var/run/docker.sock -v ./static-docker:/usr/bin/docker busybox sh + +By bind-mounting the docker unix socket and statically linked docker binary +(such as that provided by https://get.docker.io), you give the container +the full access to create and manipulate the host's docker daemon. + +.. code-block:: bash + + $ sudo docker run -p 127.0.0.1:80:8080 ubuntu bash + +This binds port ``8080`` of the container to port ``80`` on ``127.0.0.1`` of the +host machine. :ref:`port_redirection` explains in detail how to manipulate ports +in Docker. + +.. code-block:: bash + + $ sudo docker run --expose 80 ubuntu bash + +This exposes port ``80`` of the container for use within a link without +publishing the port to the host system's interfaces. :ref:`port_redirection` +explains in detail how to manipulate ports in Docker. + +.. code-block:: bash + + $ sudo docker run --name console -t -i ubuntu bash + +This will create and run a new container with the container name +being ``console``. + +.. code-block:: bash + + $ sudo docker run --link /redis:redis --name console ubuntu bash + +The ``--link`` flag will link the container named ``/redis`` into the +newly created container with the alias ``redis``. The new container +can access the network and environment of the redis container via +environment variables. The ``--name`` flag will assign the name ``console`` +to the newly created container. + +.. code-block:: bash + + $ sudo docker run --volumes-from 777f7dc92da7,ba8c0c54f0f2:ro -i -t ubuntu pwd + +The ``--volumes-from`` flag mounts all the defined volumes from the +referenced containers. Containers can be specified by a comma separated +list or by repetitions of the ``--volumes-from`` argument. The container +ID may be optionally suffixed with ``:ro`` or ``:rw`` to mount the volumes in +read-only or read-write mode, respectively. By default, the volumes are mounted +in the same mode (read write or read only) as the reference container. + +A complete example +.................. + +.. code-block:: bash + + $ sudo docker run -d --name static static-web-files sh + $ sudo docker run -d --expose=8098 --name riak riakserver + $ sudo docker run -d -m 100m -e DEVELOPMENT=1 -e BRANCH=example-code -v $(pwd):/app/bin:ro --name app appserver + $ sudo docker run -d -p 1443:443 --dns=dns.dev.org --dns-search=dev.org -v /var/log/httpd --volumes-from static --link riak --link app -h www.sven.dev.org --name web webserver + $ sudo docker run -t -i --rm --volumes-from web -w /var/log/httpd busybox tail -f access.log + +This example shows 5 containers that might be set up to test a web application change: + +1. Start a pre-prepared volume image ``static-web-files`` (in the background) that has CSS, image and static HTML in it, (with a ``VOLUME`` instruction in the ``Dockerfile`` to allow the web server to use those files); +2. Start a pre-prepared ``riakserver`` image, give the container name ``riak`` and expose port ``8098`` to any containers that link to it; +3. Start the ``appserver`` image, restricting its memory usage to 100MB, setting two environment variables ``DEVELOPMENT`` and ``BRANCH`` and bind-mounting the current directory (``$(pwd)``) in the container in read-only mode as ``/app/bin``; +4. Start the ``webserver``, mapping port ``443`` in the container to port ``1443`` on the Docker server, setting the DNS server to ``dns.dev.org`` and DNS search domain to ``dev.org``, creating a volume to put the log files into (so we can access it from another container), then importing the files from the volume exposed by the ``static`` container, and linking to all exposed ports from ``riak`` and ``app``. Lastly, we set the hostname to ``web.sven.dev.org`` so its consistent with the pre-generated SSL certificate; +5. Finally, we create a container that runs ``tail -f access.log`` using the logs volume from the ``web`` container, setting the workdir to ``/var/log/httpd``. The ``--rm`` option means that when the container exits, the container's layer is removed. + + +.. _cli_save: + +``save`` +--------- + +:: + + Usage: docker save IMAGE + + Save an image to a tar archive (streamed to stdout by default) + + -o, --output="": Write to an file, instead of STDOUT + + +Produces a tarred repository to the standard output stream. +Contains all parent layers, and all tags + versions, or specified repo:tag. + +.. code-block:: bash + + $ sudo docker save busybox > busybox.tar + $ ls -sh b.tar + 2.7M b.tar + $ sudo docker save --output busybox.tar busybox + $ ls -sh b.tar + 2.7M b.tar + $ sudo docker save -o fedora-all.tar fedora + $ sudo docker save -o fedora-latest.tar fedora:latest + + +.. _cli_search: + +``search`` +---------- + +:: + + Usage: docker search TERM + + Search the docker index for images + + --no-trunc=false: Don't truncate output + -s, --stars=0: Only displays with at least xxx stars + -t, --trusted=false: Only show trusted builds + +.. _cli_start: + +``start`` +--------- + +:: + + Usage: docker start [OPTIONS] CONTAINER + + Start a stopped container + + -a, --attach=false: Attach container's stdout/stderr and forward all signals to the process + -i, --interactive=false: Attach container's stdin + +.. _cli_stop: + +``stop`` +-------- + +:: + + Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...] + + Stop a running container (Send SIGTERM, and then SIGKILL after grace period) + + -t, --time=10: Number of seconds to wait for the container to stop before killing it. + +The main process inside the container will receive SIGTERM, and after a grace period, SIGKILL + +.. _cli_tag: + +``tag`` +------- + +:: + + Usage: docker tag [OPTIONS] IMAGE [REGISTRYHOST/][USERNAME/]NAME[:TAG] + + Tag an image into a repository + + -f, --force=false: Force + +.. _cli_top: + +``top`` +------- + +:: + + Usage: docker top CONTAINER [ps OPTIONS] + + Lookup the running processes of a container + +.. _cli_version: + +``version`` +----------- + +Show the version of the Docker client, daemon, and latest released version. + + +.. _cli_wait: + +``wait`` +-------- + +:: + + Usage: docker wait [OPTIONS] NAME + + Block until a container stops, then print its exit code. diff --git a/server/server.go b/server/server.go index 85f1125ba..f0a8cea0b 100644 --- a/server/server.go +++ b/server/server.go @@ -55,6 +55,7 @@ import ( "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/utils/filters" ) func (srv *Server) handlerWrap(h engine.Handler) engine.Handler { @@ -694,10 +695,23 @@ func (srv *Server) ImagesViz(job *engine.Job) engine.Status { func (srv *Server) Images(job *engine.Job) engine.Status { var ( - allImages map[string]*image.Image - err error + allImages map[string]*image.Image + err error + filt_tagged = true ) - if job.GetenvBool("all") { + + imageFilters, err := filters.ParseFlag(job.Getenv("filters"), nil) + if err != nil { + return job.Error(err) + } + if i, ok := imageFilters["untagged"]; ok && strings.ToLower(i) == "true" { + filt_tagged = false + } + if i, ok := imageFilters["tagged"]; ok && strings.ToLower(i) == "false" { + filt_tagged = false + } + + if job.GetenvBool("all") && !filt_tagged { allImages, err = srv.daemon.Graph().Map() } else { allImages, err = srv.daemon.Graph().Heads() @@ -721,17 +735,22 @@ func (srv *Server) Images(job *engine.Job) engine.Status { } if out, exists := lookup[id]; exists { - out.SetList("RepoTags", append(out.GetList("RepoTags"), fmt.Sprintf("%s:%s", name, tag))) + if filt_tagged { + out.SetList("RepoTags", append(out.GetList("RepoTags"), fmt.Sprintf("%s:%s", name, tag))) + } } else { - out := &engine.Env{} + // get the boolean list for if only the untagged images are requested delete(allImages, id) - out.Set("ParentId", image.Parent) - out.SetList("RepoTags", []string{fmt.Sprintf("%s:%s", name, tag)}) - out.Set("Id", image.ID) - out.SetInt64("Created", image.Created.Unix()) - out.SetInt64("Size", image.Size) - out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size) - lookup[id] = out + if filt_tagged { + out := &engine.Env{} + out.Set("ParentId", image.Parent) + out.SetList("RepoTags", []string{fmt.Sprintf("%s:%s", name, tag)}) + out.Set("Id", image.ID) + out.SetInt64("Created", image.Created.Unix()) + out.SetInt64("Size", image.Size) + out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size) + lookup[id] = out + } } } diff --git a/utils/filters/filter.go b/utils/filters/filter.go new file mode 100644 index 000000000..b9db01361 --- /dev/null +++ b/utils/filters/filter.go @@ -0,0 +1,53 @@ +package filters + +import ( + "errors" + "fmt" + "io" +) + +var DefaultFilterProcs = FilterProcSet{} + +func Register(name string, fp FilterProc) error { + return DefaultFilterProcs.Register(name, fp) +} + +var ErrorFilterExists = errors.New("filter already exists and ") +var ErrorFilterExistsConflict = errors.New("filter already exists and FilterProc are different") + +type FilterProcSet map[string]FilterProc + +func (fs FilterProcSet) Process(context string) { +} + +func (fs FilterProcSet) Register(name string, fp FilterProc) error { + if v, ok := fs[name]; ok { + if v == fp { + return ErrorFilterExists + } else { + return ErrorFilterExistsConflict + } + } + fs[name] = fp + return nil +} + +type FilterProc interface { + Process(context, key, value string, output io.Writer) error +} + +type UnknownFilterProc struct{} + +func (ufp UnknownFilterProc) Process(context, key, value string, output io.Writer) error { + if output != nil { + fmt.Fprintf(output, "do not know how to process [%s : %s]", key, value) + } + return nil +} + +type Filter interface { + Scope() string + Target() string + Expressions() []string + Match(interface{}) bool +} diff --git a/utils/filters/parse.go b/utils/filters/parse.go new file mode 100644 index 000000000..d0cbe23c2 --- /dev/null +++ b/utils/filters/parse.go @@ -0,0 +1,47 @@ +package filters + +import ( + "errors" + "strings" +) + +/* +Parse the argument to the filter flag. Like + + `docker ps -f 'created=today;image.name=ubuntu*'` + +Filters delimited by ';', and expected to be 'name=value' + +If prev map is provided, then it is appended to, and returned. By default a new +map is created. +*/ +func ParseFlag(arg string, prev map[string]string) (map[string]string, error) { + var filters map[string]string + if prev != nil { + filters = prev + } else { + filters = map[string]string{} + } + if len(arg) == 0 { + return filters, nil + } + + for _, chunk := range strings.Split(arg, ";") { + if !strings.Contains(chunk, "=") { + return filters, ErrorBadFormat + } + f := strings.SplitN(chunk, "=", 2) + filters[f[0]] = f[1] + } + return filters, nil +} + +var ErrorBadFormat = errors.New("bad format of filter (expected name=value)") + +func ToParam(f map[string]string) string { + fs := []string{} + for k, v := range f { + fs = append(fs, k+"="+v) + } + return strings.Join(fs, ";") +} From caf9b19b0c444467aff493781afb66df923250ff Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 26 Mar 2014 14:48:56 -0400 Subject: [PATCH 335/400] filters: remove out filter proc prototype Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- utils/filters/filter.go | 53 ----------------------------------------- 1 file changed, 53 deletions(-) delete mode 100644 utils/filters/filter.go diff --git a/utils/filters/filter.go b/utils/filters/filter.go deleted file mode 100644 index b9db01361..000000000 --- a/utils/filters/filter.go +++ /dev/null @@ -1,53 +0,0 @@ -package filters - -import ( - "errors" - "fmt" - "io" -) - -var DefaultFilterProcs = FilterProcSet{} - -func Register(name string, fp FilterProc) error { - return DefaultFilterProcs.Register(name, fp) -} - -var ErrorFilterExists = errors.New("filter already exists and ") -var ErrorFilterExistsConflict = errors.New("filter already exists and FilterProc are different") - -type FilterProcSet map[string]FilterProc - -func (fs FilterProcSet) Process(context string) { -} - -func (fs FilterProcSet) Register(name string, fp FilterProc) error { - if v, ok := fs[name]; ok { - if v == fp { - return ErrorFilterExists - } else { - return ErrorFilterExistsConflict - } - } - fs[name] = fp - return nil -} - -type FilterProc interface { - Process(context, key, value string, output io.Writer) error -} - -type UnknownFilterProc struct{} - -func (ufp UnknownFilterProc) Process(context, key, value string, output io.Writer) error { - if output != nil { - fmt.Fprintf(output, "do not know how to process [%s : %s]", key, value) - } - return nil -} - -type Filter interface { - Scope() string - Target() string - Expressions() []string - Match(interface{}) bool -} From 55d95185edd4a646cd2a05b9397be6b8667d60d0 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 26 Mar 2014 14:58:33 -0400 Subject: [PATCH 336/400] filters, for images: docs Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- docs/sources/reference/commandline/cli.rst | 26 +++++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 6af6a85c8..dc83b0cd4 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -598,12 +598,13 @@ To see how the ``docker:latest`` image was built: List images -a, --all=false: Show all images (by default filter out the intermediate images used to build) + -f, --filter=[]: Provide filter values (i.e. 'tagged=false') --no-trunc=false: Don't truncate output -q, --quiet=false: Only show numeric IDs -t, --tree=false: Output graph in tree format - -u, --untagged show only untagged images -v, --viz=false: Output graph in graphviz format + Listing the most recently created images ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -678,12 +679,25 @@ Displaying image hierarchy └─c96a99614930 Size: 12.29 kB (virtual 642.2 MB) └─a6a357a48c49 Size: 12.29 kB (virtual 642.2 MB) Tags: ndj/mongodb:latest -Displaying untagged (orphan) images -~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Filtering +~~~~~~~~~ + +The filtering flag (-f or --filter) format is of "key=value". If there are more +than one flag, then either semi-colon delimit (";"), or pass multiple flags. + +Current filters: + * tagged (boolean - true or false) + * untagged (boolean - true or false) + + + +Filtering for untagged images +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: bash - $ sudo docker images --untagged + $ sudo docker images --filter "tagged=false" REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE 8abc22fbb042 4 weeks ago 0 B @@ -702,7 +716,7 @@ By having this flag it allows for batch cleanup. .. code-block:: bash - $ sudo docker images --untagged -q + $ sudo docker images -f "untagged=true" -q 8abc22fbb042 48e5f45168b9 @@ -716,7 +730,7 @@ Ready for use by `docker rmi ...`, like: .. code-block:: bash - $ sudo docker rmi $(sudo docker images --untagged -q) + $ sudo docker rmi $(sudo docker images -f "untagged=true" -q) 8abc22fbb042 48e5f45168b9 From babd5720157898bb33d806bda920d9f9f2df73f0 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 26 Mar 2014 15:14:26 -0400 Subject: [PATCH 337/400] filters: cleanup & fmt Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- api/client/commands.go | 16 ---------------- utils/filters/parse.go | 6 +++--- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index 836fe3720..d0ac62148 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -1168,24 +1168,8 @@ func (cli *DockerCli) CmdImages(args ...string) error { return err } } - /* - var ( - untagged bool - ) - for k,v := range imageFilters { - } - */ - - // seeing -all untagged images is redundant, and no point in seeing a visualization of that - /* - if *flUntagged && (*all || *flViz || *flTree) { - fmt.Fprintln(cli.err, "Notice: --untagged is not to be used with --all, --tree or --viz") - *flUntagged = false - } - */ matchName := cmd.Arg(0) - // FIXME: --viz and --tree are deprecated. Remove them in a future version. if *flViz || *flTree { v := url.Values{ diff --git a/utils/filters/parse.go b/utils/filters/parse.go index d0cbe23c2..a813be9ff 100644 --- a/utils/filters/parse.go +++ b/utils/filters/parse.go @@ -22,9 +22,9 @@ func ParseFlag(arg string, prev map[string]string) (map[string]string, error) { } else { filters = map[string]string{} } - if len(arg) == 0 { - return filters, nil - } + if len(arg) == 0 { + return filters, nil + } for _, chunk := range strings.Split(arg, ";") { if !strings.Contains(chunk, "=") { From cb7857de5d2595632b76e4e17acede85a519001a Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 7 Apr 2014 13:38:53 -0400 Subject: [PATCH 338/400] images filter: remove the redundant inverted filter Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- docs/sources/reference/commandline/cli.rst | 3 +-- server/server.go | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index dc83b0cd4..7f1fab2af 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -687,7 +687,6 @@ The filtering flag (-f or --filter) format is of "key=value". If there are more than one flag, then either semi-colon delimit (";"), or pass multiple flags. Current filters: - * tagged (boolean - true or false) * untagged (boolean - true or false) @@ -697,7 +696,7 @@ Filtering for untagged images .. code-block:: bash - $ sudo docker images --filter "tagged=false" + $ sudo docker images --filter "untagged=true" REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE 8abc22fbb042 4 weeks ago 0 B diff --git a/server/server.go b/server/server.go index f0a8cea0b..dd54ab67d 100644 --- a/server/server.go +++ b/server/server.go @@ -707,9 +707,6 @@ func (srv *Server) Images(job *engine.Job) engine.Status { if i, ok := imageFilters["untagged"]; ok && strings.ToLower(i) == "true" { filt_tagged = false } - if i, ok := imageFilters["tagged"]; ok && strings.ToLower(i) == "false" { - filt_tagged = false - } if job.GetenvBool("all") && !filt_tagged { allImages, err = srv.daemon.Graph().Map() From 3391aac2b27ac95f58a20a2eb4046ad1f00b8be6 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Sun, 18 May 2014 15:23:09 -0400 Subject: [PATCH 339/400] images: fix markdown documentation Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- docs/sources/reference/commandline/cli.md | 41 + docs/sources/reference/commandline/cli.rst | 1512 -------------------- 2 files changed, 41 insertions(+), 1512 deletions(-) delete mode 100644 docs/sources/reference/commandline/cli.rst diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index f2e370ace..0c3623f9e 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -440,6 +440,7 @@ To see how the `docker:latest` image was built: List images -a, --all=false Show all images (by default filter out the intermediate image layers) + -f, --filter=[]: Provide filter values (i.e. 'tagged=false') --no-trunc=false Don't truncate output -q, --quiet=false Only show numeric IDs @@ -479,6 +480,46 @@ by default. tryout latest 2629d1fa0b81b222fca63371ca16cbf6a0772d07759ff80e8d1369b926940074 23 hours ago 131.5 MB 5ed6274db6ceb2397844896966ea239290555e74ef307030ebb01ff91b1914df 24 hours ago 1.089 GB +### Filtering + +The filtering flag (-f or --filter) format is of "key=value". If there are more +than one filter, then pass multiple flags (e.g. `--filter "foo=bar" --filter "bif=baz"`) + +Current filters: + * untagged (boolean - true or false) + +#### untagged images + + $ sudo docker images --filter "untagged=true" + + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + 8abc22fbb042 4 weeks ago 0 B + 48e5f45168b9 4 weeks ago 2.489 MB + bf747efa0e2f 4 weeks ago 0 B + 980fe10e5736 12 weeks ago 101.4 MB + dea752e4e117 12 weeks ago 101.4 MB + 511136ea3c5a 8 months ago 0 B + +This will display untagged images, that are the leaves of the images tree (not +intermediary layers). These images occur when a new build of an image takes the +repo:tag away from the IMAGE ID, leaving it untagged. A warning will be issued +if trying to remove an image when a container is presently using it. +By having this flag it allows for batch cleanup. + +Ready for use by `docker rmi ...`, like: + + $ sudo docker rmi $(sudo docker images -f "untagged=true" -q) + + 8abc22fbb042 + 48e5f45168b9 + bf747efa0e2f + 980fe10e5736 + dea752e4e117 + 511136ea3c5a + +NOTE: Docker will warn you if any containers exist that are using these untagged images. + + ## import Usage: docker import URL|- [REPOSITORY[:TAG]] diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst deleted file mode 100644 index 7f1fab2af..000000000 --- a/docs/sources/reference/commandline/cli.rst +++ /dev/null @@ -1,1512 +0,0 @@ -:title: Command Line Interface -:description: Docker's CLI command description and usage -:keywords: Docker, Docker documentation, CLI, command line - -.. _cli: - -Command Line Help ------------------ - -To list available commands, either run ``docker`` with no parameters or execute -``docker help``:: - - $ sudo docker - Usage: docker [OPTIONS] COMMAND [arg...] - -H=[unix:///var/run/docker.sock]: tcp://[host]:port to bind/connect to or unix://[/path/to/socket] to use. When host=[127.0.0.1] is omitted for tcp or path=[/var/run/docker.sock] is omitted for unix sockets, default values are used. - - A self-sufficient runtime for linux containers. - - ... - -.. _cli_options: - -Options -------- - -Single character commandline options can be combined, so rather than typing -``docker run -t -i --name test busybox sh``, you can write -``docker run -ti --name test busybox sh``. - -Boolean -~~~~~~~ - -Boolean options look like ``-d=false``. The value you see is the -default value which gets set if you do **not** use the boolean -flag. If you do call ``run -d``, that sets the opposite boolean value, -so in this case, ``true``, and so ``docker run -d`` **will** run in -"detached" mode, in the background. Other boolean options are similar --- specifying them will set the value to the opposite of the default -value. - -Multi -~~~~~ - -Options like ``-a=[]`` indicate they can be specified multiple times:: - - docker run -a stdin -a stdout -a stderr -i -t ubuntu /bin/bash - -Sometimes this can use a more complex value string, as for ``-v``:: - - docker run -v /host:/container example/mysql - -Strings and Integers -~~~~~~~~~~~~~~~~~~~~ - -Options like ``--name=""`` expect a string, and they can only be -specified once. Options like ``-c=0`` expect an integer, and they can -only be specified once. - ----- - -Commands --------- - -.. _cli_daemon: - -``daemon`` ----------- - -:: - - Usage of docker: - -D, --debug=false: Enable debug mode - -H, --host=[]: Multiple tcp://host:port or unix://path/to/socket to bind in daemon mode, single connection otherwise. systemd socket activation can be used with fd://[socketfd]. - -G, --group="docker": Group to assign the unix socket specified by -H when running in daemon mode; use '' (the empty string) to disable setting of a group - --api-enable-cors=false: Enable CORS headers in the remote API - -b, --bridge="": Attach containers to a pre-existing network bridge; use 'none' to disable container networking - --bip="": Use this CIDR notation address for the network bridge's IP, not compatible with -b - -d, --daemon=false: Enable daemon mode - --dns=[]: Force docker to use specific DNS servers - --dns-search=[]: Force Docker to use specific DNS search domains - -g, --graph="/var/lib/docker": Path to use as the root of the docker runtime - --icc=true: Enable inter-container communication - --ip="0.0.0.0": Default IP address to use when binding container ports - --ip-forward=true: Enable net.ipv4.ip_forward - --iptables=true: Enable Docker's addition of iptables rules - -p, --pidfile="/var/run/docker.pid": Path to use for daemon PID file - -r, --restart=true: Restart previously running containers - -s, --storage-driver="": Force the docker runtime to use a specific storage driver - -e, --exec-driver="native": Force the docker runtime to use a specific exec driver - -v, --version=false: Print version information and quit - --tls=false: Use TLS; implied by tls-verify flags - --tlscacert="~/.docker/ca.pem": Trust only remotes providing a certificate signed by the CA given here - --tlscert="~/.docker/cert.pem": Path to TLS certificate file - --tlskey="~/.docker/key.pem": Path to TLS key file - --tlsverify=false: Use TLS and verify the remote (daemon: verify client, client: verify daemon) - --mtu=0: Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if no default route is available - -The Docker daemon is the persistent process that manages containers. Docker uses the same binary for both the -daemon and client. To run the daemon you provide the ``-d`` flag. - -To force Docker to use devicemapper as the storage driver, use ``docker -d -s devicemapper``. - -To set the DNS server for all Docker containers, use ``docker -d --dns 8.8.8.8``. - -To set the DNS search domain for all Docker containers, use ``docker -d --dns-search example.com``. - -To run the daemon with debug output, use ``docker -d -D``. - -To use lxc as the execution driver, use ``docker -d -e lxc``. - -The docker client will also honor the ``DOCKER_HOST`` environment variable to set -the ``-H`` flag for the client. - -:: - - docker -H tcp://0.0.0.0:4243 ps - # or - export DOCKER_HOST="tcp://0.0.0.0:4243" - docker ps - # both are equal - -To run the daemon with `systemd socket activation `_, use ``docker -d -H fd://``. -Using ``fd://`` will work perfectly for most setups but you can also specify individual sockets too ``docker -d -H fd://3``. -If the specified socket activated files aren't found then docker will exit. -You can find examples of using systemd socket activation with docker and systemd in the `docker source tree `_. - -Docker supports softlinks for the Docker data directory (``/var/lib/docker``) and for ``/tmp``. -TMPDIR and the data directory can be set like this: - -:: - - TMPDIR=/mnt/disk2/tmp /usr/local/bin/docker -d -D -g /var/lib/docker -H unix:// > /var/lib/boot2docker/docker.log 2>&1 - # or - export TMPDIR=/mnt/disk2/tmp - /usr/local/bin/docker -d -D -g /var/lib/docker -H unix:// > /var/lib/boot2docker/docker.log 2>&1 - -.. _cli_attach: - -``attach`` ----------- - -:: - - Usage: docker attach CONTAINER - - Attach to a running container. - - --no-stdin=false: Do not attach stdin - --sig-proxy=true: Proxify all received signal to the process (even in non-tty mode) - -You can detach from the container again (and leave it running) with -``CTRL-c`` (for a quiet exit) or ``CTRL-\`` to get a stacktrace of -the Docker client when it quits. When you detach from the container's -process the exit code will be returned to the client. - -To stop a container, use ``docker stop``. - -To kill the container, use ``docker kill``. - -.. _cli_attach_examples: - -Examples: -~~~~~~~~~ - -.. code-block:: bash - - $ ID=$(sudo docker run -d ubuntu /usr/bin/top -b) - $ sudo docker attach $ID - top - 02:05:52 up 3:05, 0 users, load average: 0.01, 0.02, 0.05 - Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie - Cpu(s): 0.1%us, 0.2%sy, 0.0%ni, 99.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st - Mem: 373572k total, 355560k used, 18012k free, 27872k buffers - Swap: 786428k total, 0k used, 786428k free, 221740k cached - - PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND - 1 root 20 0 17200 1116 912 R 0 0.3 0:00.03 top - - top - 02:05:55 up 3:05, 0 users, load average: 0.01, 0.02, 0.05 - Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie - Cpu(s): 0.0%us, 0.2%sy, 0.0%ni, 99.8%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st - Mem: 373572k total, 355244k used, 18328k free, 27872k buffers - Swap: 786428k total, 0k used, 786428k free, 221776k cached - - PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND - 1 root 20 0 17208 1144 932 R 0 0.3 0:00.03 top - - - top - 02:05:58 up 3:06, 0 users, load average: 0.01, 0.02, 0.05 - Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie - Cpu(s): 0.2%us, 0.3%sy, 0.0%ni, 99.5%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st - Mem: 373572k total, 355780k used, 17792k free, 27880k buffers - Swap: 786428k total, 0k used, 786428k free, 221776k cached - - PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND - 1 root 20 0 17208 1144 932 R 0 0.3 0:00.03 top - ^C$ - $ sudo docker stop $ID - -.. _cli_build: - -``build`` ---------- - -:: - - Usage: docker build [OPTIONS] PATH | URL | - - Build a new container image from the source code at PATH - -t, --tag="": Repository name (and optionally a tag) to be applied - to the resulting image in case of success. - -q, --quiet=false: Suppress the verbose output generated by the containers. - --no-cache: Do not use the cache when building the image. - --rm=true: Remove intermediate containers after a successful build - -The files at ``PATH`` or ``URL`` are called the "context" of the build. -The build process may refer to any of the files in the context, for example when -using an :ref:`ADD ` instruction. -When a single ``Dockerfile`` is given as ``URL``, then no context is set. - -When a Git repository is set as ``URL``, then the repository is used as the context. -The Git repository is cloned with its submodules (`git clone --recursive`). -A fresh git clone occurs in a temporary directory on your local host, and then this -is sent to the Docker daemon as the context. -This way, your local user credentials and vpn's etc can be used to access private repositories - -.. _cli_build_examples: - -.. seealso:: :ref:`dockerbuilder`. - -Examples: -~~~~~~~~~ - -.. code-block:: bash - - $ sudo docker build . - Uploading context 10240 bytes - Step 1 : FROM busybox - Pulling repository busybox - ---> e9aa60c60128MB/2.284 MB (100%) endpoint: https://cdn-registry-1.docker.io/v1/ - Step 2 : RUN ls -lh / - ---> Running in 9c9e81692ae9 - total 24 - drwxr-xr-x 2 root root 4.0K Mar 12 2013 bin - drwxr-xr-x 5 root root 4.0K Oct 19 00:19 dev - drwxr-xr-x 2 root root 4.0K Oct 19 00:19 etc - drwxr-xr-x 2 root root 4.0K Nov 15 23:34 lib - lrwxrwxrwx 1 root root 3 Mar 12 2013 lib64 -> lib - dr-xr-xr-x 116 root root 0 Nov 15 23:34 proc - lrwxrwxrwx 1 root root 3 Mar 12 2013 sbin -> bin - dr-xr-xr-x 13 root root 0 Nov 15 23:34 sys - drwxr-xr-x 2 root root 4.0K Mar 12 2013 tmp - drwxr-xr-x 2 root root 4.0K Nov 15 23:34 usr - ---> b35f4035db3f - Step 3 : CMD echo Hello World - ---> Running in 02071fceb21b - ---> f52f38b7823e - Successfully built f52f38b7823e - Removing intermediate container 9c9e81692ae9 - Removing intermediate container 02071fceb21b - - -This example specifies that the ``PATH`` is ``.``, and so all the files in -the local directory get tar'd and sent to the Docker daemon. The ``PATH`` -specifies where to find the files for the "context" of the build on -the Docker daemon. Remember that the daemon could be running on a -remote machine and that no parsing of the ``Dockerfile`` happens at the -client side (where you're running ``docker build``). That means that -*all* the files at ``PATH`` get sent, not just the ones listed to -:ref:`ADD ` in the ``Dockerfile``. - -The transfer of context from the local machine to the Docker daemon is -what the ``docker`` client means when you see the "Uploading context" -message. - -If you wish to keep the intermediate containers after the build is complete, -you must use ``--rm=false``. This does not affect the build cache. - - -.. code-block:: bash - - $ sudo docker build -t vieux/apache:2.0 . - -This will build like the previous example, but it will then tag the -resulting image. The repository name will be ``vieux/apache`` and the -tag will be ``2.0`` - - -.. code-block:: bash - - $ sudo docker build - < Dockerfile - -This will read a ``Dockerfile`` from *stdin* without context. Due to -the lack of a context, no contents of any local directory will be sent -to the ``docker`` daemon. Since there is no context, a ``Dockerfile`` -``ADD`` only works if it refers to a remote URL. - -.. code-block:: bash - - $ sudo docker build github.com/creack/docker-firefox - -This will clone the GitHub repository and use the cloned repository as -context. The ``Dockerfile`` at the root of the repository is used as -``Dockerfile``. Note that you can specify an arbitrary Git repository -by using the ``git://`` schema. - - -.. _cli_commit: - -``commit`` ----------- - -:: - - Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]] - - Create a new image from a container's changes - - -m, --message="": Commit message - -a, --author="": Author (eg. "John Hannibal Smith " - --run="": Configuration changes to be applied when the image is launched with `docker run`. - (ex: --run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}') - -.. _cli_commit_examples: - -Commit an existing container -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: bash - - $ sudo docker ps - ID IMAGE COMMAND CREATED STATUS PORTS - c3f279d17e0a ubuntu:12.04 /bin/bash 7 days ago Up 25 hours - 197387f1b436 ubuntu:12.04 /bin/bash 7 days ago Up 25 hours - $ docker commit c3f279d17e0a SvenDowideit/testimage:version3 - f5283438590d - $ docker images | head - REPOSITORY TAG ID CREATED VIRTUAL SIZE - SvenDowideit/testimage version3 f5283438590d 16 seconds ago 335.7 MB - -Change the command that a container runs -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Sometimes you have an application container running just a service and you need -to make a quick change and then change it back. - -In this example, we run a container with ``ls`` and then change the image to -run ``ls /etc``. - -.. code-block:: bash - - $ docker run -t --name test ubuntu ls - bin boot dev etc home lib lib64 media mnt opt proc root run sbin selinux srv sys tmp usr var - $ docker commit --run='{"Cmd": ["ls","/etc"]}' test test2 - 933d16de9e70005304c1717b5c6f2f39d6fd50752834c6f34a155c70790011eb - $ docker run -t test2 - adduser.conf gshadow login.defs rc0.d - alternatives gshadow- logrotate.d rc1.d - apt host.conf lsb-base rc2.d - ... - -Merged configs example -...................... - -Say you have a Dockerfile like so: - -.. code-block:: bash - - ENV MYVAR foobar - RUN apt-get install openssh - EXPOSE 22 - CMD ["/usr/sbin/sshd -D"] - ... - -If you run that, make some changes, and then commit, Docker will merge the environment variable and exposed port configuration settings with any that you specify in the --run= option. This is a change from Docker 0.8.0 and prior where no attempt was made to preserve any existing configuration on commit. - -.. code-block:: bash - - $ docker build -t me/foo . - $ docker run -t -i me/foo /bin/bash - foo-container$ [make changes in the container] - foo-container$ exit - $ docker commit --run='{"Cmd": ["ls"]}' [container-id] me/bar - ... - -The me/bar image will now have port 22 exposed, MYVAR env var set to 'foobar', and its default command will be ["ls"]. - -Note that this is currently a shallow merge. So, for example, if you had specified a new port spec in the --run= config above, that would have clobbered the 'EXPOSE 22' setting from the parent container. - -Full --run example -.................. - -The ``--run`` JSON hash changes the ``Config`` section when running ``docker inspect CONTAINERID`` -or ``config`` when running ``docker inspect IMAGEID``. Existing configuration key-values that are -not overridden in the JSON hash will be merged in. - -(Multiline is okay within a single quote ``'``) - -.. code-block:: bash - - $ sudo docker commit --run=' - { - "Entrypoint" : null, - "Privileged" : false, - "User" : "", - "VolumesFrom" : "", - "Cmd" : ["cat", "-e", "/etc/resolv.conf"], - "Dns" : ["8.8.8.8", "8.8.4.4"], - "DnsSearch" : ["example.com"], - "MemorySwap" : 0, - "AttachStdin" : false, - "AttachStderr" : false, - "CpuShares" : 0, - "OpenStdin" : false, - "Volumes" : null, - "Hostname" : "122612f45831", - "PortSpecs" : ["22", "80", "443"], - "Image" : "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", - "Tty" : false, - "Env" : [ - "HOME=/", - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" - ], - "StdinOnce" : false, - "Domainname" : "", - "WorkingDir" : "/", - "NetworkDisabled" : false, - "Memory" : 0, - "AttachStdout" : false - }' $CONTAINER_ID - -.. _cli_cp: - -``cp`` ------- - -:: - - Usage: docker cp CONTAINER:PATH HOSTPATH - - Copy files/folders from the containers filesystem to the host - path. Paths are relative to the root of the filesystem. - -.. code-block:: bash - - $ sudo docker cp 7bb0e258aefe:/etc/debian_version . - $ sudo docker cp blue_frog:/etc/hosts . - -.. _cli_diff: - -``diff`` --------- - -:: - - Usage: docker diff CONTAINER - - List the changed files and directories in a container's filesystem - -There are 3 events that are listed in the 'diff': - -1. ```A``` - Add -2. ```D``` - Delete -3. ```C``` - Change - -For example: - -.. code-block:: bash - - $ sudo docker diff 7bb0e258aefe - - C /dev - A /dev/kmsg - C /etc - A /etc/mtab - A /go - A /go/src - A /go/src/github.com - A /go/src/github.com/dotcloud - A /go/src/github.com/dotcloud/docker - A /go/src/github.com/dotcloud/docker/.git - .... - -.. _cli_events: - -``events`` ----------- - -:: - - Usage: docker events - - Get real time events from the server - - --since="": Show previously created events and then stream. - (either seconds since epoch, or date string as below) - -.. _cli_events_example: - -Examples -~~~~~~~~ - -You'll need two shells for this example. - -Shell 1: Listening for events -............................. - -.. code-block:: bash - - $ sudo docker events - -Shell 2: Start and Stop a Container -................................... - -.. code-block:: bash - - $ sudo docker start 4386fb97867d - $ sudo docker stop 4386fb97867d - -Shell 1: (Again .. now showing events) -...................................... - -.. code-block:: bash - - [2013-09-03 15:49:26 +0200 CEST] 4386fb97867d: (from 12de384bfb10) start - [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die - [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop - -Show events in the past from a specified time -............................................. - -.. code-block:: bash - - $ sudo docker events --since 1378216169 - [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die - [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop - - $ sudo docker events --since '2013-09-03' - [2013-09-03 15:49:26 +0200 CEST] 4386fb97867d: (from 12de384bfb10) start - [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die - [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop - - $ sudo docker events --since '2013-09-03 15:49:29 +0200 CEST' - [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die - [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop - -.. _cli_export: - -``export`` ----------- - -:: - - Usage: docker export CONTAINER - - Export the contents of a filesystem as a tar archive to STDOUT - -For example: - -.. code-block:: bash - - $ sudo docker export red_panda > latest.tar - -.. _cli_history: - -``history`` ------------ - -:: - - Usage: docker history [OPTIONS] IMAGE - - Show the history of an image - - --no-trunc=false: Don't truncate output - -q, --quiet=false: Only show numeric IDs - -To see how the ``docker:latest`` image was built: - -.. code-block:: bash - - $ docker history docker - IMAGE CREATED CREATED BY SIZE - 3e23a5875458790b7a806f95f7ec0d0b2a5c1659bfc899c89f939f6d5b8f7094 8 days ago /bin/sh -c #(nop) ENV LC_ALL=C.UTF-8 0 B - 8578938dd17054dce7993d21de79e96a037400e8d28e15e7290fea4f65128a36 8 days ago /bin/sh -c dpkg-reconfigure locales && locale-gen C.UTF-8 && /usr/sbin/update-locale LANG=C.UTF-8 1.245 MB - be51b77efb42f67a5e96437b3e102f81e0a1399038f77bf28cea0ed23a65cf60 8 days ago /bin/sh -c apt-get update && apt-get install -y git libxml2-dev python build-essential make gcc python-dev locales python-pip 338.3 MB - 4b137612be55ca69776c7f30c2d2dd0aa2e7d72059820abf3e25b629f887a084 6 weeks ago /bin/sh -c #(nop) ADD jessie.tar.xz in / 121 MB - 750d58736b4b6cc0f9a9abe8f258cef269e3e9dceced1146503522be9f985ada 6 weeks ago /bin/sh -c #(nop) MAINTAINER Tianon Gravi - mkimage-debootstrap.sh -t jessie.tar.xz jessie http://http.debian.net/debian 0 B - 511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158 9 months ago 0 B - -.. _cli_images: - -``images`` ----------- - -:: - - Usage: docker images [OPTIONS] [NAME] - - List images - - -a, --all=false: Show all images (by default filter out the intermediate images used to build) - -f, --filter=[]: Provide filter values (i.e. 'tagged=false') - --no-trunc=false: Don't truncate output - -q, --quiet=false: Only show numeric IDs - -t, --tree=false: Output graph in tree format - -v, --viz=false: Output graph in graphviz format - - -Listing the most recently created images -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: bash - - $ sudo docker images | head - REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE - 77af4d6b9913 19 hours ago 1.089 GB - committest latest b6fa739cedf5 19 hours ago 1.089 GB - 78a85c484f71 19 hours ago 1.089 GB - docker latest 30557a29d5ab 20 hours ago 1.089 GB - 0124422dd9f9 20 hours ago 1.089 GB - 18ad6fad3402 22 hours ago 1.082 GB - f9f1e26352f0 23 hours ago 1.089 GB - tryout latest 2629d1fa0b81 23 hours ago 131.5 MB - 5ed6274db6ce 24 hours ago 1.089 GB - -Listing the full length image IDs -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: bash - - $ sudo docker images --no-trunc | head - REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE - 77af4d6b9913e693e8d0b4b294fa62ade6054e6b2f1ffb617ac955dd63fb0182 19 hours ago 1.089 GB - committest latest b6fa739cedf5ea12a620a439402b6004d057da800f91c7524b5086a5e4749c9f 19 hours ago 1.089 GB - 78a85c484f71509adeaace20e72e941f6bdd2b25b4c75da8693efd9f61a37921 19 hours ago 1.089 GB - docker latest 30557a29d5abc51e5f1d5b472e79b7e296f595abcf19fe6b9199dbbc809c6ff4 20 hours ago 1.089 GB - 0124422dd9f9cf7ef15c0617cda3931ee68346455441d66ab8bdc5b05e9fdce5 20 hours ago 1.089 GB - 18ad6fad340262ac2a636efd98a6d1f0ea775ae3d45240d3418466495a19a81b 22 hours ago 1.082 GB - f9f1e26352f0a3ba6a0ff68167559f64f3e21ff7ada60366e2d44a04befd1d3a 23 hours ago 1.089 GB - tryout latest 2629d1fa0b81b222fca63371ca16cbf6a0772d07759ff80e8d1369b926940074 23 hours ago 131.5 MB - 5ed6274db6ceb2397844896966ea239290555e74ef307030ebb01ff91b1914df 24 hours ago 1.089 GB - -Displaying images visually -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: bash - - $ sudo docker images --viz | dot -Tpng -o docker.png - -.. image:: docker_images.gif - :alt: Example inheritance graph of Docker images. - - -Displaying image hierarchy -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: bash - - $ sudo docker images --tree - - ├─8dbd9e392a96 Size: 131.5 MB (virtual 131.5 MB) Tags: ubuntu:12.04,ubuntu:latest,ubuntu:precise - └─27cf78414709 Size: 180.1 MB (virtual 180.1 MB) - └─b750fe79269d Size: 24.65 kB (virtual 180.1 MB) Tags: ubuntu:12.10,ubuntu:quantal - ├─f98de3b610d5 Size: 12.29 kB (virtual 180.1 MB) - │ └─7da80deb7dbf Size: 16.38 kB (virtual 180.1 MB) - │ └─65ed2fee0a34 Size: 20.66 kB (virtual 180.2 MB) - │ └─a2b9ea53dddc Size: 819.7 MB (virtual 999.8 MB) - │ └─a29b932eaba8 Size: 28.67 kB (virtual 999.9 MB) - │ └─e270a44f124d Size: 12.29 kB (virtual 999.9 MB) Tags: progrium/buildstep:latest - └─17e74ac162d8 Size: 53.93 kB (virtual 180.2 MB) - └─339a3f56b760 Size: 24.65 kB (virtual 180.2 MB) - └─904fcc40e34d Size: 96.7 MB (virtual 276.9 MB) - └─b1b0235328dd Size: 363.3 MB (virtual 640.2 MB) - └─7cb05d1acb3b Size: 20.48 kB (virtual 640.2 MB) - └─47bf6f34832d Size: 20.48 kB (virtual 640.2 MB) - └─f165104e82ed Size: 12.29 kB (virtual 640.2 MB) - └─d9cf85a47b7e Size: 1.911 MB (virtual 642.2 MB) - └─3ee562df86ca Size: 17.07 kB (virtual 642.2 MB) - └─b05fc2d00e4a Size: 24.96 kB (virtual 642.2 MB) - └─c96a99614930 Size: 12.29 kB (virtual 642.2 MB) - └─a6a357a48c49 Size: 12.29 kB (virtual 642.2 MB) Tags: ndj/mongodb:latest - - -Filtering -~~~~~~~~~ - -The filtering flag (-f or --filter) format is of "key=value". If there are more -than one flag, then either semi-colon delimit (";"), or pass multiple flags. - -Current filters: - * untagged (boolean - true or false) - - - -Filtering for untagged images -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: bash - - $ sudo docker images --filter "untagged=true" - - REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE - 8abc22fbb042 4 weeks ago 0 B - 48e5f45168b9 4 weeks ago 2.489 MB - bf747efa0e2f 4 weeks ago 0 B - 980fe10e5736 12 weeks ago 101.4 MB - dea752e4e117 12 weeks ago 101.4 MB - 511136ea3c5a 8 months ago 0 B - -This will display untagged images, that are the leaves of the images tree (not -intermediary layers). These images occur when a new build of an image takes the -repo:tag away from the IMAGE ID, leaving it untagged. A warning will be issued -if trying to remove an image when a container is presently using it. -By having this flag it allows for batch cleanup. - - -.. code-block:: bash - - $ sudo docker images -f "untagged=true" -q - - 8abc22fbb042 - 48e5f45168b9 - bf747efa0e2f - 980fe10e5736 - dea752e4e117 - 511136ea3c5a - - -Ready for use by `docker rmi ...`, like: - -.. code-block:: bash - - $ sudo docker rmi $(sudo docker images -f "untagged=true" -q) - - 8abc22fbb042 - 48e5f45168b9 - bf747efa0e2f - 980fe10e5736 - dea752e4e117 - 511136ea3c5a - -NOTE: Docker will warn you if any containers exist that are using these untagged images. - -.. _cli_import: - -``import`` ----------- - -:: - - Usage: docker import URL|- [REPOSITORY[:TAG]] - - Create an empty filesystem image and import the contents of the tarball - (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then optionally tag it. - -At this time, the URL must start with ``http`` and point to a single -file archive (.tar, .tar.gz, .tgz, .bzip, .tar.xz, or .txz) containing a -root filesystem. If you would like to import from a local directory or -archive, you can use the ``-`` parameter to take the data from *stdin*. - -Examples -~~~~~~~~ - -Import from a remote location -............................. - -This will create a new untagged image. - -.. code-block:: bash - - $ sudo docker import http://example.com/exampleimage.tgz - -Import from a local file -........................ - -Import to docker via pipe and *stdin*. - -.. code-block:: bash - - $ cat exampleimage.tgz | sudo docker import - exampleimagelocal:new - -Import from a local directory -............................. - -.. code-block:: bash - - $ sudo tar -c . | docker import - exampleimagedir - -Note the ``sudo`` in this example -- you must preserve the ownership of the -files (especially root ownership) during the archiving with tar. If you are not -root (or the sudo command) when you tar, then the ownerships might not get -preserved. - -.. _cli_info: - -``info`` --------- - -:: - - Usage: docker info - - Display system-wide information. - -.. code-block:: bash - - $ sudo docker info - Containers: 292 - Images: 194 - Debug mode (server): false - Debug mode (client): false - Fds: 22 - Goroutines: 67 - LXC Version: 0.9.0 - EventsListeners: 115 - Kernel Version: 3.8.0-33-generic - WARNING: No swap limit support - - -.. _cli_insert: - -``insert`` ----------- - -:: - - Usage: docker insert IMAGE URL PATH - - Insert a file from URL in the IMAGE at PATH - -Use the specified ``IMAGE`` as the parent for a new image which adds a -:ref:`layer ` containing the new file. The ``insert`` command does -not modify the original image, and the new image has the contents of the parent -image, plus the new file. - - -Examples -~~~~~~~~ - -Insert file from GitHub -....................... - -.. code-block:: bash - - $ sudo docker insert 8283e18b24bc https://raw.github.com/metalivedev/django/master/postinstall /tmp/postinstall.sh - 06fd35556d7b - -.. _cli_inspect: - -``inspect`` ------------ - -:: - - Usage: docker inspect CONTAINER|IMAGE [CONTAINER|IMAGE...] - - Return low-level information on a container/image - - -f, --format="": Format the output using the given go template. - -By default, this will render all results in a JSON array. If a format -is specified, the given template will be executed for each result. - -Go's `text/template `_ package -describes all the details of the format. - -Examples -~~~~~~~~ - -Get an instance's IP Address -............................ - -For the most part, you can pick out any field from the JSON in a -fairly straightforward manner. - -.. code-block:: bash - - $ sudo docker inspect --format='{{.NetworkSettings.IPAddress}}' $INSTANCE_ID - -List All Port Bindings -...................... - -One can loop over arrays and maps in the results to produce simple -text output: - -.. code-block:: bash - - $ sudo docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}} {{$p}} -> {{(index $conf 0).HostPort}} {{end}}' $INSTANCE_ID - -Find a Specific Port Mapping -............................ - -The ``.Field`` syntax doesn't work when the field name begins with a -number, but the template language's ``index`` function does. The -``.NetworkSettings.Ports`` section contains a map of the internal port -mappings to a list of external address/port objects, so to grab just -the numeric public port, you use ``index`` to find the specific port -map, and then ``index`` 0 contains first object inside of that. Then -we ask for the ``HostPort`` field to get the public address. - -.. code-block:: bash - - $ sudo docker inspect --format='{{(index (index .NetworkSettings.Ports "8787/tcp") 0).HostPort}}' $INSTANCE_ID - -Get config -.......... - -The ``.Field`` syntax doesn't work when the field contains JSON data, -but the template language's custom ``json`` function does. The ``.config`` -section contains complex json object, so to grab it as JSON, you use ``json`` -to convert config object into JSON - -.. code-block:: bash - - $ sudo docker inspect --format='{{json .config}}' $INSTANCE_ID - - -.. _cli_kill: - -``kill`` --------- - -:: - - Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...] - - Kill a running container (send SIGKILL, or specified signal) - - -s, --signal="KILL": Signal to send to the container - -The main process inside the container will be sent SIGKILL, or any signal specified with option ``--signal``. - -Known Issues (kill) -~~~~~~~~~~~~~~~~~~~ - -* :issue:`197` indicates that ``docker kill`` may leave directories - behind and make it difficult to remove the container. -* :issue:`3844` lxc 1.0.0 beta3 removed ``lcx-kill`` which is used by Docker versions before 0.8.0; - see the issue for a workaround. - -.. _cli_load: - -``load`` --------- - -:: - - Usage: docker load - - Load an image from a tar archive on STDIN - - -i, --input="": Read from a tar archive file, instead of STDIN - -Loads a tarred repository from a file or the standard input stream. -Restores both images and tags. - -.. code-block:: bash - - $ sudo docker images - REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE - $ sudo docker load < busybox.tar - $ sudo docker images - REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE - busybox latest 769b9341d937 7 weeks ago 2.489 MB - $ sudo docker load --input fedora.tar - $ sudo docker images - REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE - busybox latest 769b9341d937 7 weeks ago 2.489 MB - fedora rawhide 0d20aec6529d 7 weeks ago 387 MB - fedora 20 58394af37342 7 weeks ago 385.5 MB - fedora heisenbug 58394af37342 7 weeks ago 385.5 MB - fedora latest 58394af37342 7 weeks ago 385.5 MB - - -.. _cli_login: - -``login`` ---------- - -:: - - Usage: docker login [OPTIONS] [SERVER] - - Register or Login to the docker registry server - - -e, --email="": Email - -p, --password="": Password - -u, --username="": Username - - If you want to login to a private registry you can - specify this by adding the server name. - - example: - docker login localhost:8080 - - -.. _cli_logs: - -``logs`` --------- - -:: - - Usage: docker logs [OPTIONS] CONTAINER - - Fetch the logs of a container - - -f, --follow=false: Follow log output - -The ``docker logs`` command is a convenience which batch-retrieves whatever -logs are present at the time of execution. This does not guarantee execution -order when combined with a ``docker run`` (i.e. your run may not have generated -any logs at the time you execute ``docker logs``). - -The ``docker logs --follow`` command combines ``docker logs`` and ``docker attach``: -it will first return all logs from the beginning and then continue streaming -new output from the container's stdout and stderr. - - -.. _cli_port: - -``port`` --------- - -:: - - Usage: docker port [OPTIONS] CONTAINER PRIVATE_PORT - - Lookup the public-facing port which is NAT-ed to PRIVATE_PORT - - -.. _cli_ps: - -``ps`` ------- - -:: - - Usage: docker ps [OPTIONS] - - List containers - - -a, --all=false: Show all containers. Only running containers are shown by default. - --before="": Show only container created before Id or Name, include non-running ones. - -l, --latest=false: Show only the latest created container, include non-running ones. - -n=-1: Show n last created containers, include non-running ones. - --no-trunc=false: Don't truncate output - -q, --quiet=false: Only display numeric IDs - -s, --size=false: Display sizes, not to be used with -q - --since="": Show only containers created since Id or Name, include non-running ones. - - -Running ``docker ps`` showing 2 linked containers. - -.. code-block:: bash - - $ docker ps - CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES - 4c01db0b339c ubuntu:12.04 bash 17 seconds ago Up 16 seconds webapp - d7886598dbe2 crosbymichael/redis:latest /redis-server --dir 33 minutes ago Up 33 minutes 6379/tcp redis,webapp/db - fd2645e2e2b5 busybox:latest top 10 days ago Ghost insane_ptolemy - -The last container is marked as a ``Ghost`` container. It is a container that was running when the docker daemon was restarted (upgraded, or ``-H`` settings changed). The container is still running, but as this docker daemon process is not able to manage it, you can't attach to it. To bring them out of ``Ghost`` Status, you need to use ``docker kill`` or ``docker restart``. - -``docker ps`` will show only running containers by default. To see all containers: ``docker ps -a`` - -.. _cli_pull: - -``pull`` --------- - -:: - - Usage: docker pull NAME - - Pull an image or a repository from the registry - - -t, --tag="": Download tagged image in repository - - -.. _cli_push: - -``push`` --------- - -:: - - Usage: docker push NAME - - Push an image or a repository to the registry - - -.. _cli_restart: - -``restart`` ------------ - -:: - - Usage: docker restart [OPTIONS] NAME - - Restart a running container - - -t, --time=10: Number of seconds to try to stop for before killing the container. Once killed it will then be restarted. Default=10 - -.. _cli_rm: - -``rm`` ------- - -:: - - Usage: docker rm [OPTIONS] CONTAINER - - Remove one or more containers - -l, --link="": Remove the link instead of the actual container - -f, --force=false: Force removal of running container - -v, --volumes=false: Remove the volumes associated to the container - -Known Issues (rm) -~~~~~~~~~~~~~~~~~ - -* :issue:`197` indicates that ``docker kill`` may leave directories - behind and make it difficult to remove the container. - - -Examples: -~~~~~~~~~ - -.. code-block:: bash - - $ sudo docker rm /redis - /redis - - -This will remove the container referenced under the link ``/redis``. - - -.. code-block:: bash - - $ sudo docker rm --link /webapp/redis - /webapp/redis - - -This will remove the underlying link between ``/webapp`` and the ``/redis`` containers removing all -network communication. - -.. code-block:: bash - - $ sudo docker rm `docker ps -a -q` - - -This command will delete all stopped containers. The command ``docker ps -a -q`` will return all -existing container IDs and pass them to the ``rm`` command which will delete them. Any running -containers will not be deleted. - -.. _cli_rmi: - -``rmi`` -------- - -:: - - Usage: docker rmi IMAGE [IMAGE...] - - Remove one or more images - - -f, --force=false: Force - --no-prune=false: Do not delete untagged parents - -Removing tagged images -~~~~~~~~~~~~~~~~~~~~~~ - -Images can be removed either by their short or long ID's, or their image names. -If an image has more than one name, each of them needs to be removed before the -image is removed. - -.. code-block:: bash - - $ sudo docker images - REPOSITORY TAG IMAGE ID CREATED SIZE - test1 latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB) - test latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB) - test2 latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB) - - $ sudo docker rmi fd484f19954f - Error: Conflict, cannot delete image fd484f19954f because it is tagged in multiple repositories - 2013/12/11 05:47:16 Error: failed to remove one or more images - - $ sudo docker rmi test1 - Untagged: fd484f19954f4920da7ff372b5067f5b7ddb2fd3830cecd17b96ea9e286ba5b8 - $ sudo docker rmi test2 - Untagged: fd484f19954f4920da7ff372b5067f5b7ddb2fd3830cecd17b96ea9e286ba5b8 - - $ sudo docker images - REPOSITORY TAG IMAGE ID CREATED SIZE - test1 latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB) - $ sudo docker rmi test - Untagged: fd484f19954f4920da7ff372b5067f5b7ddb2fd3830cecd17b96ea9e286ba5b8 - Deleted: fd484f19954f4920da7ff372b5067f5b7ddb2fd3830cecd17b96ea9e286ba5b8 - - -.. _cli_run: - -``run`` -------- - -:: - - Usage: docker run [OPTIONS] IMAGE[:TAG] [COMMAND] [ARG...] - - Run a command in a new container - - -a, --attach=map[]: Attach to stdin, stdout or stderr - -c, --cpu-shares=0: CPU shares (relative weight) - --cidfile="": Write the container ID to the file - -d, --detach=false: Detached mode: Run container in the background, print new container id - -e, --env=[]: Set environment variables - -h, --hostname="": Container host name - -i, --interactive=false: Keep stdin open even if not attached - --privileged=false: Give extended privileges to this container - -m, --memory="": Memory limit (format: , where unit = b, k, m or g) - -n, --networking=true: Enable networking for this container - -p, --publish=[]: Map a network port to the container - --rm=false: Automatically remove the container when it exits (incompatible with -d) - -t, --tty=false: Allocate a pseudo-tty - -u, --user="": Username or UID - --dns=[]: Set custom dns servers for the container - --dns-search=[]: Set custom DNS search domains for the container - -v, --volume=[]: Create a bind mount to a directory or file with: [host-path]:[container-path]:[rw|ro]. If a directory "container-path" is missing, then docker creates a new volume. - --volumes-from="": Mount all volumes from the given container(s) - --entrypoint="": Overwrite the default entrypoint set by the image - -w, --workdir="": Working directory inside the container - --lxc-conf=[]: (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" - --sig-proxy=true: Proxify all received signal to the process (even in non-tty mode) - --expose=[]: Expose a port from the container without publishing it to your host - --link="": Add link to another container (name:alias) - --name="": Assign the specified name to the container. If no name is specific docker will generate a random name - -P, --publish-all=false: Publish all exposed ports to the host interfaces - -The ``docker run`` command first ``creates`` a writeable container layer over -the specified image, and then ``starts`` it using the specified command. That -is, ``docker run`` is equivalent to the API ``/containers/create`` then -``/containers/(id)/start``. -Once the container is stopped it still exists and can be started back up. See ``docker ps -a`` to view a list of all containers. - -The ``docker run`` command can be used in combination with ``docker commit`` to -:ref:`change the command that a container runs `. - -See :ref:`port_redirection` for more detailed information about the ``--expose``, -``-p``, ``-P`` and ``--link`` parameters, and :ref:`working_with_links_names` for -specific examples using ``--link``. - -Known Issues (run --volumes-from) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* :issue:`2702`: "lxc-start: Permission denied - failed to mount" - could indicate a permissions problem with AppArmor. Please see the - issue for a workaround. - -Examples: -~~~~~~~~~ - -.. code-block:: bash - - $ sudo docker run --cidfile /tmp/docker_test.cid ubuntu echo "test" - -This will create a container and print ``test`` to the console. The -``cidfile`` flag makes Docker attempt to create a new file and write the -container ID to it. If the file exists already, Docker will return an -error. Docker will close this file when ``docker run`` exits. - -.. code-block:: bash - - $ sudo docker run -t -i --rm ubuntu bash - root@bc338942ef20:/# mount -t tmpfs none /mnt - mount: permission denied - - -This will *not* work, because by default, most potentially dangerous -kernel capabilities are dropped; including ``cap_sys_admin`` (which is -required to mount filesystems). However, the ``--privileged`` flag will -allow it to run: - -.. code-block:: bash - - $ sudo docker run --privileged ubuntu bash - root@50e3f57e16e6:/# mount -t tmpfs none /mnt - root@50e3f57e16e6:/# df -h - Filesystem Size Used Avail Use% Mounted on - none 1.9G 0 1.9G 0% /mnt - - -The ``--privileged`` flag gives *all* capabilities to the container, -and it also lifts all the limitations enforced by the ``device`` -cgroup controller. In other words, the container can then do almost -everything that the host can do. This flag exists to allow special -use-cases, like running Docker within Docker. - -.. code-block:: bash - - $ sudo docker run -w /path/to/dir/ -i -t ubuntu pwd - -The ``-w`` lets the command being executed inside directory given, -here ``/path/to/dir/``. If the path does not exists it is created inside the -container. - -.. code-block:: bash - - $ sudo docker run -v `pwd`:`pwd` -w `pwd` -i -t ubuntu pwd - -The ``-v`` flag mounts the current working directory into the container. -The ``-w`` lets the command being executed inside the current -working directory, by changing into the directory to the value -returned by ``pwd``. So this combination executes the command -using the container, but inside the current working directory. - -.. code-block:: bash - - $ sudo docker run -v /doesnt/exist:/foo -w /foo -i -t ubuntu bash - -When the host directory of a bind-mounted volume doesn't exist, Docker -will automatically create this directory on the host for you. In the -example above, Docker will create the ``/doesnt/exist`` folder before -starting your container. - -.. code-block:: bash - - $ sudo docker run -t -i -v /var/run/docker.sock:/var/run/docker.sock -v ./static-docker:/usr/bin/docker busybox sh - -By bind-mounting the docker unix socket and statically linked docker binary -(such as that provided by https://get.docker.io), you give the container -the full access to create and manipulate the host's docker daemon. - -.. code-block:: bash - - $ sudo docker run -p 127.0.0.1:80:8080 ubuntu bash - -This binds port ``8080`` of the container to port ``80`` on ``127.0.0.1`` of the -host machine. :ref:`port_redirection` explains in detail how to manipulate ports -in Docker. - -.. code-block:: bash - - $ sudo docker run --expose 80 ubuntu bash - -This exposes port ``80`` of the container for use within a link without -publishing the port to the host system's interfaces. :ref:`port_redirection` -explains in detail how to manipulate ports in Docker. - -.. code-block:: bash - - $ sudo docker run --name console -t -i ubuntu bash - -This will create and run a new container with the container name -being ``console``. - -.. code-block:: bash - - $ sudo docker run --link /redis:redis --name console ubuntu bash - -The ``--link`` flag will link the container named ``/redis`` into the -newly created container with the alias ``redis``. The new container -can access the network and environment of the redis container via -environment variables. The ``--name`` flag will assign the name ``console`` -to the newly created container. - -.. code-block:: bash - - $ sudo docker run --volumes-from 777f7dc92da7,ba8c0c54f0f2:ro -i -t ubuntu pwd - -The ``--volumes-from`` flag mounts all the defined volumes from the -referenced containers. Containers can be specified by a comma separated -list or by repetitions of the ``--volumes-from`` argument. The container -ID may be optionally suffixed with ``:ro`` or ``:rw`` to mount the volumes in -read-only or read-write mode, respectively. By default, the volumes are mounted -in the same mode (read write or read only) as the reference container. - -A complete example -.................. - -.. code-block:: bash - - $ sudo docker run -d --name static static-web-files sh - $ sudo docker run -d --expose=8098 --name riak riakserver - $ sudo docker run -d -m 100m -e DEVELOPMENT=1 -e BRANCH=example-code -v $(pwd):/app/bin:ro --name app appserver - $ sudo docker run -d -p 1443:443 --dns=dns.dev.org --dns-search=dev.org -v /var/log/httpd --volumes-from static --link riak --link app -h www.sven.dev.org --name web webserver - $ sudo docker run -t -i --rm --volumes-from web -w /var/log/httpd busybox tail -f access.log - -This example shows 5 containers that might be set up to test a web application change: - -1. Start a pre-prepared volume image ``static-web-files`` (in the background) that has CSS, image and static HTML in it, (with a ``VOLUME`` instruction in the ``Dockerfile`` to allow the web server to use those files); -2. Start a pre-prepared ``riakserver`` image, give the container name ``riak`` and expose port ``8098`` to any containers that link to it; -3. Start the ``appserver`` image, restricting its memory usage to 100MB, setting two environment variables ``DEVELOPMENT`` and ``BRANCH`` and bind-mounting the current directory (``$(pwd)``) in the container in read-only mode as ``/app/bin``; -4. Start the ``webserver``, mapping port ``443`` in the container to port ``1443`` on the Docker server, setting the DNS server to ``dns.dev.org`` and DNS search domain to ``dev.org``, creating a volume to put the log files into (so we can access it from another container), then importing the files from the volume exposed by the ``static`` container, and linking to all exposed ports from ``riak`` and ``app``. Lastly, we set the hostname to ``web.sven.dev.org`` so its consistent with the pre-generated SSL certificate; -5. Finally, we create a container that runs ``tail -f access.log`` using the logs volume from the ``web`` container, setting the workdir to ``/var/log/httpd``. The ``--rm`` option means that when the container exits, the container's layer is removed. - - -.. _cli_save: - -``save`` ---------- - -:: - - Usage: docker save IMAGE - - Save an image to a tar archive (streamed to stdout by default) - - -o, --output="": Write to an file, instead of STDOUT - - -Produces a tarred repository to the standard output stream. -Contains all parent layers, and all tags + versions, or specified repo:tag. - -.. code-block:: bash - - $ sudo docker save busybox > busybox.tar - $ ls -sh b.tar - 2.7M b.tar - $ sudo docker save --output busybox.tar busybox - $ ls -sh b.tar - 2.7M b.tar - $ sudo docker save -o fedora-all.tar fedora - $ sudo docker save -o fedora-latest.tar fedora:latest - - -.. _cli_search: - -``search`` ----------- - -:: - - Usage: docker search TERM - - Search the docker index for images - - --no-trunc=false: Don't truncate output - -s, --stars=0: Only displays with at least xxx stars - -t, --trusted=false: Only show trusted builds - -.. _cli_start: - -``start`` ---------- - -:: - - Usage: docker start [OPTIONS] CONTAINER - - Start a stopped container - - -a, --attach=false: Attach container's stdout/stderr and forward all signals to the process - -i, --interactive=false: Attach container's stdin - -.. _cli_stop: - -``stop`` --------- - -:: - - Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...] - - Stop a running container (Send SIGTERM, and then SIGKILL after grace period) - - -t, --time=10: Number of seconds to wait for the container to stop before killing it. - -The main process inside the container will receive SIGTERM, and after a grace period, SIGKILL - -.. _cli_tag: - -``tag`` -------- - -:: - - Usage: docker tag [OPTIONS] IMAGE [REGISTRYHOST/][USERNAME/]NAME[:TAG] - - Tag an image into a repository - - -f, --force=false: Force - -.. _cli_top: - -``top`` -------- - -:: - - Usage: docker top CONTAINER [ps OPTIONS] - - Lookup the running processes of a container - -.. _cli_version: - -``version`` ------------ - -Show the version of the Docker client, daemon, and latest released version. - - -.. _cli_wait: - -``wait`` --------- - -:: - - Usage: docker wait [OPTIONS] NAME - - Block until a container stops, then print its exit code. From f1cc7ce5d728d6bdb1b3ab668e36fb1fc66515ff Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 19 May 2014 16:31:15 -0400 Subject: [PATCH 340/400] filter flag: split out for separate --filter flags adding tests and allowing for easy passing of filters.Args from client to server. Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- api/client/commands.go | 8 ++--- api/server/server.go | 2 +- server/server.go | 13 ++++++-- utils/filters/parse.go | 47 ++++++++++++++++------------- utils/filters/parse_test.go | 60 +++++++++++++++++++++++++++++++++++++ 5 files changed, 101 insertions(+), 29 deletions(-) create mode 100644 utils/filters/parse_test.go diff --git a/api/client/commands.go b/api/client/commands.go index d0ac62148..1dd619495 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -1160,10 +1160,10 @@ func (cli *DockerCli) CmdImages(args ...string) error { // Consolidate all filter flags, and sanity check them early. // They'll get process in the daemon/server. - imageFilters := map[string]string{} + imageFilterArgs := filters.Args{} for _, f := range flFilter.GetAll() { var err error - imageFilters, err = filters.ParseFlag(f, imageFilters) + imageFilterArgs, err = filters.ParseFlag(f, imageFilterArgs) if err != nil { return err } @@ -1174,7 +1174,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { if *flViz || *flTree { v := url.Values{ "all": []string{"1"}, - "filters": []string{filters.ToParam(imageFilters)}, + "filters": []string{filters.ToParam(imageFilterArgs)}, } body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false)) if err != nil { @@ -1238,7 +1238,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { } } else { v := url.Values{ - "filters": []string{filters.ToParam(imageFilters)}, + "filters": []string{filters.ToParam(imageFilterArgs)}, } if cmd.NArg() == 1 { // FIXME rename this parameter, to not be confused with the filters flag diff --git a/api/server/server.go b/api/server/server.go index 0bed58226..43b2c6232 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -189,7 +189,7 @@ func getImagesJSON(eng *engine.Engine, version version.Version, w http.ResponseW ) job.Setenv("filters", r.Form.Get("filters")) - // FIXME rename this parameter, to not be confused with the filters flag + // FIXME this parameter could just be a match filter job.Setenv("filter", r.Form.Get("filter")) job.Setenv("all", r.Form.Get("all")) diff --git a/server/server.go b/server/server.go index dd54ab67d..0495b0ad5 100644 --- a/server/server.go +++ b/server/server.go @@ -700,12 +700,19 @@ func (srv *Server) Images(job *engine.Job) engine.Status { filt_tagged = true ) - imageFilters, err := filters.ParseFlag(job.Getenv("filters"), nil) + utils.Debugf("SUCH JOB: %#v", job) + utils.Debugf("SUCH ENV: %#v", *job.Env()) + imageFilters, err := filters.FromParam(job.Getenv("filters")) if err != nil { return job.Error(err) } - if i, ok := imageFilters["untagged"]; ok && strings.ToLower(i) == "true" { - filt_tagged = false + utils.Debugf("SUCH FILTERS: %#v", imageFilters) + if i, ok := imageFilters["untagged"]; ok { + for _, value := range i { + if strings.ToLower(value) == "true" { + filt_tagged = false + } + } } if job.GetenvBool("all") && !filt_tagged { diff --git a/utils/filters/parse.go b/utils/filters/parse.go index a813be9ff..1eb46830a 100644 --- a/utils/filters/parse.go +++ b/utils/filters/parse.go @@ -2,46 +2,51 @@ package filters import ( "errors" + "github.com/dotcloud/docker/pkg/beam/data" "strings" ) +type Args map[string][]string + /* Parse the argument to the filter flag. Like - `docker ps -f 'created=today;image.name=ubuntu*'` - -Filters delimited by ';', and expected to be 'name=value' + `docker ps -f 'created=today' -f 'image.name=ubuntu*'` If prev map is provided, then it is appended to, and returned. By default a new map is created. */ -func ParseFlag(arg string, prev map[string]string) (map[string]string, error) { - var filters map[string]string - if prev != nil { - filters = prev - } else { - filters = map[string]string{} +func ParseFlag(arg string, prev Args) (Args, error) { + var filters Args = prev + if prev == nil { + filters = Args{} } if len(arg) == 0 { return filters, nil } - for _, chunk := range strings.Split(arg, ";") { - if !strings.Contains(chunk, "=") { - return filters, ErrorBadFormat - } - f := strings.SplitN(chunk, "=", 2) - filters[f[0]] = f[1] + if !strings.Contains(arg, "=") { + return filters, ErrorBadFormat } + + f := strings.SplitN(arg, "=", 2) + filters[f[0]] = append(filters[f[0]], f[1]) + return filters, nil } var ErrorBadFormat = errors.New("bad format of filter (expected name=value)") -func ToParam(f map[string]string) string { - fs := []string{} - for k, v := range f { - fs = append(fs, k+"="+v) - } - return strings.Join(fs, ";") +/* +packs the Args into an string for easy transport from client to server +*/ +func ToParam(a Args) string { + return data.Encode(a) +} + +/* +unpacks the filter Args +*/ +func FromParam(p string) (Args, error) { + return data.Decode(p) } diff --git a/utils/filters/parse_test.go b/utils/filters/parse_test.go new file mode 100644 index 000000000..c24fe3949 --- /dev/null +++ b/utils/filters/parse_test.go @@ -0,0 +1,60 @@ +package filters + +import ( + "sort" + "testing" +) + +func TestParseArgs(t *testing.T) { + // equivalent of `docker ps -f 'created=today' -f 'image.name=ubuntu*' -f 'image.name=*untu'` + flagArgs := []string{ + "created=today", + "image.name=ubuntu*", + "image.name=*untu", + } + var ( + args = Args{} + err error + ) + for i := range flagArgs { + args, err = ParseFlag(flagArgs[i], args) + if err != nil { + t.Errorf("failed to parse %s: %s", flagArgs[i], err) + } + } + if len(args["created"]) != 1 { + t.Errorf("failed to set this arg") + } + if len(args["image.name"]) != 2 { + t.Errorf("the args should have collapsed") + } +} + +func TestParam(t *testing.T) { + a := Args{ + "created": []string{"today"}, + "image.name": []string{"ubuntu*", "*untu"}, + } + + v := ToParam(a) + v1, err := FromParam(v) + if err != nil { + t.Errorf("%s", err) + } + for key, vals := range v1 { + if _, ok := a[key]; !ok { + t.Errorf("could not find key %s in original set", key) + } + sort.Strings(vals) + sort.Strings(a[key]) + if len(vals) != len(a[key]) { + t.Errorf("value lengths ought to match") + continue + } + for i := range vals { + if vals[i] != a[key][i] { + t.Errorf("expected %s, but got %s", a[key][i], vals[i]) + } + } + } +} From f322168563dee9d0361524d15c6a5069772b44f8 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 21 May 2014 17:53:58 -0400 Subject: [PATCH 341/400] server: very debug. Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- server/server.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/server/server.go b/server/server.go index 0495b0ad5..967129b90 100644 --- a/server/server.go +++ b/server/server.go @@ -700,13 +700,10 @@ func (srv *Server) Images(job *engine.Job) engine.Status { filt_tagged = true ) - utils.Debugf("SUCH JOB: %#v", job) - utils.Debugf("SUCH ENV: %#v", *job.Env()) imageFilters, err := filters.FromParam(job.Getenv("filters")) if err != nil { return job.Error(err) } - utils.Debugf("SUCH FILTERS: %#v", imageFilters) if i, ok := imageFilters["untagged"]; ok { for _, value := range i { if strings.ToLower(value) == "true" { From 3ecfaa8f2da60bb4e6ce6c9ae0428bc7b647e34b Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 21 May 2014 18:44:31 -0400 Subject: [PATCH 342/400] filters: change untagged to dangling Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- api/client/commands.go | 2 +- docs/sources/reference/commandline/cli.md | 8 ++++---- server/server.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index 1dd619495..00dd761b1 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -1148,7 +1148,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { flTree := cmd.Bool([]string{"#t", "#tree", "#-tree"}, false, "Output graph in tree format") var flFilter opts.ListOpts - cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'tagged=false')") + cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'dangling=true')") if err := cmd.Parse(args); err != nil { return nil diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 0c3623f9e..829f13b9a 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -440,7 +440,7 @@ To see how the `docker:latest` image was built: List images -a, --all=false Show all images (by default filter out the intermediate image layers) - -f, --filter=[]: Provide filter values (i.e. 'tagged=false') + -f, --filter=[]: Provide filter values (i.e. 'dangling=true') --no-trunc=false Don't truncate output -q, --quiet=false Only show numeric IDs @@ -486,11 +486,11 @@ The filtering flag (-f or --filter) format is of "key=value". If there are more than one filter, then pass multiple flags (e.g. `--filter "foo=bar" --filter "bif=baz"`) Current filters: - * untagged (boolean - true or false) + * dangling (boolean - true or false) #### untagged images - $ sudo docker images --filter "untagged=true" + $ sudo docker images --filter "dangling=true" REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE 8abc22fbb042 4 weeks ago 0 B @@ -508,7 +508,7 @@ By having this flag it allows for batch cleanup. Ready for use by `docker rmi ...`, like: - $ sudo docker rmi $(sudo docker images -f "untagged=true" -q) + $ sudo docker rmi $(sudo docker images -f "dangling=true" -q) 8abc22fbb042 48e5f45168b9 diff --git a/server/server.go b/server/server.go index 967129b90..8c70842a4 100644 --- a/server/server.go +++ b/server/server.go @@ -704,7 +704,7 @@ func (srv *Server) Images(job *engine.Job) engine.Status { if err != nil { return job.Error(err) } - if i, ok := imageFilters["untagged"]; ok { + if i, ok := imageFilters["dangling"]; ok { for _, value := range i { if strings.ToLower(value) == "true" { filt_tagged = false From 89a15fa235f0aab6dd326a3737260c983f0fc22b Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Fri, 30 May 2014 14:59:03 -0400 Subject: [PATCH 343/400] filters: use json marshal, instead of beam/data Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- api/client/commands.go | 16 +++++++++++++--- utils/filters/parse.go | 17 +++++++++++++---- utils/filters/parse_test.go | 5 ++++- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index 00dd761b1..4e013b8fe 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -1172,10 +1172,15 @@ func (cli *DockerCli) CmdImages(args ...string) error { matchName := cmd.Arg(0) // FIXME: --viz and --tree are deprecated. Remove them in a future version. if *flViz || *flTree { + filterJson, err := filters.ToParam(imageFilterArgs) + if err != nil { + return err + } v := url.Values{ "all": []string{"1"}, - "filters": []string{filters.ToParam(imageFilterArgs)}, + "filters": []string{filterJson}, } + body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false)) if err != nil { return err @@ -1237,9 +1242,14 @@ func (cli *DockerCli) CmdImages(args ...string) error { fmt.Fprintf(cli.out, " base [style=invisible]\n}\n") } } else { - v := url.Values{ - "filters": []string{filters.ToParam(imageFilterArgs)}, + filterJson, err := filters.ToParam(imageFilterArgs) + if err != nil { + return err } + v := url.Values{ + "filters": []string{filterJson}, + } + if cmd.NArg() == 1 { // FIXME rename this parameter, to not be confused with the filters flag v.Set("filter", matchName) diff --git a/utils/filters/parse.go b/utils/filters/parse.go index 1eb46830a..c1d6936b9 100644 --- a/utils/filters/parse.go +++ b/utils/filters/parse.go @@ -1,8 +1,8 @@ package filters import ( + "encoding/json" "errors" - "github.com/dotcloud/docker/pkg/beam/data" "strings" ) @@ -40,13 +40,22 @@ var ErrorBadFormat = errors.New("bad format of filter (expected name=value)") /* packs the Args into an string for easy transport from client to server */ -func ToParam(a Args) string { - return data.Encode(a) +func ToParam(a Args) (string, error) { + buf, err := json.Marshal(a) + if err != nil { + return "", err + } + return string(buf), nil } /* unpacks the filter Args */ func FromParam(p string) (Args, error) { - return data.Decode(p) + args := Args{} + err := json.Unmarshal([]byte(p), &args) + if err != nil { + return nil, err + } + return args, nil } diff --git a/utils/filters/parse_test.go b/utils/filters/parse_test.go index c24fe3949..59c5a5d93 100644 --- a/utils/filters/parse_test.go +++ b/utils/filters/parse_test.go @@ -36,7 +36,10 @@ func TestParam(t *testing.T) { "image.name": []string{"ubuntu*", "*untu"}, } - v := ToParam(a) + v, err := ToParam(a) + if err != nil { + t.Errorf("failed to marshal the filters: %s", err) + } v1, err := FromParam(v) if err != nil { t.Errorf("%s", err) From 3a4e3ca3277fef8f1d88858fa5a3e149a897f8ae Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Fri, 30 May 2014 15:55:40 -0400 Subject: [PATCH 344/400] filters: don't encode an empty set. update comments Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- utils/filters/parse.go | 30 ++++++++++++++++-------------- utils/filters/parse_test.go | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/utils/filters/parse.go b/utils/filters/parse.go index c1d6936b9..27c7132e8 100644 --- a/utils/filters/parse.go +++ b/utils/filters/parse.go @@ -8,14 +8,12 @@ import ( type Args map[string][]string -/* -Parse the argument to the filter flag. Like - - `docker ps -f 'created=today' -f 'image.name=ubuntu*'` - -If prev map is provided, then it is appended to, and returned. By default a new -map is created. -*/ +// Parse the argument to the filter flag. Like +// +// `docker ps -f 'created=today' -f 'image.name=ubuntu*'` +// +// If prev map is provided, then it is appended to, and returned. By default a new +// map is created. func ParseFlag(arg string, prev Args) (Args, error) { var filters Args = prev if prev == nil { @@ -37,10 +35,13 @@ func ParseFlag(arg string, prev Args) (Args, error) { var ErrorBadFormat = errors.New("bad format of filter (expected name=value)") -/* -packs the Args into an string for easy transport from client to server -*/ +// packs the Args into an string for easy transport from client to server func ToParam(a Args) (string, error) { + // this way we don't URL encode {}, just empty space + if len(a) == 0 { + return "", nil + } + buf, err := json.Marshal(a) if err != nil { return "", err @@ -48,11 +49,12 @@ func ToParam(a Args) (string, error) { return string(buf), nil } -/* -unpacks the filter Args -*/ +// unpacks the filter Args func FromParam(p string) (Args, error) { args := Args{} + if len(p) == 0 { + return args, nil + } err := json.Unmarshal([]byte(p), &args) if err != nil { return nil, err diff --git a/utils/filters/parse_test.go b/utils/filters/parse_test.go index 59c5a5d93..a24835022 100644 --- a/utils/filters/parse_test.go +++ b/utils/filters/parse_test.go @@ -61,3 +61,18 @@ func TestParam(t *testing.T) { } } } + +func TestEmpty(t *testing.T) { + a := Args{} + v, err := ToParam(a) + if err != nil { + t.Errorf("failed to marshal the filters: %s", err) + } + v1, err := FromParam(v) + if err != nil { + t.Errorf("%s", err) + } + if len(a) != len(v1) { + t.Errorf("these should both be empty sets") + } +} From af6ab357e8db8888429a28014f629af19a2250b8 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 2 Jun 2014 20:47:07 +0000 Subject: [PATCH 345/400] switch skipping from error to debug Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- server/server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/server.go b/server/server.go index 85f1125ba..e7134d8f0 100644 --- a/server/server.go +++ b/server/server.go @@ -1095,7 +1095,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin // ensure no two downloads of the same layer happen at the same time if c, err := srv.poolAdd("pull", "layer:"+id); err != nil { - utils.Errorf("Image (id: %s) pull is already running, skipping: %v", id, err) + utils.Debugf("Image (id: %s) pull is already running, skipping: %v", id, err) <-c } defer srv.poolRemove("pull", "layer:"+id) @@ -1234,7 +1234,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName <-c out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Download complete", nil)) } else { - utils.Errorf("Image (id: %s) pull is already running, skipping: %v", img.ID, err) + utils.Debugf("Image (id: %s) pull is already running, skipping: %v", img.ID, err) } if parallel { errors <- nil From 0633b12b286d763521124f6d144deade89a89bfc Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 2 Jun 2014 23:03:10 +0000 Subject: [PATCH 346/400] add proto validation at parse Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- nat/nat.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/nat/nat.go b/nat/nat.go index 7aad775d7..31633dd54 100644 --- a/nat/nat.go +++ b/nat/nat.go @@ -5,9 +5,10 @@ package nat import ( "fmt" - "github.com/dotcloud/docker/utils" "strconv" "strings" + + "github.com/dotcloud/docker/utils" ) const ( @@ -72,6 +73,15 @@ func SplitProtoPort(rawPort string) (string, string) { return parts[1], parts[0] } +func validateProto(proto string) bool { + for _, availableProto := range []string{"tcp", "udp"} { + if availableProto == proto { + return true + } + } + return false +} + // We will receive port specs in the format of ip:public:private/proto and these need to be // parsed in the internal types func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) { @@ -113,6 +123,9 @@ func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, if _, err := strconv.ParseUint(hostPort, 10, 16); hostPort != "" && err != nil { return nil, nil, fmt.Errorf("Invalid hostPort: %s", hostPort) } + if !validateProto(proto) { + return nil, nil, fmt.Errorf("Invalid proto: %s", proto) + } port := NewPort(proto, containerPort) if _, exists := exposedPorts[port]; !exists { From 996133b9babbe4c4dfb42ee0d5aea9566568af14 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 3 Jun 2014 00:05:54 +0000 Subject: [PATCH 347/400] add volumes back to inspect Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- daemon/inspect.go | 1 + 1 file changed, 1 insertion(+) diff --git a/daemon/inspect.go b/daemon/inspect.go index cf70d1579..4da09d544 100644 --- a/daemon/inspect.go +++ b/daemon/inspect.go @@ -42,6 +42,7 @@ func (daemon *Daemon) ContainerInspect(job *engine.Job) engine.Status { out.Set("ExecDriver", container.ExecDriver) out.Set("MountLabel", container.MountLabel) out.Set("ProcessLabel", container.ProcessLabel) + out.SetJson("Volumes", container.Volumes) out.SetJson("VolumesRW", container.VolumesRW) out.SetJson("HostConfig", container.hostConfig) if _, err := out.WriteTo(job.Stdout); err != nil { From 4e7254840bc60f31e501c52e25990c540015e83b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 3 Jun 2014 00:46:06 +0000 Subject: [PATCH 348/400] Add redirect and env proxy support to docker login Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- registry/auth.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/registry/auth.go b/registry/auth.go index 4fdd51fda..7384efbad 100644 --- a/registry/auth.go +++ b/registry/auth.go @@ -5,12 +5,13 @@ import ( "encoding/json" "errors" "fmt" - "github.com/dotcloud/docker/utils" "io/ioutil" "net/http" "os" "path" "strings" + + "github.com/dotcloud/docker/utils" ) // Where we store the config file @@ -152,10 +153,16 @@ func SaveConfig(configFile *ConfigFile) error { // try to register/login to the registry server func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, error) { var ( - status string - reqBody []byte - err error - client = &http.Client{} + status string + reqBody []byte + err error + client = &http.Client{ + Transport: &http.Transport{ + DisableKeepAlives: true, + Proxy: http.ProxyFromEnvironment, + }, + CheckRedirect: AddRequiredHeadersToRedirectedRequests, + } reqStatusCode = 0 serverAddress = authConfig.ServerAddress ) From f41ced96af9dba56dd368e213e6e83fd02b85b03 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 2 Jun 2014 14:16:30 -0700 Subject: [PATCH 349/400] Ensure that ownership and perms are copied to volume This only works if the file or dir is already created in the image before setting it to be a volume. There is no way around this because we don't have the data avaliable to set the volume at the beginning of the dockerfile Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/volumes.go | 102 ++++++++++++++++++++++++---------------------- 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/daemon/volumes.go b/daemon/volumes.go index c3b003d0e..d9719369a 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -97,13 +97,16 @@ func applyVolumesFrom(container *Container) error { if _, exists := container.Volumes[volPath]; exists { continue } + stat, err := os.Stat(c.getResourcePath(volPath)) if err != nil { return err } + if err := createIfNotExists(container.getResourcePath(volPath), stat.IsDir()); err != nil { return err } + container.Volumes[volPath] = id if isRW, exists := c.VolumesRW[volPath]; exists { container.VolumesRW[volPath] = isRW && mountRW @@ -180,38 +183,39 @@ func createVolumes(container *Container) error { return nil } -func createIfNotExists(path string, isDir bool) error { - if _, err := os.Stat(path); err != nil { - if os.IsNotExist(err) { - if isDir { - if err := os.MkdirAll(path, 0755); err != nil { - return err - } - } else { - if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { - return err - } - f, err := os.OpenFile(path, os.O_CREATE, 0755) - if err != nil { - return err - } - defer f.Close() +func createIfNotExists(destination string, isDir bool) error { + if _, err := os.Stat(destination); err != nil && os.IsNotExist(err) { + if isDir { + if err := os.MkdirAll(destination, 0755); err != nil { + return err } + } else { + if err := os.MkdirAll(filepath.Dir(destination), 0755); err != nil { + return err + } + + f, err := os.OpenFile(destination, os.O_CREATE, 0755) + if err != nil { + return err + } + f.Close() } } + return nil } func initializeVolume(container *Container, volPath string, binds map[string]BindMap) error { volumesDriver := container.daemon.volumes.Driver() volPath = filepath.Clean(volPath) + // Skip existing volumes if _, exists := container.Volumes[volPath]; exists { return nil } var ( - srcPath string + destination string isBindMount bool volIsDir = true @@ -221,19 +225,21 @@ func initializeVolume(container *Container, volPath string, binds map[string]Bin // If an external bind is defined for this volume, use that as a source if bindMap, exists := binds[volPath]; exists { isBindMount = true - srcPath = bindMap.SrcPath - if !filepath.IsAbs(srcPath) { - return fmt.Errorf("%s must be an absolute path", srcPath) + destination = bindMap.SrcPath + + if !filepath.IsAbs(destination) { + return fmt.Errorf("%s must be an absolute path", destination) } + if strings.ToLower(bindMap.Mode) == "rw" { srcRW = true } + if stat, err := os.Stat(bindMap.SrcPath); err != nil { return err } else { volIsDir = stat.IsDir() } - // Otherwise create an directory in $ROOT/volumes/ and use that } else { // Do not pass a container as the parameter for the volume creation. // The graph driver using the container's information ( Image ) to @@ -242,26 +248,28 @@ func initializeVolume(container *Container, volPath string, binds map[string]Bin if err != nil { return err } - srcPath, err = volumesDriver.Get(c.ID, "") + + destination, err = volumesDriver.Get(c.ID, "") if err != nil { return fmt.Errorf("Driver %s failed to get volume rootfs %s: %s", volumesDriver, c.ID, err) } - srcRW = true // RW by default + + srcRW = true } - if p, err := filepath.EvalSymlinks(srcPath); err != nil { + if p, err := filepath.EvalSymlinks(destination); err != nil { return err } else { - srcPath = p + destination = p } // Create the mountpoint - rootVolPath, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, volPath), container.basefs) + source, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, volPath), container.basefs) if err != nil { return err } - newVolPath, err := filepath.Rel(container.basefs, rootVolPath) + newVolPath, err := filepath.Rel(container.basefs, source) if err != nil { return err } @@ -272,59 +280,57 @@ func initializeVolume(container *Container, volPath string, binds map[string]Bin delete(container.VolumesRW, volPath) } - container.Volumes[newVolPath] = srcPath + container.Volumes[newVolPath] = destination container.VolumesRW[newVolPath] = srcRW - if err := createIfNotExists(rootVolPath, volIsDir); err != nil { + if err := createIfNotExists(source, volIsDir); err != nil { return err } // Do not copy or change permissions if we are mounting from the host if srcRW && !isBindMount { - if err := copyExistingContents(rootVolPath, srcPath); err != nil { + if err := copyExistingContents(source, destination); err != nil { return err } } return nil } -func copyExistingContents(rootVolPath, srcPath string) error { - volList, err := ioutil.ReadDir(rootVolPath) +func copyExistingContents(source, destination string) error { + volList, err := ioutil.ReadDir(source) if err != nil { return err } if len(volList) > 0 { - srcList, err := ioutil.ReadDir(srcPath) + srcList, err := ioutil.ReadDir(destination) if err != nil { return err } if len(srcList) == 0 { // If the source volume is empty copy files from the root into the volume - if err := archive.CopyWithTar(rootVolPath, srcPath); err != nil { + if err := archive.CopyWithTar(source, destination); err != nil { return err } } } - var ( - stat syscall.Stat_t - srcStat syscall.Stat_t - ) + return copyOwnership(source, destination) +} - if err := syscall.Stat(rootVolPath, &stat); err != nil { +// copyOwnership copies the permissions and uid:gid of the source file +// into the destination file +func copyOwnership(source, destination string) error { + var stat syscall.Stat_t + + if err := syscall.Stat(source, &stat); err != nil { return err } - if err := syscall.Stat(srcPath, &srcStat); err != nil { + + if err := os.Chown(destination, int(stat.Uid), int(stat.Gid)); err != nil { return err } - // Change the source volume's ownership if it differs from the root - // files that were just copied - if stat.Uid != srcStat.Uid || stat.Gid != srcStat.Gid { - if err := os.Chown(srcPath, int(stat.Uid), int(stat.Gid)); err != nil { - return err - } - } - return nil + + return os.Chmod(destination, os.FileMode(stat.Mode)) } From c024c9bd1ec13aade038f87266a03a1d1bc13441 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 2 Jun 2014 14:46:28 -0700 Subject: [PATCH 350/400] Add test for volume ownership and perms Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- integration-cli/docker_cli_build_test.go | 32 ++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 0a698308b..7f6271a38 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -1067,6 +1067,7 @@ func TestBuildADDLocalAndRemoteFilesWithCache(t *testing.T) { logDone("build - add local and remote file with cache") } +// TODO: TestCaching func TestBuildADDLocalAndRemoteFilesWithoutCache(t *testing.T) { name := "testbuildaddlocalandremotefilewithoutcache" defer deleteImages(name) @@ -1101,3 +1102,34 @@ func TestBuildADDLocalAndRemoteFilesWithoutCache(t *testing.T) { } logDone("build - add local and remote file without cache") } + +func TestBuildWithVolumeOwnership(t *testing.T) { + name := "testbuildimg" + defer deleteImages(name) + + _, err := buildImage(name, + `FROM busybox:latest + RUN mkdir /test && chown daemon:daemon /test && chmod 0600 /test + VOLUME /test`, + true) + + if err != nil { + t.Fatal(err) + } + + cmd := exec.Command(dockerBinary, "run", "--rm", "testbuildimg", "ls", "-la", "/test") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err) + } + + if expected := "drw-------"; !strings.Contains(out, expected) { + t.Fatalf("expected %s received %s", expected, out) + } + + if expected := "daemon daemon"; !strings.Contains(out, expected) { + t.Fatalf("expected %s received %s", expected, out) + } + + logDone("build - volume ownership") +} From 41f7cef2bd186d321fc4489691ba53ab41eb48e5 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 2 Jun 2014 18:23:47 -0700 Subject: [PATCH 351/400] Add SYS_CHROOT cap to unprivileged containers Fixes #6103 Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- .../execdriver/native/template/default_template.go | 1 + integration-cli/docker_cli_run_test.go | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/daemon/execdriver/native/template/default_template.go b/daemon/execdriver/native/template/default_template.go index 21c888a03..e7d3143df 100644 --- a/daemon/execdriver/native/template/default_template.go +++ b/daemon/execdriver/native/template/default_template.go @@ -20,6 +20,7 @@ func New() *libcontainer.Container { "SETFCAP", "SETPCAP", "NET_BIND_SERVICE", + "SYS_CHROOT", }, Namespaces: map[string]bool{ "NEWNS": true, diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 209d730f9..545ad371e 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -873,3 +873,15 @@ func TestThatCharacterDevicesActLikeCharacterDevices(t *testing.T) { logDone("run - test that character devices work.") } + +func TestRunUnprivilegedWithChroot(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "busybox", "chroot", "/", "true") + + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + + deleteAllContainers() + + logDone("run - unprivileged with chroot") +} From 68476e277f953d1076c8e966691769d5a35e65b6 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 2 Jun 2014 19:26:41 -0600 Subject: [PATCH 352/400] Move duplicated FS "magic" values to the graphdriver package so they can be shared instead of duplicated Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- daemon/graphdriver/aufs/aufs.go | 9 ++++++--- daemon/graphdriver/btrfs/btrfs.go | 6 +----- daemon/graphdriver/driver.go | 7 +++++++ 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/daemon/graphdriver/aufs/aufs.go b/daemon/graphdriver/aufs/aufs.go index a8d728bbb..3757ec56d 100644 --- a/daemon/graphdriver/aufs/aufs.go +++ b/daemon/graphdriver/aufs/aufs.go @@ -39,7 +39,10 @@ import ( var ( ErrAufsNotSupported = fmt.Errorf("AUFS was not found in /proc/filesystems") - IncompatibleFSMagic = []int64{0x9123683E /*btrfs*/, 0x61756673 /*AUFS*/} + incompatibleFsMagic = []graphdriver.FsMagic{ + graphdriver.FsMagicBtrfs, + graphdriver.FsMagicAufs, + } ) func init() { @@ -67,8 +70,8 @@ func Init(root string) (graphdriver.Driver, error) { return nil, fmt.Errorf("Couldn't stat the root directory: %s", err) } - for _, magic := range IncompatibleFSMagic { - if int64(buf.Type) == magic { + for _, magic := range incompatibleFsMagic { + if graphdriver.FsMagic(buf.Type) == magic { return nil, graphdriver.ErrIncompatibleFS } } diff --git a/daemon/graphdriver/btrfs/btrfs.go b/daemon/graphdriver/btrfs/btrfs.go index 06c3fd066..56299b18e 100644 --- a/daemon/graphdriver/btrfs/btrfs.go +++ b/daemon/graphdriver/btrfs/btrfs.go @@ -18,10 +18,6 @@ import ( "unsafe" ) -const ( - btrfsSuperMagic = 0x9123683E -) - func init() { graphdriver.Register("btrfs", Init) } @@ -34,7 +30,7 @@ func Init(home string) (graphdriver.Driver, error) { return nil, err } - if buf.Type != btrfsSuperMagic { + if graphdriver.FsMagic(buf.Type) != graphdriver.FsMagicBtrfs { return nil, graphdriver.ErrPrerequisites } diff --git a/daemon/graphdriver/driver.go b/daemon/graphdriver/driver.go index 33965ccae..8f9c0b6d2 100644 --- a/daemon/graphdriver/driver.go +++ b/daemon/graphdriver/driver.go @@ -8,6 +8,13 @@ import ( "path" ) +type FsMagic uint64 + +const ( + FsMagicBtrfs = FsMagic(0x9123683E) + FsMagicAufs = FsMagic(0x61756673) +) + type InitFunc func(root string) (Driver, error) type Driver interface { From 3a77a73a856f50a5954f22c5a76395ca77e43d00 Mon Sep 17 00:00:00 2001 From: Zac Dover Date: Tue, 3 Jun 2014 15:26:14 +1000 Subject: [PATCH 353/400] grammar; nothing big Docker-DCO-1.1-Signed-off-by: Zac Dover (github: zdover23) Added another "The" Docker-DCO-1.1-Signed-off-by: Zac Dover (github: zdover23) --- contrib/man/md/Dockerfile.5.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contrib/man/md/Dockerfile.5.md b/contrib/man/md/Dockerfile.5.md index 06970eb87..a298083f8 100644 --- a/contrib/man/md/Dockerfile.5.md +++ b/contrib/man/md/Dockerfile.5.md @@ -6,12 +6,12 @@ Dockerfile - automate the steps of creating a Docker image # INTRODUCTION -**Dockerfile** is a configuration file that automates the steps of creating a -Docker image. It is similar to a Makefile. Docker reads instructions from -**Dockerfile** to automate the steps otherwise performed manually to create -an image. To build an image, create a file called **Dockerfile**. The -**Dockerfile** describes the steps taken to assemble the image. When the -**Dockerfile** has been created, call the **docker build** command, using the +The **Dockerfile** is a configuration file that automates the steps of creating +a Docker image. It is similar to a Makefile. Docker reads instructions from the +**Dockerfile** to automate the steps otherwise performed manually to create an +image. To build an image, create a file called **Dockerfile**. The +**Dockerfile** describes the steps taken to assemble the image. When the +**Dockerfile** has been created, call the **docker build** command, using the path of directory that contains **Dockerfile** as the argument. # SYNOPSIS From 6b8f0e394b3960bba1db982c62c002221419f3f5 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 3 Jun 2014 11:29:27 +0200 Subject: [PATCH 354/400] libcontainer/cgroup: Use raw access to set up and join the devices cgroup The systemd support for the devices cgroup lacks two required features: * Support for wildcards to allow mknod on any device * Support for wildcards to allow /dev/pts support The second is available in more recent systemd as "char-pts", but not in e.g. v208 which is in wide use. Additionally, the current approach of letting systemd set up the devices cgroup and then adding some devices to it doesn't work, because some times systemd (at least v208) re-initializes the devices cgroup, overwriting our custom devices. See https://github.com/dotcloud/docker/issues/6009 for the details. When wildcarded mknod support is available in systemd we should implement a pure systemd version, but we need to keep the old one around for backwards compat. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- .../cgroups/systemd/apply_systemd.go | 40 +++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/pkg/libcontainer/cgroups/systemd/apply_systemd.go b/pkg/libcontainer/cgroups/systemd/apply_systemd.go index e57cf16b2..622ad44dd 100644 --- a/pkg/libcontainer/cgroups/systemd/apply_systemd.go +++ b/pkg/libcontainer/cgroups/systemd/apply_systemd.go @@ -111,11 +111,6 @@ func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) { systemd1.Property{"PIDs", dbus.MakeVariant([]uint32{uint32(pid)})}, ) - if !c.AllowAllDevices { - properties = append(properties, - systemd1.Property{"DevicePolicy", dbus.MakeVariant("strict")}) - } - // Always enable accounting, this gets us the same behaviour as the fs implementation, // plus the kernel has some problems with joining the memory cgroup at a later time. properties = append(properties, @@ -148,13 +143,44 @@ func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) { cgroup := props["ControlGroup"].(string) if !c.AllowAllDevices { + // Atm we can't use the systemd device support because of two missing things: + // * Support for wildcards to allow mknod on any device + // * Support for wildcards to allow /dev/pts support + // + // The second is available in more recent systemd as "char-pts", but not in e.g. v208 which is + // in wide use. When both these are availalable we will be able to switch, but need to keep the old + // implementation for backwards compat. + // + // Note: we can't use systemd to set up the initial limits, and then change the cgroup + // because systemd will re-write the device settings if it needs to re-apply the cgroup context. + // This happens at least for v208 when any sibling unit is started. + mountpoint, err := cgroups.FindCgroupMountpoint("devices") if err != nil { return nil, err } - dir := filepath.Join(mountpoint, cgroup) - // We use the same method of allowing devices as in the fs backend. This needs to be changed to use DBUS as soon as possible. However, that change has to wait untill http://cgit.freedesktop.org/systemd/systemd/commit/?id=90060676c442604780634c0a993e3f9c3733f8e6 has been applied in most commonly used systemd versions. + initPath, err := cgroups.GetInitCgroupDir("devices") + if err != nil { + return nil, err + } + + dir := filepath.Join(mountpoint, initPath, c.Parent, c.Name) + + res.cleanupDirs = append(res.cleanupDirs, dir) + + if err := os.MkdirAll(dir, 0755); err != nil && !os.IsExist(err) { + return nil, err + } + + if err := ioutil.WriteFile(filepath.Join(dir, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700); err != nil { + return nil, err + } + + if err := writeFile(dir, "devices.deny", "a"); err != nil { + return nil, err + } + for _, dev := range c.AllowedDevices { if err := writeFile(dir, "devices.allow", dev.GetCgroupAllowString()); err != nil { return nil, err From 02255ddaa4948fd94450fd5d9d2ee9b147e88352 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Fri, 30 May 2014 16:42:37 -0400 Subject: [PATCH 355/400] filters: don't encode empty set. API docs Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- api/client/commands.go | 27 ++++++++++--------- .../reference/api/docker_remote_api_v1.12.md | 10 +++++++ server/server.go | 2 +- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index 4e013b8fe..4f0915ba7 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -1172,13 +1172,15 @@ func (cli *DockerCli) CmdImages(args ...string) error { matchName := cmd.Arg(0) // FIXME: --viz and --tree are deprecated. Remove them in a future version. if *flViz || *flTree { - filterJson, err := filters.ToParam(imageFilterArgs) - if err != nil { - return err - } v := url.Values{ - "all": []string{"1"}, - "filters": []string{filterJson}, + "all": []string{"1"}, + } + if len(imageFilterArgs) > 0 { + filterJson, err := filters.ToParam(imageFilterArgs) + if err != nil { + return err + } + v.Set("filters", filterJson) } body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false)) @@ -1242,12 +1244,13 @@ func (cli *DockerCli) CmdImages(args ...string) error { fmt.Fprintf(cli.out, " base [style=invisible]\n}\n") } } else { - filterJson, err := filters.ToParam(imageFilterArgs) - if err != nil { - return err - } - v := url.Values{ - "filters": []string{filterJson}, + v := url.Values{} + if len(imageFilterArgs) > 0 { + filterJson, err := filters.ToParam(imageFilterArgs) + if err != nil { + return err + } + v.Set("filters", filterJson) } if cmd.NArg() == 1 { diff --git a/docs/sources/reference/api/docker_remote_api_v1.12.md b/docs/sources/reference/api/docker_remote_api_v1.12.md index 9cc84f4ff..23afa36bc 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.12.md +++ b/docs/sources/reference/api/docker_remote_api_v1.12.md @@ -712,6 +712,16 @@ Copy files or folders of container `id` } ] + + Query Parameters: + +   + + - **all** – 1/True/true or 0/False/false, default false + - **filters** – a json encoded value of the filters (a map[string][]string) to process on the images list. + + + ### Create an image `POST /images/create` diff --git a/server/server.go b/server/server.go index 8c70842a4..ff9d37030 100644 --- a/server/server.go +++ b/server/server.go @@ -712,7 +712,7 @@ func (srv *Server) Images(job *engine.Job) engine.Status { } } - if job.GetenvBool("all") && !filt_tagged { + if job.GetenvBool("all") && filt_tagged { allImages, err = srv.daemon.Graph().Map() } else { allImages, err = srv.daemon.Graph().Heads() From 613f74c1fbbdc5e476d28974d1dbe3727033d083 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 30 May 2014 15:09:07 -0700 Subject: [PATCH 356/400] Implement systemd support for freezer These PR does a few things. It ensures that the freezer cgroup is joined in the systemd driver. It also provides a public api for setting the freezer state via the cgroups package. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/cgroups/cgroups.go | 13 ++- pkg/libcontainer/cgroups/fs/apply_raw.go | 103 ++++++++---------- pkg/libcontainer/cgroups/fs/freezer.go | 5 +- .../cgroups/systemd/apply_nosystemd.go | 4 + .../cgroups/systemd/apply_systemd.go | 61 ++++++++++- 5 files changed, 118 insertions(+), 68 deletions(-) diff --git a/pkg/libcontainer/cgroups/cgroups.go b/pkg/libcontainer/cgroups/cgroups.go index 905d0ca0b..85607b548 100644 --- a/pkg/libcontainer/cgroups/cgroups.go +++ b/pkg/libcontainer/cgroups/cgroups.go @@ -10,6 +10,14 @@ var ( ErrNotFound = errors.New("mountpoint not found") ) +type FreezerState string + +const ( + Undefined FreezerState = "" + Frozen FreezerState = "FROZEN" + Thawed FreezerState = "THAWED" +) + type Cgroup struct { Name string `json:"name,omitempty"` Parent string `json:"parent,omitempty"` // name of parent cgroup or slice @@ -23,9 +31,8 @@ type Cgroup struct { CpuQuota int64 `json:"cpu_quota,omitempty"` // CPU hardcap limit (in usecs). Allowed cpu time in a given period. CpuPeriod int64 `json:"cpu_period,omitempty"` // CPU period to be used for hardcapping (in usecs). 0 to use system default. CpusetCpus string `json:"cpuset_cpus,omitempty"` // CPU to use - Freezer string `json:"freezer,omitempty"` // set the freeze value for the process - - Slice string `json:"slice,omitempty"` // Parent slice to use for systemd + Freezer FreezerState `json:"freezer,omitempty"` // set the freeze value for the process + Slice string `json:"slice,omitempty"` // Parent slice to use for systemd } type ActiveCgroup interface { diff --git a/pkg/libcontainer/cgroups/fs/apply_raw.go b/pkg/libcontainer/cgroups/fs/apply_raw.go index 291d1e84b..8fa25997f 100644 --- a/pkg/libcontainer/cgroups/fs/apply_raw.go +++ b/pkg/libcontainer/cgroups/fs/apply_raw.go @@ -37,65 +37,28 @@ type data struct { } func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) { - // We have two implementation of cgroups support, one is based on - // systemd and the dbus api, and one is based on raw cgroup fs operations - // following the pre-single-writer model docs at: - // http://www.freedesktop.org/wiki/Software/systemd/PaxControlGroups/ - // - // we can pick any subsystem to find the root - - cgroupRoot, err := cgroups.FindCgroupMountpoint("cpu") + d, err := getCgroupData(c, pid) if err != nil { return nil, err } - cgroupRoot = filepath.Dir(cgroupRoot) - if _, err := os.Stat(cgroupRoot); err != nil { - return nil, fmt.Errorf("cgroups fs not found") - } - - cgroup := c.Name - if c.Parent != "" { - cgroup = filepath.Join(c.Parent, cgroup) - } - - d := &data{ - root: cgroupRoot, - cgroup: cgroup, - c: c, - pid: pid, - } for _, sys := range subsystems { if err := sys.Set(d); err != nil { d.Cleanup() return nil, err } } + return d, nil } func GetStats(c *cgroups.Cgroup) (*cgroups.Stats, error) { stats := cgroups.NewStats() - cgroupRoot, err := cgroups.FindCgroupMountpoint("cpu") + + d, err := getCgroupData(c, 0) if err != nil { return nil, err } - cgroupRoot = filepath.Dir(cgroupRoot) - - if _, err := os.Stat(cgroupRoot); err != nil { - return nil, fmt.Errorf("cgroups fs not found") - } - - cgroup := c.Name - if c.Parent != "" { - cgroup = filepath.Join(c.Parent, cgroup) - } - - d := &data{ - root: cgroupRoot, - cgroup: cgroup, - c: c, - } for _, sys := range subsystems { if err := sys.GetStats(d, stats); err != nil { @@ -106,27 +69,26 @@ func GetStats(c *cgroups.Cgroup) (*cgroups.Stats, error) { return stats, nil } +// Freeze toggles the container's freezer cgroup depending on the state +// provided +func Freeze(c *cgroups.Cgroup, state cgroups.FreezerState) error { + d, err := getCgroupData(c, 0) + if err != nil { + return err + } + + c.Freezer = state + + freezer := subsystems["freezer"] + + return freezer.Set(d) +} + func GetPids(c *cgroups.Cgroup) ([]int, error) { - cgroupRoot, err := cgroups.FindCgroupMountpoint("cpu") + d, err := getCgroupData(c, 0) if err != nil { return nil, err } - cgroupRoot = filepath.Dir(cgroupRoot) - - if _, err := os.Stat(cgroupRoot); err != nil { - return nil, fmt.Errorf("cgroup root %s not found", cgroupRoot) - } - - cgroup := c.Name - if c.Parent != "" { - cgroup = filepath.Join(c.Parent, cgroup) - } - - d := &data{ - root: cgroupRoot, - cgroup: cgroup, - c: c, - } dir, err := d.path("devices") if err != nil { @@ -136,6 +98,31 @@ func GetPids(c *cgroups.Cgroup) ([]int, error) { return cgroups.ReadProcsFile(dir) } +func getCgroupData(c *cgroups.Cgroup, pid int) (*data, error) { + // we can pick any subsystem to find the root + cgroupRoot, err := cgroups.FindCgroupMountpoint("cpu") + if err != nil { + return nil, err + } + cgroupRoot = filepath.Dir(cgroupRoot) + + if _, err := os.Stat(cgroupRoot); err != nil { + return nil, fmt.Errorf("cgroups fs not found") + } + + cgroup := c.Name + if c.Parent != "" { + cgroup = filepath.Join(c.Parent, cgroup) + } + + return &data{ + root: cgroupRoot, + cgroup: cgroup, + c: c, + pid: pid, + }, nil +} + func (raw *data) parent(subsystem string) (string, error) { initPath, err := cgroups.GetInitCgroupDir(subsystem) if err != nil { diff --git a/pkg/libcontainer/cgroups/fs/freezer.go b/pkg/libcontainer/cgroups/fs/freezer.go index a9a27ef5a..ed57f8ba9 100644 --- a/pkg/libcontainer/cgroups/fs/freezer.go +++ b/pkg/libcontainer/cgroups/fs/freezer.go @@ -20,11 +20,12 @@ func (s *freezerGroup) Set(d *data) error { return nil } - if d.c.Freezer != "" { - if err := writeFile(dir, "freezer.state", d.c.Freezer); err != nil { + if d.c.Freezer != cgroups.Undefined { + if err := writeFile(dir, "freezer.state", string(d.c.Freezer)); err != nil { return err } } + return nil } diff --git a/pkg/libcontainer/cgroups/systemd/apply_nosystemd.go b/pkg/libcontainer/cgroups/systemd/apply_nosystemd.go index 0fff3e4c6..c72bb1161 100644 --- a/pkg/libcontainer/cgroups/systemd/apply_nosystemd.go +++ b/pkg/libcontainer/cgroups/systemd/apply_nosystemd.go @@ -19,3 +19,7 @@ func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) { func GetPids(c *cgroups.Cgroup) ([]int, error) { return nil, fmt.Errorf("Systemd not supported") } + +func Freeze(c *cgroups.Cgroup, state cgroups.FreezerState) error { + return fmt.Errorf("Systemd not supported") +} diff --git a/pkg/libcontainer/cgroups/systemd/apply_systemd.go b/pkg/libcontainer/cgroups/systemd/apply_systemd.go index 622ad44dd..eb1425b84 100644 --- a/pkg/libcontainer/cgroups/systemd/apply_systemd.go +++ b/pkg/libcontainer/cgroups/systemd/apply_systemd.go @@ -218,6 +218,14 @@ func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) { } } + // we need to manually join the freezer cgroup in systemd because it does not currently support it + // via the dbus api + freezerPath, err := joinFreezer(c, pid) + if err != nil { + return nil, err + } + res.cleanupDirs = append(res.cleanupDirs, freezerPath) + if len(cpusetArgs) != 0 { // systemd does not atm set up the cpuset controller, so we must manually // join it. Additionally that is a very finicky controller where each @@ -227,14 +235,19 @@ func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) { if err != nil { return nil, err } + initPath, err := cgroups.GetInitCgroupDir("cpuset") if err != nil { return nil, err } - rootPath := filepath.Join(mountpoint, initPath) + var ( + foundCpus bool + foundMems bool - path := filepath.Join(mountpoint, initPath, c.Parent+"-"+c.Name) + rootPath = filepath.Join(mountpoint, initPath) + path = filepath.Join(mountpoint, initPath, c.Parent+"-"+c.Name) + ) res.cleanupDirs = append(res.cleanupDirs, path) @@ -242,9 +255,6 @@ func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) { return nil, err } - foundCpus := false - foundMems := false - for _, arg := range cpusetArgs { if arg.File == "cpuset.cpus" { foundCpus = true @@ -303,6 +313,47 @@ func (c *systemdCgroup) Cleanup() error { return nil } +func joinFreezer(c *cgroups.Cgroup, pid int) (string, error) { + path, err := getFreezerPath(c) + if err != nil { + return "", err + } + + if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) { + return "", err + } + + if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700); err != nil { + return "", err + } + + return path, nil +} + +func getFreezerPath(c *cgroups.Cgroup) (string, error) { + mountpoint, err := cgroups.FindCgroupMountpoint("freezer") + if err != nil { + return "", err + } + + initPath, err := cgroups.GetInitCgroupDir("freezer") + if err != nil { + return "", err + } + + return filepath.Join(mountpoint, initPath, fmt.Sprintf("%s-%s", c.Parent, c.Name)), nil + +} + +func Freeze(c *cgroups.Cgroup, state cgroups.FreezerState) error { + path, err := getFreezerPath(c) + if err != nil { + return err + } + + return ioutil.WriteFile(filepath.Join(path, "freezer.state"), []byte(state), 0) +} + func GetPids(c *cgroups.Cgroup) ([]int, error) { unitName := getUnitName(c) From 9fc8028c987ac560ceb3b29eb609d77a82abdacc Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 2 Jun 2014 14:48:58 -0400 Subject: [PATCH 357/400] Changed all references from Trusted Builds to Automated Builds * Updated docker images output * Deprecated docker images -t/--trusted option and replace with --automated * Changed *trusted variables to *automated * Changed added support for is_automated alongside deprecated is_trusted * Updated man pages, docs and completion * Updated API documentation Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- api/client/commands.go | 9 +++++---- contrib/completion/bash/docker | 2 +- contrib/completion/fish/docker.fish | 2 +- contrib/man/md/docker-search.1.md | 16 ++++++++-------- docs/sources/docker-io/builds.md | 11 +++++------ docs/sources/docker-io/repos.md | 2 +- docs/sources/examples/mongodb.md | 2 +- docs/sources/reference/api/docker_remote_api.md | 3 +++ .../reference/api/docker_remote_api_v1.12.md | 6 +++--- docs/sources/reference/commandline/cli.md | 2 +- docs/sources/userguide/dockerimages.md | 10 ++++++---- docs/sources/userguide/index.md | 2 +- 12 files changed, 36 insertions(+), 31 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index 4f0915ba7..1ad5d7889 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -1724,7 +1724,8 @@ func (cli *DockerCli) CmdAttach(args ...string) error { func (cli *DockerCli) CmdSearch(args ...string) error { cmd := cli.Subcmd("search", "TERM", "Search the docker index for images") noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") - trusted := cmd.Bool([]string{"t", "#trusted", "-trusted"}, false, "Only show trusted builds") + trusted := cmd.Bool([]string{"#t", "#trusted", "#-trusted"}, false, "Only show trusted builds") + automated := cmd.Bool([]string{"-automated"}, false, "Only show automated builds") stars := cmd.Int([]string{"s", "#stars", "-stars"}, 0, "Only displays with at least xxx stars") if err := cmd.Parse(args); err != nil { return nil @@ -1747,9 +1748,9 @@ func (cli *DockerCli) CmdSearch(args ...string) error { return err } w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0) - fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tTRUSTED\n") + fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n") for _, out := range outs.Data { - if (*trusted && !out.GetBool("is_trusted")) || (*stars > out.GetInt("star_count")) { + if ((*automated || *trusted) && (!out.GetBool("is_trusted") && !out.GetBool("is_automated"))) || (*stars > out.GetInt("star_count")) { continue } desc := strings.Replace(out.Get("description"), "\n", " ", -1) @@ -1763,7 +1764,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error { } fmt.Fprint(w, "\t") - if out.GetBool("is_trusted") { + if out.GetBool("is_automated") || out.GetBool("is_trusted") { fmt.Fprint(w, "[OK]") } fmt.Fprint(w, "\n") diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index e6a191d32..e2ddd2acc 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -539,7 +539,7 @@ _docker_search() case "$cur" in -*) - COMPREPLY=( $( compgen -W "--no-trunc -t --trusted -s --stars" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--no-trunc --automated -s --stars" -- "$cur" ) ) ;; *) ;; diff --git a/contrib/completion/fish/docker.fish b/contrib/completion/fish/docker.fish index 7ea478d05..a7fd52e31 100644 --- a/contrib/completion/fish/docker.fish +++ b/contrib/completion/fish/docker.fish @@ -229,7 +229,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from save' -a '(__fish_print complete -c docker -f -n '__fish_docker_no_subcommand' -a search -d 'Search for an image in the docker index' complete -c docker -A -f -n '__fish_seen_subcommand_from search' -l no-trunc -d "Don't truncate output" complete -c docker -A -f -n '__fish_seen_subcommand_from search' -s s -l stars -d 'Only displays with at least xxx stars' -complete -c docker -A -f -n '__fish_seen_subcommand_from search' -s t -l trusted -d 'Only show trusted builds' +complete -c docker -A -f -n '__fish_seen_subcommand_from search' -l automated -d 'Only show automated builds' # start complete -c docker -f -n '__fish_docker_no_subcommand' -a start -d 'Start a stopped container' diff --git a/contrib/man/md/docker-search.1.md b/contrib/man/md/docker-search.1.md index fb2c921f8..945dd34e5 100644 --- a/contrib/man/md/docker-search.1.md +++ b/contrib/man/md/docker-search.1.md @@ -5,7 +5,7 @@ docker-search - Search the docker index for images # SYNOPSIS -**docker search** **--no-trunc**[=*false*] **-t**|**--trusted**[=*false*] +**docker search** **--no-trunc**[=*false*] **--automated**[=*false*] **-s**|**--stars**[=*0*] TERM # DESCRIPTION @@ -13,7 +13,7 @@ docker-search - Search the docker index for images Search an index for an image with that matches the term TERM. The table of images returned displays the name, description (truncated by default), number of stars awarded, whether the image is official, and whether it -is trusted. +is automated. # OPTIONS **--no-trunc**=*true*|*false* @@ -23,8 +23,8 @@ is trusted. Only displays with at least NUM (integer) stars. I.e. only those images ranked >=NUM. -**-t**, **--trusted**=*true*|*false* - When true only show trusted builds. The default is false. +**--automated**=*true*|*false* + When true only show automated builds. The default is false. # EXAMPLE @@ -34,19 +34,19 @@ Search the registry for the term 'fedora' and only display those images ranked 3 or higher: $ sudo docker search -s 3 fedora - NAME DESCRIPTION STARS OFFICIAL TRUSTED + NAME DESCRIPTION STARS OFFICIAL AUTOMATED mattdm/fedora A basic Fedora image corresponding roughly... 50 fedora (Semi) Official Fedora base image. 38 mattdm/fedora-small A small Fedora image on which to build. Co... 8 goldmann/wildfly A WildFly application server running on a ... 3 [OK] -## Search the registry for trusted images +## Search the registry for automated images -Search the registry for the term 'fedora' and only display trusted images +Search the registry for the term 'fedora' and only display automated images ranked 1 or higher: $ sudo docker search -s 1 -t fedora - NAME DESCRIPTION STARS OFFICIAL TRUSTED + NAME DESCRIPTION STARS OFFICIAL AUTOMATED goldmann/wildfly A WildFly application server running on a ... 3 [OK] tutum/fedora-20 Fedora 20 image with SSH access. For the r... 1 [OK] diff --git a/docs/sources/docker-io/builds.md b/docs/sources/docker-io/builds.md index dd18907cf..c70de4006 100644 --- a/docs/sources/docker-io/builds.md +++ b/docs/sources/docker-io/builds.md @@ -1,6 +1,6 @@ page_title: Automated Builds on Docker.io page_description: Docker.io Automated Builds -page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker.io, docs, documentation, trusted, builds, trusted builds, automated builds +page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker.io, docs, documentation, trusted, builds, trusted builds, automated, automated builds # Automated Builds on Docker.io ## Automated Builds @@ -101,10 +101,9 @@ to Docker.io. #### Creating an Automated Build -You can [create a Trusted -Build](https://index.docker.io/builds/bitbucket/select/) -from any of your public or private BitBucket repositories with a -`Dockerfile`. +You can [create an Automated +Build](https://index.docker.io/builds/bitbucket/select/) from any of +your public or private BitBucket repositories with a `Dockerfile`. ### The Dockerfile and Automated Builds @@ -162,7 +161,7 @@ payload: "repository":{ "status":"Active", "description":"my docker repo that does cool things", - "is_trusted":false, + "is_automated":false, "full_description":"This is my full description", "repo_url":"https://index.docker.io/u/username/reponame/", "owner":"username", diff --git a/docs/sources/docker-io/repos.md b/docs/sources/docker-io/repos.md index a9bdabd89..11170182a 100644 --- a/docs/sources/docker-io/repos.md +++ b/docs/sources/docker-io/repos.md @@ -81,7 +81,7 @@ with a JSON payload similar to the example shown below. "repository":{ "status":"Active", "description":"my docker repo that does cool things", - "is_trusted":false, + "is_automated":false, "full_description":"This is my full description", "repo_url":"https://index.docker.io/u/username/reponame/", "owner":"username", diff --git a/docs/sources/examples/mongodb.md b/docs/sources/examples/mongodb.md index 5b0b7d3e2..17d58e0db 100644 --- a/docs/sources/examples/mongodb.md +++ b/docs/sources/examples/mongodb.md @@ -151,4 +151,4 @@ as daemon process(es). - [Linking containers](/userguide/dockerlinks) - [Cross-host linking containers](/articles/ambassador_pattern_linking/) - - [Creating a Trusted Build](/docker-io/builds/#trusted-builds) + - [Creating an Automated Build](/docker-io/builds/#automated-builds) diff --git a/docs/sources/reference/api/docker_remote_api.md b/docs/sources/reference/api/docker_remote_api.md index 64837817e..355102a70 100644 --- a/docs/sources/reference/api/docker_remote_api.md +++ b/docs/sources/reference/api/docker_remote_api.md @@ -47,6 +47,9 @@ Build now has support for the `forcerm` parameter to always remove containers **New!** All the JSON keys are now in CamelCase +**New!** +Trusted builds are now Automated Builds - `is_trusted` is now `is_automated`. + ## v1.11 ### Full Documentation diff --git a/docs/sources/reference/api/docker_remote_api_v1.12.md b/docs/sources/reference/api/docker_remote_api_v1.12.md index 23afa36bc..91b273158 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.12.md +++ b/docs/sources/reference/api/docker_remote_api_v1.12.md @@ -1002,21 +1002,21 @@ Search for an image on [Docker.io](https://index.docker.io). { "description": "", "is_official": false, - "is_trusted": false, + "is_automated": false, "name": "wma55/u1210sshd", "star_count": 0 }, { "description": "", "is_official": false, - "is_trusted": false, + "is_automated": false, "name": "jdswinbank/sshd", "star_count": 0 }, { "description": "", "is_official": false, - "is_trusted": false, + "is_automated": false, "name": "vgauthier/sshd", "star_count": 0 } diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 829f13b9a..faa9e2809 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1124,7 +1124,7 @@ Search [Docker.io](https://index.docker.io) for images --no-trunc=false Don't truncate output -s, --stars=0 Only displays with at least xxx stars - -t, --trusted=false Only show trusted builds + --automated=false Only show automated builds See [*Find Public Images on Docker.io*]( /userguide/dockerrepos/#find-public-images-on-dockerio) for diff --git a/docs/sources/userguide/dockerimages.md b/docs/sources/userguide/dockerimages.md index 1a69ab9aa..bd8b0d2c2 100644 --- a/docs/sources/userguide/dockerimages.md +++ b/docs/sources/userguide/dockerimages.md @@ -116,7 +116,7 @@ by using the `docker search` command to find all the images that contain the term `sinatra`. $ sudo docker search sinatra - NAME DESCRIPTION STARS OFFICIAL TRUSTED + NAME DESCRIPTION STARS OFFICIAL AUTOMATED training/sinatra Sinatra training image 0 [OK] marceldegraaf/sinatra Sinatra test app 0 mattwarren/docker-sinatra-demo 0 [OK] @@ -129,9 +129,11 @@ term `sinatra`. We can see we've returned a lot of images that use the term `sinatra`. We've returned a list of image names, descriptions, Stars (which measure the social popularity of images - if a user likes an image then they can "star" it), and -the Official and Trusted statuses. Official repositories are XXX and Trusted -repositories are [Trusted Build](/userguide/dockerrepos/) that allow you to -validate the source and content of an image. +the Official and Automated build statuses. Official repositories are built and +maintained by the [Stackbrew](https://github.com/dotcloud/stackbrew) project, +and Automated repositories are [Automated Builds]( +/userguide/dockerrepos/#automated-builds) that allow you to validate the source +and content of an image. We've reviewed the images available to use and we decided to use the `training/sinatra` image. So far we've seen two types of images repositories, diff --git a/docs/sources/userguide/index.md b/docs/sources/userguide/index.md index 7f703b685..715054043 100644 --- a/docs/sources/userguide/index.md +++ b/docs/sources/userguide/index.md @@ -76,7 +76,7 @@ Go to [Managing Data in Containers](/userguide/dockervolumes). Now we've learned a bit more about how to use Docker we're going to see how to combine Docker with the services available on Docker.io including -Trusted Builds and private repositories. +Automated Builds and private repositories. Go to [Working with Docker.io](/userguide/dockerrepos). From 5febba93babcf8c4b01862e88b6f6e11a1532bc8 Mon Sep 17 00:00:00 2001 From: SvenDowideit Date: Wed, 4 Jun 2014 05:44:20 +1000 Subject: [PATCH 358/400] IANA allocated Docker port: 2375 2375/2376 are assigned: http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=docker For #1440 Docker-DCO-1.1-Signed-off-by: SvenDowideit (github: SvenDowideit) --- contrib/man/md/docker.1.md | 6 +++--- contrib/man/old-man/docker.1 | 2 +- contrib/vagrant-docker/README.md | 8 ++++---- docs/sources/articles/basics.md | 10 +++++----- docs/sources/articles/https.md | 4 ++-- docs/sources/installation/mac.md | 2 +- docs/sources/installation/ubuntulinux.md | 4 ++-- docs/sources/reference/api/docker_remote_api_v1.0.md | 2 +- docs/sources/reference/api/docker_remote_api_v1.1.md | 2 +- docs/sources/reference/api/docker_remote_api_v1.10.md | 2 +- docs/sources/reference/api/docker_remote_api_v1.11.md | 2 +- docs/sources/reference/api/docker_remote_api_v1.12.md | 2 +- docs/sources/reference/api/docker_remote_api_v1.2.md | 4 ++-- docs/sources/reference/api/docker_remote_api_v1.3.md | 4 ++-- docs/sources/reference/api/docker_remote_api_v1.4.md | 4 ++-- docs/sources/reference/api/docker_remote_api_v1.5.md | 4 ++-- docs/sources/reference/api/docker_remote_api_v1.6.md | 2 +- docs/sources/reference/api/docker_remote_api_v1.7.md | 2 +- docs/sources/reference/api/docker_remote_api_v1.8.md | 2 +- docs/sources/reference/api/docker_remote_api_v1.9.md | 2 +- docs/sources/reference/commandline/cli.md | 4 ++-- integration-cli/docker_cli_build_test.go | 10 +++++----- integration/buildfile_test.go | 4 ++-- utils/utils_test.go | 2 +- 24 files changed, 45 insertions(+), 45 deletions(-) diff --git a/contrib/man/md/docker.1.md b/contrib/man/md/docker.1.md index c73b083e6..f990e5162 100644 --- a/contrib/man/md/docker.1.md +++ b/contrib/man/md/docker.1.md @@ -26,10 +26,10 @@ To see the man page for a command run **man docker **. **-D**=*true*|*false* Enable debug mode. Default is false. -**-H**, **--host**=[unix:///var/run/docker.sock]: tcp://[host[:port]] to bind or +**-H**, **--host**=[unix:///var/run/docker.sock]: tcp://[host:port] to bind or unix://[/path/to/socket] to use. - Enable both the socket support and TCP on localhost. When host=[0.0.0.0], -port=[4243] or path =[/var/run/docker.sock] is omitted, default values are used. + The socket(s) to bind to in daemon mode specified using one or more + tcp://host:port, unix:///path/to/socket, fd://* or fd://socketfd. **--api-enable-cors**=*true*|*false* Enable CORS headers in the remote API. Default is false. diff --git a/contrib/man/old-man/docker.1 b/contrib/man/old-man/docker.1 index 5a5586512..95f60891c 100644 --- a/contrib/man/old-man/docker.1 +++ b/contrib/man/old-man/docker.1 @@ -19,7 +19,7 @@ To see the man page for a command run \fBman docker \fR. Enable debug mode .TP .B\-H=[unix:///var/run/docker.sock]: tcp://[host[:port]] to bind or unix://[/path/to/socket] to use. -When host=[0.0.0.0], port=[4243] or path +When host=[0.0.0.0], port=[2375] or path =[/var/run/docker.sock] is omitted, default values are used. .TP .B \-\-api-enable-cors=false diff --git a/contrib/vagrant-docker/README.md b/contrib/vagrant-docker/README.md index d422492fe..4ef9c2877 100644 --- a/contrib/vagrant-docker/README.md +++ b/contrib/vagrant-docker/README.md @@ -31,20 +31,20 @@ stop on runlevel [!2345] respawn script - /usr/bin/docker -d -H=tcp://0.0.0.0:4243 + /usr/bin/docker -d -H=tcp://0.0.0.0:2375 end script ``` Once that's done, you need to set up a SSH tunnel between your host machine and the vagrant machine that's running Docker. This can be done by running the following command in a host terminal: ``` -ssh -L 4243:localhost:4243 -p 2222 vagrant@localhost +ssh -L 2375:localhost:2375 -p 2222 vagrant@localhost ``` -(The first 4243 is what your host can connect to, the second 4243 is what port Docker is running on in the vagrant machine, and the 2222 is the port Vagrant is providing for SSH. If VirtualBox is the VM you're using, you can see what value "2222" should be by going to: Network > Adapter 1 > Advanced > Port Forwarding in the VirtualBox GUI.) +(The first 2375 is what your host can connect to, the second 2375 is what port Docker is running on in the vagrant machine, and the 2222 is the port Vagrant is providing for SSH. If VirtualBox is the VM you're using, you can see what value "2222" should be by going to: Network > Adapter 1 > Advanced > Port Forwarding in the VirtualBox GUI.) Note that because the port has been changed, to run docker commands from within the command line you must run them like this: ``` -sudo docker -H 0.0.0.0:4243 < commands for docker > +sudo docker -H 0.0.0.0:2375 < commands for docker > ``` diff --git a/docs/sources/articles/basics.md b/docs/sources/articles/basics.md index 1f21e3454..cd4a9df65 100644 --- a/docs/sources/articles/basics.md +++ b/docs/sources/articles/basics.md @@ -61,7 +61,7 @@ image cache. With `-H` it is possible to make the Docker daemon to listen on a specific IP and port. By default, it will listen on `unix:///var/run/docker.sock` to allow only local connections by the -*root* user. You *could* set it to `0.0.0.0:4243` or a specific host IP +*root* user. You *could* set it to `0.0.0.0:2375` or a specific host IP to give access to everybody, but that is **not recommended** because then it is trivial for someone to gain root access to the host where the daemon is running. @@ -74,8 +74,8 @@ Similarly, the Docker client can use `-H` to connect to a custom port. For example: -- `tcp://host:4243` -> TCP connection on - host:4243 +- `tcp://host:2375` -> TCP connection on + host:2375 - `unix://path/to/socket` -> Unix socket located at `path/to/socket` @@ -98,11 +98,11 @@ You can use multiple `-H`, for example, if you want to listen on both TCP and a Unix socket # Run docker in daemon mode - $ sudo /docker -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock -d & + $ sudo /docker -H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock -d & # Download an ubuntu image, use default Unix socket $ sudo docker pull ubuntu # OR use the TCP port - $ sudo docker -H tcp://127.0.0.1:4243 pull ubuntu + $ sudo docker -H tcp://127.0.0.1:2375 pull ubuntu ## Starting a long-running worker process diff --git a/docs/sources/articles/https.md b/docs/sources/articles/https.md index c46cf6b88..cc8c6a976 100644 --- a/docs/sources/articles/https.md +++ b/docs/sources/articles/https.md @@ -67,13 +67,13 @@ Now you can make the Docker daemon only accept connections from clients providing a certificate trusted by our CA: $ sudo docker -d --tlsverify --tlscacert=ca.pem --tlscert=server-cert.pem --tlskey=server-key.pem \ - -H=0.0.0.0:4243 + -H=0.0.0.0:2375 To be able to connect to Docker and validate its certificate, you now need to provide your client keys, certificates and trusted CA: $ docker --tlsverify --tlscacert=ca.pem --tlscert=client-cert.pem --tlskey=client-key.pem \ - -H=dns-name-of-docker-host:4243 + -H=dns-name-of-docker-host:2375 > **Warning**: > As shown in the example above, you don't have to run the diff --git a/docs/sources/installation/mac.md b/docs/sources/installation/mac.md index 135130ef8..c450cd12d 100644 --- a/docs/sources/installation/mac.md +++ b/docs/sources/installation/mac.md @@ -33,7 +33,7 @@ virtual machine and run the Docker daemon. ``` boot2docker init boot2docker start - export DOCKER_HOST=tcp://localhost:4243 + export DOCKER_HOST=tcp://localhost:2375 ``` `boot2docker init` will ask you to enter an ssh key passphrase - the simplest diff --git a/docs/sources/installation/ubuntulinux.md b/docs/sources/installation/ubuntulinux.md index bd32802bb..78a0679e5 100644 --- a/docs/sources/installation/ubuntulinux.md +++ b/docs/sources/installation/ubuntulinux.md @@ -259,9 +259,9 @@ Then reload UFW: UFW's default set of rules denies all incoming traffic. If you want to be able to reach your containers from another host then you should allow -incoming connections on the Docker port (default 4243): +incoming connections on the Docker port (default 2375): - $ sudo ufw allow 4243/tcp + $ sudo ufw allow 2375/tcp ## Docker and local DNS server warnings diff --git a/docs/sources/reference/api/docker_remote_api_v1.0.md b/docs/sources/reference/api/docker_remote_api_v1.0.md index d719ca27e..ba5338c5f 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.0.md +++ b/docs/sources/reference/api/docker_remote_api_v1.0.md @@ -7,7 +7,7 @@ page_keywords: API, Docker, rcli, REST, documentation # 1. Brief introduction - The Remote API is replacing rcli -- Default port in the docker daemon is 4243 +- Default port in the docker daemon is 2375 - The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr diff --git a/docs/sources/reference/api/docker_remote_api_v1.1.md b/docs/sources/reference/api/docker_remote_api_v1.1.md index 21997e548..b884ee69d 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.1.md +++ b/docs/sources/reference/api/docker_remote_api_v1.1.md @@ -7,7 +7,7 @@ page_keywords: API, Docker, rcli, REST, documentation # 1. Brief introduction - The Remote API is replacing rcli -- Default port in the docker daemon is 4243 +- Default port in the docker daemon is 2375 - The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr diff --git a/docs/sources/reference/api/docker_remote_api_v1.10.md b/docs/sources/reference/api/docker_remote_api_v1.10.md index 336edfbe5..8676833af 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.10.md +++ b/docs/sources/reference/api/docker_remote_api_v1.10.md @@ -1297,4 +1297,4 @@ stdout and stderr on the same socket. This might change in the future. To enable cross origin requests to the remote api add the flag "–api-enable-cors" when running docker in daemon mode. - $ docker -d -H="192.168.1.9:4243" --api-enable-cors + $ docker -d -H="192.168.1.9:2375" --api-enable-cors diff --git a/docs/sources/reference/api/docker_remote_api_v1.11.md b/docs/sources/reference/api/docker_remote_api_v1.11.md index 4133deaea..b77b33b09 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.11.md +++ b/docs/sources/reference/api/docker_remote_api_v1.11.md @@ -1358,4 +1358,4 @@ stdout and stderr on the same socket. This might change in the future. To enable cross origin requests to the remote api add the flag "–api-enable-cors" when running docker in daemon mode. - $ docker -d -H="192.168.1.9:4243" --api-enable-cors + $ docker -d -H="192.168.1.9:2375" --api-enable-cors diff --git a/docs/sources/reference/api/docker_remote_api_v1.12.md b/docs/sources/reference/api/docker_remote_api_v1.12.md index 23afa36bc..1bc1591d7 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.12.md +++ b/docs/sources/reference/api/docker_remote_api_v1.12.md @@ -1370,4 +1370,4 @@ stdout and stderr on the same socket. This might change in the future. To enable cross origin requests to the remote api add the flag "–api-enable-cors" when running docker in daemon mode. - $ docker -d -H="192.168.1.9:4243" --api-enable-cors + $ docker -d -H="192.168.1.9:2375" --api-enable-cors diff --git a/docs/sources/reference/api/docker_remote_api_v1.2.md b/docs/sources/reference/api/docker_remote_api_v1.2.md index 17967eab3..e231cff02 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.2.md +++ b/docs/sources/reference/api/docker_remote_api_v1.2.md @@ -7,7 +7,7 @@ page_keywords: API, Docker, rcli, REST, documentation # 1. Brief introduction - The Remote API is replacing rcli -- Default port in the docker daemon is 4243 +- Default port in the docker daemon is 2375 - The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr @@ -999,5 +999,5 @@ stdout and stderr on the same socket. This might change in the future. To enable cross origin requests to the remote api add the flag "–api-enable-cors" when running docker in daemon mode. -> docker -d -H="[tcp://192.168.1.9:4243](tcp://192.168.1.9:4243)" +> docker -d -H="[tcp://192.168.1.9:2375](tcp://192.168.1.9:2375)" > –api-enable-cors diff --git a/docs/sources/reference/api/docker_remote_api_v1.3.md b/docs/sources/reference/api/docker_remote_api_v1.3.md index 9f7bd22e3..71c70273f 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.3.md +++ b/docs/sources/reference/api/docker_remote_api_v1.3.md @@ -7,7 +7,7 @@ page_keywords: API, Docker, rcli, REST, documentation # 1. Brief introduction - The Remote API is replacing rcli -- Default port in the docker daemon is 4243 +- Default port in the docker daemon is 2375 - The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr @@ -1081,4 +1081,4 @@ stdout and stderr on the same socket. This might change in the future. To enable cross origin requests to the remote api add the flag "–api-enable-cors" when running docker in daemon mode. -> docker -d -H="192.168.1.9:4243" –api-enable-cors +> docker -d -H="192.168.1.9:2375" –api-enable-cors diff --git a/docs/sources/reference/api/docker_remote_api_v1.4.md b/docs/sources/reference/api/docker_remote_api_v1.4.md index 2e7e94f7d..253944fd9 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.4.md +++ b/docs/sources/reference/api/docker_remote_api_v1.4.md @@ -7,7 +7,7 @@ page_keywords: API, Docker, rcli, REST, documentation # 1. Brief introduction - The Remote API is replacing rcli -- Default port in the docker daemon is 4243 +- Default port in the docker daemon is 2375 - The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr @@ -1127,4 +1127,4 @@ stdout and stderr on the same socket. This might change in the future. To enable cross origin requests to the remote api add the flag "–api-enable-cors" when running docker in daemon mode. - $ docker -d -H="192.168.1.9:4243" --api-enable-cors + $ docker -d -H="192.168.1.9:2375" --api-enable-cors diff --git a/docs/sources/reference/api/docker_remote_api_v1.5.md b/docs/sources/reference/api/docker_remote_api_v1.5.md index 08457bfd9..7dc5334f4 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.5.md +++ b/docs/sources/reference/api/docker_remote_api_v1.5.md @@ -7,7 +7,7 @@ page_keywords: API, Docker, rcli, REST, documentation # 1. Brief introduction - The Remote API is replacing rcli -- Default port in the docker daemon is 4243 +- Default port in the docker daemon is 2375 - The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr @@ -1134,4 +1134,4 @@ stdout and stderr on the same socket. This might change in the future. To enable cross origin requests to the remote api add the flag "–api-enable-cors" when running docker in daemon mode. - $ docker -d -H="192.168.1.9:4243" --api-enable-cors + $ docker -d -H="192.168.1.9:2375" --api-enable-cors diff --git a/docs/sources/reference/api/docker_remote_api_v1.6.md b/docs/sources/reference/api/docker_remote_api_v1.6.md index cd5b949c6..021a357b7 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.6.md +++ b/docs/sources/reference/api/docker_remote_api_v1.6.md @@ -1235,4 +1235,4 @@ stdout and stderr on the same socket. This might change in the future. To enable cross origin requests to the remote api add the flag "–api-enable-cors" when running docker in daemon mode. - $ docker -d -H="192.168.1.9:4243" --api-enable-cors + $ docker -d -H="192.168.1.9:2375" --api-enable-cors diff --git a/docs/sources/reference/api/docker_remote_api_v1.7.md b/docs/sources/reference/api/docker_remote_api_v1.7.md index 3ad2a729c..02073e9b6 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.7.md +++ b/docs/sources/reference/api/docker_remote_api_v1.7.md @@ -1229,4 +1229,4 @@ stdout and stderr on the same socket. This might change in the future. To enable cross origin requests to the remote api add the flag "–api-enable-cors" when running docker in daemon mode. - $ docker -d -H="192.168.1.9:4243" --api-enable-cors + $ docker -d -H="192.168.1.9:2375" --api-enable-cors diff --git a/docs/sources/reference/api/docker_remote_api_v1.8.md b/docs/sources/reference/api/docker_remote_api_v1.8.md index 597922e1f..a691930c6 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.8.md +++ b/docs/sources/reference/api/docker_remote_api_v1.8.md @@ -1275,4 +1275,4 @@ stdout and stderr on the same socket. This might change in the future. To enable cross origin requests to the remote api add the flag "–api-enable-cors" when running docker in daemon mode. - $ docker -d -H="192.168.1.9:4243" --api-enable-cors + $ docker -d -H="192.168.1.9:2375" --api-enable-cors diff --git a/docs/sources/reference/api/docker_remote_api_v1.9.md b/docs/sources/reference/api/docker_remote_api_v1.9.md index 2030959f9..d67443fd2 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.9.md +++ b/docs/sources/reference/api/docker_remote_api_v1.9.md @@ -1312,4 +1312,4 @@ stdout and stderr on the same socket. This might change in the future. To enable cross origin requests to the remote api add the flag "–api-enable-cors" when running docker in daemon mode. - $ docker -d -H="192.168.1.9:4243" --api-enable-cors + $ docker -d -H="192.168.1.9:2375" --api-enable-cors diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 829f13b9a..f9c7ad424 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -103,9 +103,9 @@ To use lxc as the execution driver, use `docker -d -e lxc`. The docker client will also honor the `DOCKER_HOST` environment variable to set the `-H` flag for the client. - $ docker -H tcp://0.0.0.0:4243 ps + $ docker -H tcp://0.0.0.0:2375 ps # or - $ export DOCKER_HOST="tcp://0.0.0.0:4243" + $ export DOCKER_HOST="tcp://0.0.0.0:2375" $ docker ps # both are equal diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 7f6271a38..f00023584 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -683,12 +683,12 @@ func TestBuildRelativeWorkdir(t *testing.T) { func TestBuildEnv(t *testing.T) { name := "testbuildenv" - expected := "[HOME=/ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PORT=4243]" + expected := "[HOME=/ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PORT=2375]" defer deleteImages(name) _, err := buildImage(name, `FROM busybox - ENV PORT 4243 - RUN [ $(env | grep PORT) = 'PORT=4243' ]`, + ENV PORT 2375 + RUN [ $(env | grep PORT) = 'PORT=2375' ]`, true) if err != nil { t.Fatal(err) @@ -726,11 +726,11 @@ func TestBuildCmd(t *testing.T) { func TestBuildExpose(t *testing.T) { name := "testbuildexpose" - expected := "map[4243/tcp:map[]]" + expected := "map[2375/tcp:map[]]" defer deleteImages(name) _, err := buildImage(name, `FROM scratch - EXPOSE 4243`, + EXPOSE 2375`, true) if err != nil { t.Fatal(err) diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index f91d5c2a6..8c02cf043 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -545,7 +545,7 @@ func TestBuildInheritance(t *testing.T) { img, err := buildImage(testContextTemplate{` from {IMAGE} - expose 4243 + expose 2375 `, nil, nil}, t, eng, true) @@ -569,7 +569,7 @@ func TestBuildInheritance(t *testing.T) { } // from parent - if _, exists := img.Config.ExposedPorts[nat.NewPort("tcp", "4243")]; !exists { + if _, exists := img.Config.ExposedPorts[nat.NewPort("tcp", "2375")]; !exists { t.Fail() } } diff --git a/utils/utils_test.go b/utils/utils_test.go index 83164c68d..63d722ed0 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -302,7 +302,7 @@ func TestParseHost(t *testing.T) { if addr, err := ParseHost(defaultHttpHost, defaultUnix, "udp://127.0.0.1"); err == nil { t.Errorf("udp protocol address expected error return, but err == nil. Got %s", addr) } - if addr, err := ParseHost(defaultHttpHost, defaultUnix, "udp://127.0.0.1:4243"); err == nil { + if addr, err := ParseHost(defaultHttpHost, defaultUnix, "udp://127.0.0.1:2375"); err == nil { t.Errorf("udp protocol address expected error return, but err == nil. Got %s", addr) } } From d96eb47dee10a059436b4d4cf285db1d70468e26 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Tue, 3 Jun 2014 17:10:59 -0400 Subject: [PATCH 359/400] Added note in manpages to clarify effect of docker start on started container When run on a container that is already running, docker always succeeds. This was an intentional change in v0.10; it previously caused an error. This commit clarifies the man pages to indicate this is intended behavior. Docker-DCO-1.1-Signed-off-by: Matthew Heon (github: mheon) --- contrib/man/md/docker-start.1.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contrib/man/md/docker-start.1.md b/contrib/man/md/docker-start.1.md index 9e639bbb5..2815f1b07 100644 --- a/contrib/man/md/docker-start.1.md +++ b/contrib/man/md/docker-start.1.md @@ -20,6 +20,10 @@ the process **-i**, **--interactive**=*true*|*false* When true attach to container's stdin +# NOTES +If run on a started container, start takes no action and succeeds +unconditionally. + # HISTORY April 2014, Originally compiled by William Henry (whenry at redhat dot com) based on docker.io source material and internal work. From 1ef3ca83d8624aaaaed05cfce1f71282d70d84dd Mon Sep 17 00:00:00 2001 From: unclejack Date: Tue, 3 Jun 2014 21:49:34 +0300 Subject: [PATCH 360/400] apparmor: write & load the profile on every start Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- daemon/execdriver/native/driver.go | 7 +++--- pkg/apparmor/setup.go | 34 +----------------------------- 2 files changed, 4 insertions(+), 37 deletions(-) diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index c84dbf7dc..ab34481dc 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -21,9 +21,8 @@ import ( ) const ( - DriverName = "native" - Version = "0.2" - BackupApparmorProfilePath = "apparmor/docker.back" // relative to docker root + DriverName = "native" + Version = "0.2" ) func init() { @@ -72,7 +71,7 @@ func NewDriver(root, initPath string) (*driver, error) { } // native driver root is at docker_root/execdriver/native. Put apparmor at docker_root - if err := apparmor.InstallDefaultProfile(filepath.Join(root, "../..", BackupApparmorProfilePath)); err != nil { + if err := apparmor.InstallDefaultProfile(); err != nil { return nil, err } diff --git a/pkg/apparmor/setup.go b/pkg/apparmor/setup.go index ef6333a01..8ed543747 100644 --- a/pkg/apparmor/setup.go +++ b/pkg/apparmor/setup.go @@ -2,7 +2,6 @@ package apparmor import ( "fmt" - "io" "os" "os/exec" "path" @@ -12,42 +11,11 @@ const ( DefaultProfilePath = "/etc/apparmor.d/docker" ) -func InstallDefaultProfile(backupPath string) error { +func InstallDefaultProfile() error { if !IsEnabled() { return nil } - // If the profile already exists, check if we already have a backup - // if not, do the backup and override it. (docker 0.10 upgrade changed the apparmor profile) - // see gh#5049, apparmor blocks signals in ubuntu 14.04 - if _, err := os.Stat(DefaultProfilePath); err == nil { - if _, err := os.Stat(backupPath); err == nil { - // If both the profile and the backup are present, do nothing - return nil - } - // Make sure the directory exists - if err := os.MkdirAll(path.Dir(backupPath), 0755); err != nil { - return err - } - - // Create the backup file - f, err := os.Create(backupPath) - if err != nil { - return err - } - defer f.Close() - - src, err := os.Open(DefaultProfilePath) - if err != nil { - return err - } - defer src.Close() - - if _, err := io.Copy(f, src); err != nil { - return err - } - } - // Make sure /etc/apparmor.d exists if err := os.MkdirAll(path.Dir(DefaultProfilePath), 0755); err != nil { return err From 534521301c81bcc536446351cf4018f02d477d2a Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 3 Jun 2014 15:15:11 -0700 Subject: [PATCH 361/400] Don't rejoin the cgroup each time Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/cgroups/fs/freezer.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg/libcontainer/cgroups/fs/freezer.go b/pkg/libcontainer/cgroups/fs/freezer.go index ed57f8ba9..432018fd3 100644 --- a/pkg/libcontainer/cgroups/fs/freezer.go +++ b/pkg/libcontainer/cgroups/fs/freezer.go @@ -12,18 +12,20 @@ type freezerGroup struct { } func (s *freezerGroup) Set(d *data) error { - dir, err := d.join("freezer") - if err != nil { - if err != cgroups.ErrNotFound { + switch d.c.Freezer { + case cgroups.Frozen, cgroups.Thawed: + dir, err := d.path("freezer") + if err != nil { return err } - return nil - } - if d.c.Freezer != cgroups.Undefined { if err := writeFile(dir, "freezer.state", string(d.c.Freezer)); err != nil { return err } + default: + if _, err := d.join("freezer"); err != nil && err != cgroups.ErrNotFound { + return err + } } return nil From 7a60b9063c109892f60165adb71682988d39d3d8 Mon Sep 17 00:00:00 2001 From: unclejack Date: Wed, 4 Jun 2014 02:46:55 +0300 Subject: [PATCH 362/400] fix diffs->diff typo in aufs.go Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- daemon/graphdriver/aufs/aufs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/graphdriver/aufs/aufs.go b/daemon/graphdriver/aufs/aufs.go index 3757ec56d..79de4cda7 100644 --- a/daemon/graphdriver/aufs/aufs.go +++ b/daemon/graphdriver/aufs/aufs.go @@ -7,7 +7,7 @@ aufs driver directory structure │   ├── 1 │   ├── 2 │   └── 3 -├── diffs // Content of the layer +├── diff // Content of the layer │   ├── 1 // Contains layers that need to be mounted for the id │   ├── 2 │   └── 3 From dc21dd2634791d272b8e39493815bb81eaba5605 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 4 Jun 2014 00:25:07 +0000 Subject: [PATCH 363/400] implement wait on freeze Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- pkg/libcontainer/cgroups/fs/apply_raw.go | 5 +++++ pkg/libcontainer/cgroups/fs/freezer.go | 12 ++++++++++++ .../cgroups/systemd/apply_systemd.go | 17 ++++++++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/pkg/libcontainer/cgroups/fs/apply_raw.go b/pkg/libcontainer/cgroups/fs/apply_raw.go index 8fa25997f..2231ab9a3 100644 --- a/pkg/libcontainer/cgroups/fs/apply_raw.go +++ b/pkg/libcontainer/cgroups/fs/apply_raw.go @@ -164,6 +164,11 @@ func writeFile(dir, file, data string) error { return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700) } +func readFile(dir, file string) (string, error) { + data, err := ioutil.ReadFile(filepath.Join(dir, file)) + return string(data), err +} + func removePath(p string, err error) error { if err != nil { return err diff --git a/pkg/libcontainer/cgroups/fs/freezer.go b/pkg/libcontainer/cgroups/fs/freezer.go index 432018fd3..5c9ba5b54 100644 --- a/pkg/libcontainer/cgroups/fs/freezer.go +++ b/pkg/libcontainer/cgroups/fs/freezer.go @@ -4,6 +4,7 @@ import ( "io/ioutil" "path/filepath" "strings" + "time" "github.com/dotcloud/docker/pkg/libcontainer/cgroups" ) @@ -22,6 +23,17 @@ func (s *freezerGroup) Set(d *data) error { if err := writeFile(dir, "freezer.state", string(d.c.Freezer)); err != nil { return err } + + for { + state, err := readFile(dir, "freezer.state") + if err != nil { + return err + } + if strings.TrimSpace(state) == string(d.c.Freezer) { + break + } + time.Sleep(1 * time.Millisecond) + } default: if _, err := d.join("freezer"); err != nil && err != cgroups.ErrNotFound { return err diff --git a/pkg/libcontainer/cgroups/systemd/apply_systemd.go b/pkg/libcontainer/cgroups/systemd/apply_systemd.go index eb1425b84..c486dbeb9 100644 --- a/pkg/libcontainer/cgroups/systemd/apply_systemd.go +++ b/pkg/libcontainer/cgroups/systemd/apply_systemd.go @@ -3,6 +3,7 @@ package systemd import ( + "bytes" "fmt" "io/ioutil" "os" @@ -10,6 +11,7 @@ import ( "strconv" "strings" "sync" + "time" systemd1 "github.com/coreos/go-systemd/dbus" "github.com/dotcloud/docker/pkg/libcontainer/cgroups" @@ -351,7 +353,20 @@ func Freeze(c *cgroups.Cgroup, state cgroups.FreezerState) error { return err } - return ioutil.WriteFile(filepath.Join(path, "freezer.state"), []byte(state), 0) + if err := ioutil.WriteFile(filepath.Join(path, "freezer.state"), []byte(state), 0); err != nil { + return err + } + for { + state_, err := ioutil.ReadFile(filepath.Join(path, "freezer.state")) + if err != nil { + return err + } + if string(state) == string(bytes.TrimSpace(state_)) { + break + } + time.Sleep(1 * time.Millisecond) + } + return nil } func GetPids(c *cgroups.Cgroup) ([]int, error) { From 2e14f0f70af2ce152b531f02c4fce363b6605e37 Mon Sep 17 00:00:00 2001 From: Chris Snow Date: Wed, 4 Jun 2014 08:08:44 +0100 Subject: [PATCH 364/400] Update builder.md --- docs/sources/reference/builder.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index 4e92ae7f6..c8af26c5d 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -154,7 +154,7 @@ have changed. This will also invalidate the cache for `RUN` instructions. CMD has three forms: -- `CMD ["executable","param1","param2"]` (like an *exec*, preferred form) +- `CMD ["executable","param1","param2"]` (like an *exec*, this is the preferred form) - `CMD ["param1","param2"]` (as *default parameters to ENTRYPOINT*) - `CMD command param1 param2` (as a *shell*) From eb3e2f4f4736eaaab260804ff8543fb77243a254 Mon Sep 17 00:00:00 2001 From: kies Date: Wed, 4 Jun 2014 17:54:06 +0800 Subject: [PATCH 365/400] pass address of metaHeaders to GetenvJson in ImagePush and ImagePull Docker-DCO-1.1-Signed-off-by: kies (github: kieslee) --- server/server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/server.go b/server/server.go index 507f32688..c1b4b4323 100644 --- a/server/server.go +++ b/server/server.go @@ -1381,7 +1381,7 @@ func (srv *Server) ImagePull(job *engine.Job) engine.Status { } job.GetenvJson("authConfig", authConfig) - job.GetenvJson("metaHeaders", metaHeaders) + job.GetenvJson("metaHeaders", &metaHeaders) c, err := srv.poolAdd("pull", localName+":"+tag) if err != nil { @@ -1611,7 +1611,7 @@ func (srv *Server) ImagePush(job *engine.Job) engine.Status { tag := job.Getenv("tag") job.GetenvJson("authConfig", authConfig) - job.GetenvJson("metaHeaders", metaHeaders) + job.GetenvJson("metaHeaders", &metaHeaders) if _, err := srv.poolAdd("push", localName); err != nil { return job.Error(err) } From b054569cde788b2111ddbc4080b215dcda89f06e Mon Sep 17 00:00:00 2001 From: Ian Main Date: Wed, 21 May 2014 15:06:18 -0600 Subject: [PATCH 366/400] Add ability to pause/unpause containers via cgroups freeze This patch adds pause/unpause to the command line, api, and drivers for use on containers. This is implemented using the cgroups/freeze utility in libcontainer and lxc freeze/unfreeze. Co-Authored-By: Eric Windisch Co-Authored-By: Chris Alfonso Docker-DCO-1.1-Signed-off-by: Ian Main (github: imain) --- api/client/commands.go | 48 +++++++++++++++++++ api/server/server.go | 32 +++++++++++++ daemon/container.go | 25 ++++++++++ daemon/daemon.go | 18 +++++++ daemon/execdriver/driver.go | 2 + daemon/execdriver/lxc/driver.go | 24 ++++++++++ .../execdriver/native/configuration/parse.go | 11 +++++ daemon/execdriver/native/driver.go | 26 ++++++++++ daemon/state.go | 24 ++++++++++ server/server.go | 38 +++++++++++++++ 10 files changed, 248 insertions(+) diff --git a/api/client/commands.go b/api/client/commands.go index bf1b979f6..c55e6e59b 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -65,6 +65,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { {"login", "Register or Login to the docker registry server"}, {"logs", "Fetch the logs of a container"}, {"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"}, + {"pause", "Pause all processes within a container"}, {"ps", "List containers"}, {"pull", "Pull an image or a repository from the docker registry server"}, {"push", "Push an image or a repository to the docker registry server"}, @@ -78,6 +79,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { {"stop", "Stop a running container"}, {"tag", "Tag an image into a repository"}, {"top", "Lookup the running processes of a container"}, + {"unpause", "Unpause a paused container"}, {"version", "Show the docker version information"}, {"wait", "Block until a container stops, then print its exit code"}, } { @@ -648,6 +650,52 @@ func (cli *DockerCli) CmdStart(args ...string) error { return nil } +func (cli *DockerCli) CmdUnpause(args ...string) error { + cmd := cli.Subcmd("unpause", "CONTAINER", "Unpause all processes within a container") + if err := cmd.Parse(args); err != nil { + return nil + } + + if cmd.NArg() != 1 { + cmd.Usage() + return nil + } + + var encounteredError error + for _, name := range cmd.Args() { + if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/unpause", name), nil, false)); err != nil { + fmt.Fprintf(cli.err, "%s\n", err) + encounteredError = fmt.Errorf("Error: failed to unpause container named %s", name) + } else { + fmt.Fprintf(cli.out, "%s\n", name) + } + } + return encounteredError +} + +func (cli *DockerCli) CmdPause(args ...string) error { + cmd := cli.Subcmd("pause", "CONTAINER", "Pause all processes within a container") + if err := cmd.Parse(args); err != nil { + return nil + } + + if cmd.NArg() != 1 { + cmd.Usage() + return nil + } + + var encounteredError error + for _, name := range cmd.Args() { + if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/pause", name), nil, false)); err != nil { + fmt.Fprintf(cli.err, "%s\n", err) + encounteredError = fmt.Errorf("Error: failed to pause container named %s", name) + } else { + fmt.Fprintf(cli.out, "%s\n", name) + } + } + return encounteredError +} + func (cli *DockerCli) CmdInspect(args ...string) error { cmd := cli.Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container/image") tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template.") diff --git a/api/server/server.go b/api/server/server.go index 43b2c6232..a2c02f57f 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -165,6 +165,36 @@ func postContainersKill(eng *engine.Engine, version version.Version, w http.Resp return nil } +func postContainersPause(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + if err := parseForm(r); err != nil { + return err + } + job := eng.Job("pause", vars["name"]) + if err := job.Run(); err != nil { + return err + } + w.WriteHeader(http.StatusNoContent) + return nil +} + +func postContainersUnpause(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + if err := parseForm(r); err != nil { + return err + } + job := eng.Job("unpause", vars["name"]) + if err := job.Run(); err != nil { + return err + } + w.WriteHeader(http.StatusNoContent) + return nil +} + func getContainersExport(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") @@ -1087,6 +1117,8 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st "/images/{name:.*}/tag": postImagesTag, "/containers/create": postContainersCreate, "/containers/{name:.*}/kill": postContainersKill, + "/containers/{name:.*}/pause": postContainersPause, + "/containers/{name:.*}/unpause": postContainersUnpause, "/containers/{name:.*}/restart": postContainersRestart, "/containers/{name:.*}/start": postContainersStart, "/containers/{name:.*}/stop": postContainersStop, diff --git a/daemon/container.go b/daemon/container.go index 3bdaf849d..9492e5341 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -544,6 +544,26 @@ func (container *Container) KillSig(sig int) error { return container.daemon.Kill(container, sig) } +func (container *Container) Pause() error { + if container.State.IsPaused() { + return fmt.Errorf("Container %s is already paused", container.ID) + } + if !container.State.IsRunning() { + return fmt.Errorf("Container %s is not running", container.ID) + } + return container.daemon.Pause(container) +} + +func (container *Container) Unpause() error { + if !container.State.IsPaused() { + return fmt.Errorf("Container %s is not paused", container.ID) + } + if !container.State.IsRunning() { + return fmt.Errorf("Container %s is not running", container.ID) + } + return container.daemon.Unpause(container) +} + func (container *Container) Kill() error { if !container.State.IsRunning() { return nil @@ -574,6 +594,11 @@ func (container *Container) Stop(seconds int) error { return nil } + // We could unpause the container for them rather than returning this error + if container.State.IsPaused() { + return fmt.Errorf("Container %s is paused. Unpause the container before stopping", container.ID) + } + // 1. Send a SIGTERM if err := container.KillSig(15); err != nil { log.Print("Failed to send SIGTERM to the process, force killing") diff --git a/daemon/daemon.go b/daemon/daemon.go index 50869baad..ac9c99e61 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1014,6 +1014,24 @@ func (daemon *Daemon) Run(c *Container, pipes *execdriver.Pipes, startCallback e return daemon.execDriver.Run(c.command, pipes, startCallback) } +func (daemon *Daemon) Pause(c *Container) error { + err := daemon.execDriver.Pause(c.command) + if err != nil { + return err + } + c.State.SetPaused() + return nil +} + +func (daemon *Daemon) Unpause(c *Container) error { + err := daemon.execDriver.Unpause(c.command) + if err != nil { + return err + } + c.State.SetUnpaused() + return nil +} + func (daemon *Daemon) Kill(c *Container, sig int) error { return daemon.execDriver.Kill(c.command, sig) } diff --git a/daemon/execdriver/driver.go b/daemon/execdriver/driver.go index b2af92d08..a5c5c814d 100644 --- a/daemon/execdriver/driver.go +++ b/daemon/execdriver/driver.go @@ -83,6 +83,8 @@ type TtyTerminal interface { type Driver interface { Run(c *Command, pipes *Pipes, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code Kill(c *Command, sig int) error + Pause(c *Command) error + Unpause(c *Command) error Name() string // Driver name Info(id string) Info // "temporary" hack (until we move state from core to plugins) GetPidsForContainer(id string) ([]int, error) // Returns a list of pids for the given container. diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index e1b476358..8f785e8a8 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -218,6 +218,30 @@ func (d *driver) Kill(c *execdriver.Command, sig int) error { return KillLxc(c.ID, sig) } +func (d *driver) Pause(c *execdriver.Command) error { + _, err := exec.LookPath("lxc-freeze") + if err == nil { + output, errExec := exec.Command("lxc-freeze", "-n", c.ID).CombinedOutput() + if errExec != nil { + return fmt.Errorf("Err: %s Output: %s", errExec, output) + } + } + + return err +} + +func (d *driver) Unpause(c *execdriver.Command) error { + _, err := exec.LookPath("lxc-unfreeze") + if err == nil { + output, errExec := exec.Command("lxc-unfreeze", "-n", c.ID).CombinedOutput() + if errExec != nil { + return fmt.Errorf("Err: %s Output: %s", errExec, output) + } + } + + return err +} + func (d *driver) Terminate(c *execdriver.Command) error { return KillLxc(c.ID, 9) } diff --git a/daemon/execdriver/native/configuration/parse.go b/daemon/execdriver/native/configuration/parse.go index f18a60f79..3dffb4ed5 100644 --- a/daemon/execdriver/native/configuration/parse.go +++ b/daemon/execdriver/native/configuration/parse.go @@ -27,6 +27,7 @@ var actions = map[string]Action{ "cgroups.memory_reservation": memoryReservation, // set the memory reservation "cgroups.memory_swap": memorySwap, // set the memory swap limit "cgroups.cpuset.cpus": cpusetCpus, // set the cpus used + "cgroups.freezer": freezer, // set the frozen/thaw state "systemd.slice": systemdSlice, // set parent Slice used for systemd unit @@ -35,6 +36,16 @@ var actions = map[string]Action{ "fs.readonly": readonlyFs, // make the rootfs of the container read only } +func freezer(container *libcontainer.Container, context interface{}, value string) error { + if container.Cgroups == nil { + return fmt.Errorf("cannot set cgroups when they are disabled") + } + + container.Cgroups.Freezer = value + + return nil +} + func cpusetCpus(container *libcontainer.Container, context interface{}, value string) error { if container.Cgroups == nil { return fmt.Errorf("cannot set cgroups when they are disabled") diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index ab34481dc..5889d5b28 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -145,6 +145,32 @@ func (d *driver) Kill(p *execdriver.Command, sig int) error { return syscall.Kill(p.Process.Pid, syscall.Signal(sig)) } +func (d *driver) Pause(c *execdriver.Command) error { + active := d.activeContainers[c.ID] + active.container.Cgroups.Freezer = "FROZEN" + pid := c.Process.Pid + + if systemd.UseSystemd() { + _, err := systemd.Apply(active.container.Cgroups, pid) + return err + } + _, err := fs.Apply(active.container.Cgroups, pid) + return err +} + +func (d *driver) Unpause(c *execdriver.Command) error { + active := d.activeContainers[c.ID] + active.container.Cgroups.Freezer = "THAWED" + pid := c.Process.Pid + + if systemd.UseSystemd() { + _, err := systemd.Apply(active.container.Cgroups, pid) + return err + } + _, err := fs.Apply(active.container.Cgroups, pid) + return err +} + func (d *driver) Terminate(p *execdriver.Command) error { // lets check the start time for the process started, err := d.readStartTime(p) diff --git a/daemon/state.go b/daemon/state.go index c0ed9516e..7ee8fc48c 100644 --- a/daemon/state.go +++ b/daemon/state.go @@ -11,6 +11,7 @@ import ( type State struct { sync.RWMutex Running bool + Paused bool Pid int ExitCode int StartedAt time.Time @@ -23,6 +24,9 @@ func (s *State) String() string { defer s.RUnlock() if s.Running { + if s.Paused { + return fmt.Sprintf("Up %s (Paused)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) + } return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) } if s.FinishedAt.IsZero() { @@ -50,6 +54,7 @@ func (s *State) SetRunning(pid int) { defer s.Unlock() s.Running = true + s.Paused = false s.ExitCode = 0 s.Pid = pid s.StartedAt = time.Now().UTC() @@ -64,3 +69,22 @@ func (s *State) SetStopped(exitCode int) { s.FinishedAt = time.Now().UTC() s.ExitCode = exitCode } + +func (s *State) SetPaused() { + s.Lock() + defer s.Unlock() + s.Paused = true +} + +func (s *State) SetUnpaused() { + s.Lock() + defer s.Unlock() + s.Paused = false +} + +func (s *State) IsPaused() bool { + s.RLock() + defer s.RUnlock() + + return s.Paused +} diff --git a/server/server.go b/server/server.go index 507f32688..0c5fe9eb0 100644 --- a/server/server.go +++ b/server/server.go @@ -125,6 +125,8 @@ func InitServer(job *engine.Job) engine.Status { "restart": srv.ContainerRestart, "start": srv.ContainerStart, "kill": srv.ContainerKill, + "pause": srv.ContainerPause, + "unpause": srv.ContainerUnpause, "wait": srv.ContainerWait, "tag": srv.ImageTag, // FIXME merge with "image_tag" "resize": srv.ContainerResize, @@ -168,6 +170,42 @@ func InitServer(job *engine.Job) engine.Status { return engine.StatusOK } +func (srv *Server) ContainerPause(job *engine.Job) engine.Status { + if n := len(job.Args); n < 1 || n > 2 { + return job.Errorf("Usage: %s CONTAINER", job.Name) + } + var ( + name = job.Args[0] + ) + + if container := srv.daemon.Get(name); container != nil { + if err := container.Pause(); err != nil { + return job.Errorf("Cannot pause container %s: %s", name, err) + } + } else { + return job.Errorf("No such container: %s", name) + } + return engine.StatusOK +} + +func (srv *Server) ContainerUnpause(job *engine.Job) engine.Status { + if n := len(job.Args); n < 1 || n > 2 { + return job.Errorf("Usage: %s CONTAINER", job.Name) + } + var ( + name = job.Args[0] + ) + + if container := srv.daemon.Get(name); container != nil { + if err := container.Unpause(); err != nil { + return job.Errorf("Cannot unpause container %s: %s", name, err) + } + } else { + return job.Errorf("No such container: %s", name) + } + return engine.StatusOK +} + // ContainerKill send signal to the container // If no signal is given (sig 0), then Kill with SIGKILL and wait // for the container to exit. From 26246ebd5379a83b2ed656668bd985c652e98167 Mon Sep 17 00:00:00 2001 From: Chris Alfonso Date: Tue, 3 Jun 2014 09:44:21 -0600 Subject: [PATCH 367/400] Integrating systemd freeze functionality. This pulls together #6061 and #6125 Docker-DCO-1.1-Signed-off-by: Chris Alfonso (github: calfonso) --- daemon/container.go | 10 +++--- daemon/daemon.go | 6 ++-- .../execdriver/native/configuration/parse.go | 11 ------- daemon/execdriver/native/driver.go | 30 ++++++++--------- server/server.go | 32 ++++++++----------- 5 files changed, 34 insertions(+), 55 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index 9492e5341..bb02b37f5 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -538,6 +538,11 @@ func (container *Container) KillSig(sig int) error { container.Lock() defer container.Unlock() + // We could unpause the container for them rather than returning this error + if container.State.IsPaused() { + return fmt.Errorf("Container %s is paused. Unpause the container before stopping", container.ID) + } + if !container.State.IsRunning() { return nil } @@ -594,11 +599,6 @@ func (container *Container) Stop(seconds int) error { return nil } - // We could unpause the container for them rather than returning this error - if container.State.IsPaused() { - return fmt.Errorf("Container %s is paused. Unpause the container before stopping", container.ID) - } - // 1. Send a SIGTERM if err := container.KillSig(15); err != nil { log.Print("Failed to send SIGTERM to the process, force killing") diff --git a/daemon/daemon.go b/daemon/daemon.go index ac9c99e61..8879d019e 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1015,8 +1015,7 @@ func (daemon *Daemon) Run(c *Container, pipes *execdriver.Pipes, startCallback e } func (daemon *Daemon) Pause(c *Container) error { - err := daemon.execDriver.Pause(c.command) - if err != nil { + if err := daemon.execDriver.Pause(c.command); err != nil { return err } c.State.SetPaused() @@ -1024,8 +1023,7 @@ func (daemon *Daemon) Pause(c *Container) error { } func (daemon *Daemon) Unpause(c *Container) error { - err := daemon.execDriver.Unpause(c.command) - if err != nil { + if err := daemon.execDriver.Unpause(c.command); err != nil { return err } c.State.SetUnpaused() diff --git a/daemon/execdriver/native/configuration/parse.go b/daemon/execdriver/native/configuration/parse.go index 3dffb4ed5..f18a60f79 100644 --- a/daemon/execdriver/native/configuration/parse.go +++ b/daemon/execdriver/native/configuration/parse.go @@ -27,7 +27,6 @@ var actions = map[string]Action{ "cgroups.memory_reservation": memoryReservation, // set the memory reservation "cgroups.memory_swap": memorySwap, // set the memory swap limit "cgroups.cpuset.cpus": cpusetCpus, // set the cpus used - "cgroups.freezer": freezer, // set the frozen/thaw state "systemd.slice": systemdSlice, // set parent Slice used for systemd unit @@ -36,16 +35,6 @@ var actions = map[string]Action{ "fs.readonly": readonlyFs, // make the rootfs of the container read only } -func freezer(container *libcontainer.Container, context interface{}, value string) error { - if container.Cgroups == nil { - return fmt.Errorf("cannot set cgroups when they are disabled") - } - - container.Cgroups.Freezer = value - - return nil -} - func cpusetCpus(container *libcontainer.Container, context interface{}, value string) error { if container.Cgroups == nil { return fmt.Errorf("cannot set cgroups when they are disabled") diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index 5889d5b28..a57c466f2 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -147,28 +147,26 @@ func (d *driver) Kill(p *execdriver.Command, sig int) error { func (d *driver) Pause(c *execdriver.Command) error { active := d.activeContainers[c.ID] - active.container.Cgroups.Freezer = "FROZEN" - pid := c.Process.Pid - - if systemd.UseSystemd() { - _, err := systemd.Apply(active.container.Cgroups, pid) - return err + if active == nil { + return fmt.Errorf("active container for %s does not exist", c.ID) } - _, err := fs.Apply(active.container.Cgroups, pid) - return err + active.container.Cgroups.Freezer = "FROZEN" + if systemd.UseSystemd() { + return systemd.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer) + } + return fs.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer) } func (d *driver) Unpause(c *execdriver.Command) error { active := d.activeContainers[c.ID] - active.container.Cgroups.Freezer = "THAWED" - pid := c.Process.Pid - - if systemd.UseSystemd() { - _, err := systemd.Apply(active.container.Cgroups, pid) - return err + if active == nil { + return fmt.Errorf("active container for %s does not exist", c.ID) } - _, err := fs.Apply(active.container.Cgroups, pid) - return err + active.container.Cgroups.Freezer = "THAWED" + if systemd.UseSystemd() { + return systemd.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer) + } + return fs.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer) } func (d *driver) Terminate(p *execdriver.Command) error { diff --git a/server/server.go b/server/server.go index 0c5fe9eb0..9ca3f133b 100644 --- a/server/server.go +++ b/server/server.go @@ -171,20 +171,17 @@ func InitServer(job *engine.Job) engine.Status { } func (srv *Server) ContainerPause(job *engine.Job) engine.Status { - if n := len(job.Args); n < 1 || n > 2 { + if len(job.Args) != 1 { return job.Errorf("Usage: %s CONTAINER", job.Name) } - var ( - name = job.Args[0] - ) - - if container := srv.daemon.Get(name); container != nil { - if err := container.Pause(); err != nil { - return job.Errorf("Cannot pause container %s: %s", name, err) - } - } else { + name := job.Args[0] + container := srv.daemon.Get(name) + if container == nil { return job.Errorf("No such container: %s", name) } + if err := container.Pause(); err != nil { + return job.Errorf("Cannot pause container %s: %s", name, err) + } return engine.StatusOK } @@ -192,17 +189,14 @@ func (srv *Server) ContainerUnpause(job *engine.Job) engine.Status { if n := len(job.Args); n < 1 || n > 2 { return job.Errorf("Usage: %s CONTAINER", job.Name) } - var ( - name = job.Args[0] - ) - - if container := srv.daemon.Get(name); container != nil { - if err := container.Unpause(); err != nil { - return job.Errorf("Cannot unpause container %s: %s", name, err) - } - } else { + name := job.Args[0] + container := srv.daemon.Get(name) + if container == nil { return job.Errorf("No such container: %s", name) } + if err := container.Unpause(); err != nil { + return job.Errorf("Cannot unpause container %s: %s", name, err) + } return engine.StatusOK } From 8d63b610c39eb34b06f275b507c482460015531e Mon Sep 17 00:00:00 2001 From: Vishnu Kannan Date: Wed, 4 Jun 2014 06:41:03 +0000 Subject: [PATCH 368/400] Add stats for memory allocation failure count and instantaneous cpu usage in the usermode and kernelmode. Docker-DCO-1.1-Signed-off-by: Vishnu Kannan (github: vishh) --- pkg/libcontainer/cgroups/fs/cpuacct.go | 44 ++++++++++++---------- pkg/libcontainer/cgroups/fs/memory.go | 5 +++ pkg/libcontainer/cgroups/fs/memory_test.go | 4 +- pkg/libcontainer/cgroups/stats.go | 6 +++ 4 files changed, 39 insertions(+), 20 deletions(-) diff --git a/pkg/libcontainer/cgroups/fs/cpuacct.go b/pkg/libcontainer/cgroups/fs/cpuacct.go index dc17fdf83..5ea01dc94 100644 --- a/pkg/libcontainer/cgroups/fs/cpuacct.go +++ b/pkg/libcontainer/cgroups/fs/cpuacct.go @@ -20,6 +20,8 @@ var ( clockTicks = uint64(system.GetClockTicks()) ) +const nanosecondsInSecond = 1000000000 + type cpuacctGroup struct { } @@ -37,13 +39,13 @@ func (s *cpuacctGroup) Remove(d *data) error { func (s *cpuacctGroup) GetStats(d *data, stats *cgroups.Stats) error { var ( - startCpu, lastCpu, startSystem, lastSystem, startUsage, lastUsage uint64 - percentage uint64 + startCpu, lastCpu, startSystem, lastSystem, startUsage, lastUsage, kernelModeUsage, userModeUsage, percentage uint64 ) path, err := d.path("cpuacct") - if startCpu, err = s.getCpuUsage(d, path); err != nil { + if kernelModeUsage, userModeUsage, err = s.getCpuUsage(d, path); err != nil { return err } + startCpu = kernelModeUsage + userModeUsage if startSystem, err = s.getSystemCpuUsage(d); err != nil { return err } @@ -53,9 +55,10 @@ func (s *cpuacctGroup) GetStats(d *data, stats *cgroups.Stats) error { } // sample for 100ms time.Sleep(100 * time.Millisecond) - if lastCpu, err = s.getCpuUsage(d, path); err != nil { + if kernelModeUsage, userModeUsage, err = s.getCpuUsage(d, path); err != nil { return err } + lastCpu = kernelModeUsage + userModeUsage if lastSystem, err = s.getSystemCpuUsage(d); err != nil { return err } @@ -82,6 +85,8 @@ func (s *cpuacctGroup) GetStats(d *data, stats *cgroups.Stats) error { return err } stats.CpuStats.CpuUsage.PercpuUsage = percpuUsage + stats.CpuStats.CpuUsage.UsageInKernelmode = (kernelModeUsage * nanosecondsInSecond) / clockTicks + stats.CpuStats.CpuUsage.UsageInUsermode = (userModeUsage * nanosecondsInSecond) / clockTicks return nil } @@ -119,24 +124,25 @@ func (s *cpuacctGroup) getSystemCpuUsage(d *data) (uint64, error) { return 0, fmt.Errorf("invalid stat format") } -func (s *cpuacctGroup) getCpuUsage(d *data, path string) (uint64, error) { - cpuTotal := uint64(0) - f, err := os.Open(filepath.Join(path, "cpuacct.stat")) +func (s *cpuacctGroup) getCpuUsage(d *data, path string) (uint64, uint64, error) { + kernelModeUsage := uint64(0) + userModeUsage := uint64(0) + data, err := ioutil.ReadFile(filepath.Join(path, "cpuacct.stat")) if err != nil { - return 0, err + return 0, 0, err + } + fields := strings.Fields(string(data)) + if len(fields) != 4 { + return 0, 0, fmt.Errorf("Failure - %s is expected to have 4 fields", filepath.Join(path, "cpuacct.stat")) + } + if userModeUsage, err = strconv.ParseUint(fields[1], 10, 64); err != nil { + return 0, 0, err + } + if kernelModeUsage, err = strconv.ParseUint(fields[3], 10, 64); err != nil { + return 0, 0, err } - defer f.Close() - sc := bufio.NewScanner(f) - for sc.Scan() { - _, v, err := getCgroupParamKeyValue(sc.Text()) - if err != nil { - return 0, err - } - // set the raw data in map - cpuTotal += v - } - return cpuTotal, nil + return kernelModeUsage, userModeUsage, nil } func (s *cpuacctGroup) getPercpuUsage(path string) ([]uint64, error) { diff --git a/pkg/libcontainer/cgroups/fs/memory.go b/pkg/libcontainer/cgroups/fs/memory.go index f202b1600..202ddc8ed 100644 --- a/pkg/libcontainer/cgroups/fs/memory.go +++ b/pkg/libcontainer/cgroups/fs/memory.go @@ -84,6 +84,11 @@ func (s *memoryGroup) GetStats(d *data, stats *cgroups.Stats) error { return err } stats.MemoryStats.MaxUsage = value + value, err = getCgroupParamInt(path, "memory.failcnt") + if err != nil { + return err + } + stats.MemoryStats.Failcnt = value return nil } diff --git a/pkg/libcontainer/cgroups/fs/memory_test.go b/pkg/libcontainer/cgroups/fs/memory_test.go index e7d201871..29aea1f21 100644 --- a/pkg/libcontainer/cgroups/fs/memory_test.go +++ b/pkg/libcontainer/cgroups/fs/memory_test.go @@ -11,6 +11,7 @@ const ( rss 1024` memoryUsageContents = "2048\n" memoryMaxUsageContents = "4096\n" + memoryFailcnt = "100\n" ) func TestMemoryStats(t *testing.T) { @@ -20,6 +21,7 @@ func TestMemoryStats(t *testing.T) { "memory.stat": memoryStatContents, "memory.usage_in_bytes": memoryUsageContents, "memory.max_usage_in_bytes": memoryMaxUsageContents, + "memory.failcnt": memoryFailcnt, }) memory := &memoryGroup{} @@ -27,7 +29,7 @@ func TestMemoryStats(t *testing.T) { if err != nil { t.Fatal(err) } - expectedStats := cgroups.MemoryStats{Usage: 2048, MaxUsage: 4096, Stats: map[string]uint64{"cache": 512, "rss": 1024}} + expectedStats := cgroups.MemoryStats{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Stats: map[string]uint64{"cache": 512, "rss": 1024}} expectMemoryStatEquals(t, expectedStats, actualStats.MemoryStats) } diff --git a/pkg/libcontainer/cgroups/stats.go b/pkg/libcontainer/cgroups/stats.go index eddf0ee0b..20a5f00a3 100644 --- a/pkg/libcontainer/cgroups/stats.go +++ b/pkg/libcontainer/cgroups/stats.go @@ -15,6 +15,10 @@ type CpuUsage struct { // nanoseconds of cpu time consumed over the last 100 ms. CurrentUsage uint64 `json:"current_usage,omitempty"` PercpuUsage []uint64 `json:"percpu_usage,omitempty"` + // Time spent by tasks of the cgroup in kernel mode. Units: nanoseconds. + UsageInKernelmode uint64 `json:"usage_in_kernelmode"` + // Time spent by tasks of the cgroup in user mode. Units: nanoseconds. + UsageInUsermode uint64 `json:"usage_in_usermode"` } type CpuStats struct { @@ -30,6 +34,8 @@ type MemoryStats struct { // TODO(vishh): Export these as stronger types. // all the stats exported via memory.stat. Stats map[string]uint64 `json:"stats,omitempty"` + // number of times memory usage hits limits. + Failcnt uint64 `json:"failcnt"` } type BlkioStatEntry struct { From 91757eaeb3523679b30afee61c1dede33c8664c9 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 4 Jun 2014 15:11:11 -0700 Subject: [PATCH 369/400] Remove the API container struct and use env This change touches docker logs, attach, and start -a along with get exit code in the client. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- api/client/commands.go | 114 ++++++++++++++++++++++++----------------- api/client/utils.go | 11 ++-- api/container.go | 18 ------- 3 files changed, 73 insertions(+), 70 deletions(-) delete mode 100644 api/container.go diff --git a/api/client/commands.go b/api/client/commands.go index bf1b979f6..d7eb4884b 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -560,10 +560,14 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal { func (cli *DockerCli) CmdStart(args ...string) error { var ( + cErr chan error + tty bool + cmd = cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container") attach = cmd.Bool([]string{"a", "-attach"}, false, "Attach container's stdout/stderr and forward all signals to the process") openStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's stdin") ) + if err := cmd.Parse(args); err != nil { return nil } @@ -572,29 +576,24 @@ func (cli *DockerCli) CmdStart(args ...string) error { return nil } - var ( - cErr chan error - tty bool - ) if *attach || *openStdin { if cmd.NArg() > 1 { return fmt.Errorf("You cannot start and attach multiple containers at once.") } - body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false)) + steam, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false) if err != nil { return err } - container := &api.Container{} - err = json.Unmarshal(body, container) - if err != nil { + env := engine.Env{} + if err := env.Decode(steam); err != nil { return err } + config := env.GetSubEnv("Config") + tty = config.GetBool("Tty") - tty = container.Config.Tty - - if !container.Config.Tty { + if !tty { sigc := cli.forwardAllSignals(cmd.Arg(0)) defer signal.StopCatch(sigc) } @@ -603,15 +602,17 @@ func (cli *DockerCli) CmdStart(args ...string) error { v := url.Values{} v.Set("stream", "1") - if *openStdin && container.Config.OpenStdin { + + if *openStdin && config.GetBool("OpenStdin") { v.Set("stdin", "1") in = cli.in } + v.Set("stdout", "1") v.Set("stderr", "1") cErr = utils.Go(func() error { - return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, in, cli.out, cli.err, nil) + return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, nil) }) } @@ -774,34 +775,38 @@ func (cli *DockerCli) CmdPort(args ...string) error { } var ( - port = cmd.Arg(1) - proto = "tcp" - parts = strings.SplitN(port, "/", 2) - container api.Container + port = cmd.Arg(1) + proto = "tcp" + parts = strings.SplitN(port, "/", 2) ) if len(parts) == 2 && len(parts[1]) != 0 { port = parts[0] proto = parts[1] } - body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false)) + + steam, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false) if err != nil { return err } - err = json.Unmarshal(body, &container) - if err != nil { + env := engine.Env{} + if err := env.Decode(steam); err != nil { + return err + } + ports := nat.PortMap{} + if err := env.GetSubEnv("NetworkSettings").GetJson("Ports", &ports); err != nil { return err } - if frontends, exists := container.NetworkSettings.Ports[nat.Port(port+"/"+proto)]; exists && frontends != nil { + if frontends, exists := ports[nat.Port(port+"/"+proto)]; exists && frontends != nil { for _, frontend := range frontends { fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort) } - } else { - return fmt.Errorf("Error: No public port '%s' published for %s", cmd.Arg(1), cmd.Arg(0)) + return nil } - return nil + + return fmt.Errorf("Error: No public port '%s' published for %s", cmd.Arg(1), cmd.Arg(0)) } // 'docker rmi IMAGE' removes all images with the name IMAGE @@ -1619,72 +1624,84 @@ func (cli *DockerCli) CmdDiff(args ...string) error { } func (cli *DockerCli) CmdLogs(args ...string) error { - cmd := cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container") - follow := cmd.Bool([]string{"f", "-follow"}, false, "Follow log output") - times := cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps") + var ( + cmd = cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container") + follow = cmd.Bool([]string{"f", "-follow"}, false, "Follow log output") + times = cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps") + ) + if err := cmd.Parse(args); err != nil { return nil } + if cmd.NArg() != 1 { cmd.Usage() return nil } name := cmd.Arg(0) - body, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, false)) + + steam, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false) if err != nil { return err } - container := &api.Container{} - err = json.Unmarshal(body, container) - if err != nil { + env := engine.Env{} + if err := env.Decode(steam); err != nil { return err } v := url.Values{} v.Set("stdout", "1") v.Set("stderr", "1") + if *times { v.Set("timestamps", "1") } - if *follow && container.State.Running { + + if *follow { v.Set("follow", "1") } - if err := cli.streamHelper("GET", "/containers/"+name+"/logs?"+v.Encode(), container.Config.Tty, nil, cli.out, cli.err, nil); err != nil { - return err - } - return nil + return cli.streamHelper("GET", "/containers/"+name+"/logs?"+v.Encode(), env.GetSubEnv("Config").GetBool("Tty"), nil, cli.out, cli.err, nil) } func (cli *DockerCli) CmdAttach(args ...string) error { - cmd := cli.Subcmd("attach", "[OPTIONS] CONTAINER", "Attach to a running container") - noStdin := cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach stdin") - proxy := cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxify all received signal to the process (even in non-tty mode)") + var ( + cmd = cli.Subcmd("attach", "[OPTIONS] CONTAINER", "Attach to a running container") + noStdin = cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach stdin") + proxy = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxify all received signal to the process (even in non-tty mode)") + ) + if err := cmd.Parse(args); err != nil { return nil } + if cmd.NArg() != 1 { cmd.Usage() return nil } name := cmd.Arg(0) - body, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, false)) + + stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false) if err != nil { return err } - container := &api.Container{} - err = json.Unmarshal(body, container) - if err != nil { + env := engine.Env{} + if err := env.Decode(stream); err != nil { return err } - if !container.State.Running { + if !env.GetSubEnv("State").GetBool("Running") { return fmt.Errorf("You cannot attach to a stopped container, start it first") } - if container.Config.Tty && cli.isTerminal { + var ( + config = env.GetSubEnv("Config") + tty = config.GetBool("Tty") + ) + + if tty && cli.isTerminal { if err := cli.monitorTtySize(cmd.Arg(0)); err != nil { utils.Debugf("Error monitoring TTY size: %s", err) } @@ -1694,19 +1711,20 @@ func (cli *DockerCli) CmdAttach(args ...string) error { v := url.Values{} v.Set("stream", "1") - if !*noStdin && container.Config.OpenStdin { + if !*noStdin && config.GetBool("OpenStdin") { v.Set("stdin", "1") in = cli.in } + v.Set("stdout", "1") v.Set("stderr", "1") - if *proxy && !container.Config.Tty { + if *proxy && !tty { sigc := cli.forwardAllSignals(cmd.Arg(0)) defer signal.StopCatch(sigc) } - if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, in, cli.out, cli.err, nil); err != nil { + if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, nil); err != nil { return err } diff --git a/api/client/utils.go b/api/client/utils.go index 8f303dcd9..262068370 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -200,7 +200,7 @@ func waitForExit(cli *DockerCli, containerId string) (int, error) { // getExitCode perform an inspect on the container. It returns // the running state and the exit code. func getExitCode(cli *DockerCli, containerId string) (bool, int, error) { - body, _, err := readBody(cli.call("GET", "/containers/"+containerId+"/json", nil, false)) + steam, _, err := cli.call("GET", "/containers/"+containerId+"/json", nil, false) if err != nil { // If we can't connect, then the daemon probably died. if err != ErrConnectionRefused { @@ -208,11 +208,14 @@ func getExitCode(cli *DockerCli, containerId string) (bool, int, error) { } return false, -1, nil } - c := &api.Container{} - if err := json.Unmarshal(body, c); err != nil { + + var result engine.Env + if err := result.Decode(steam); err != nil { return false, -1, err } - return c.State.Running, c.State.ExitCode, nil + + state := result.GetSubEnv("State") + return state.GetBool("Running"), state.GetInt("ExitCode"), nil } func (cli *DockerCli) monitorTtySize(id string) error { diff --git a/api/container.go b/api/container.go deleted file mode 100644 index 4cc73b225..000000000 --- a/api/container.go +++ /dev/null @@ -1,18 +0,0 @@ -package api - -import ( - "github.com/dotcloud/docker/nat" - "github.com/dotcloud/docker/runconfig" -) - -type Container struct { - Config runconfig.Config - HostConfig runconfig.HostConfig - State struct { - Running bool - ExitCode int - } - NetworkSettings struct { - Ports nat.PortMap - } -} From 8aff01c0b447fa4d68f053c44e8baf7b24247164 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 4 Jun 2014 15:47:57 -0700 Subject: [PATCH 370/400] Rename nsinit package to namespaces in libcontainer Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/execdriver/native/driver.go | 16 ++++++++-------- .../{nsinit => namespaces}/create.go | 2 +- pkg/libcontainer/{nsinit => namespaces}/exec.go | 2 +- .../{nsinit => namespaces}/execin.go | 2 +- pkg/libcontainer/{nsinit => namespaces}/init.go | 2 +- pkg/libcontainer/{nsinit => namespaces}/pid.go | 2 +- .../{nsinit => namespaces}/std_term.go | 2 +- .../{nsinit => namespaces}/sync_pipe.go | 2 +- pkg/libcontainer/{nsinit => namespaces}/term.go | 2 +- .../{nsinit => namespaces}/tty_term.go | 2 +- .../{nsinit => namespaces}/unsupported.go | 2 +- pkg/libcontainer/nsinit/{nsinit => }/main.go | 16 ++++++++-------- 12 files changed, 26 insertions(+), 26 deletions(-) rename pkg/libcontainer/{nsinit => namespaces}/create.go (92%) rename pkg/libcontainer/{nsinit => namespaces}/exec.go (99%) rename pkg/libcontainer/{nsinit => namespaces}/execin.go (99%) rename pkg/libcontainer/{nsinit => namespaces}/init.go (99%) rename pkg/libcontainer/{nsinit => namespaces}/pid.go (97%) rename pkg/libcontainer/{nsinit => namespaces}/std_term.go (97%) rename pkg/libcontainer/{nsinit => namespaces}/sync_pipe.go (98%) rename pkg/libcontainer/{nsinit => namespaces}/term.go (95%) rename pkg/libcontainer/{nsinit => namespaces}/tty_term.go (98%) rename pkg/libcontainer/{nsinit => namespaces}/unsupported.go (97%) rename pkg/libcontainer/nsinit/{nsinit => }/main.go (83%) diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index a57c466f2..41121a70f 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -16,7 +16,7 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/cgroups/fs" "github.com/dotcloud/docker/pkg/libcontainer/cgroups/systemd" - "github.com/dotcloud/docker/pkg/libcontainer/nsinit" + "github.com/dotcloud/docker/pkg/libcontainer/namespaces" "github.com/dotcloud/docker/pkg/system" ) @@ -42,11 +42,11 @@ func init() { if err != nil { return err } - syncPipe, err := nsinit.NewSyncPipeFromFd(0, uintptr(args.Pipe)) + syncPipe, err := namespaces.NewSyncPipeFromFd(0, uintptr(args.Pipe)) if err != nil { return err } - if err := nsinit.Init(container, rootfs, args.Console, syncPipe, args.Args); err != nil { + if err := namespaces.Init(container, rootfs, args.Console, syncPipe, args.Args); err != nil { return err } return nil @@ -110,8 +110,8 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba term := getTerminal(c, pipes) - return nsinit.Exec(container, term, c.Rootfs, dataPath, args, func(container *libcontainer.Container, console, rootfs, dataPath, init string, child *os.File, args []string) *exec.Cmd { - // we need to join the rootfs because nsinit will setup the rootfs and chroot + return namespaces.Exec(container, term, c.Rootfs, dataPath, args, func(container *libcontainer.Container, console, rootfs, dataPath, init string, child *os.File, args []string) *exec.Cmd { + // we need to join the rootfs because namespaces will setup the rootfs and chroot initPath := filepath.Join(c.Rootfs, c.InitPath) c.Path = d.initPath @@ -126,7 +126,7 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba // set this to nil so that when we set the clone flags anything else is reset c.SysProcAttr = nil - system.SetCloneFlags(&c.Cmd, uintptr(nsinit.GetNamespaceFlags(container.Namespaces))) + system.SetCloneFlags(&c.Cmd, uintptr(namespaces.GetNamespaceFlags(container.Namespaces))) c.ExtraFiles = []*os.File{child} c.Env = container.Env @@ -258,8 +258,8 @@ func getEnv(key string, env []string) string { return "" } -func getTerminal(c *execdriver.Command, pipes *execdriver.Pipes) nsinit.Terminal { - var term nsinit.Terminal +func getTerminal(c *execdriver.Command, pipes *execdriver.Pipes) namespaces.Terminal { + var term namespaces.Terminal if c.Tty { term = &dockerTtyTerm{ pipes: pipes, diff --git a/pkg/libcontainer/nsinit/create.go b/pkg/libcontainer/namespaces/create.go similarity index 92% rename from pkg/libcontainer/nsinit/create.go rename to pkg/libcontainer/namespaces/create.go index d5cba464d..60b2a2db0 100644 --- a/pkg/libcontainer/nsinit/create.go +++ b/pkg/libcontainer/namespaces/create.go @@ -1,4 +1,4 @@ -package nsinit +package namespaces import ( "os" diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/namespaces/exec.go similarity index 99% rename from pkg/libcontainer/nsinit/exec.go rename to pkg/libcontainer/namespaces/exec.go index f266303f8..288205ea6 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/namespaces/exec.go @@ -1,6 +1,6 @@ // +build linux -package nsinit +package namespaces import ( "os" diff --git a/pkg/libcontainer/nsinit/execin.go b/pkg/libcontainer/namespaces/execin.go similarity index 99% rename from pkg/libcontainer/nsinit/execin.go rename to pkg/libcontainer/namespaces/execin.go index 40b95093d..09bf40582 100644 --- a/pkg/libcontainer/nsinit/execin.go +++ b/pkg/libcontainer/namespaces/execin.go @@ -1,6 +1,6 @@ // +build linux -package nsinit +package namespaces import ( "fmt" diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/namespaces/init.go similarity index 99% rename from pkg/libcontainer/nsinit/init.go rename to pkg/libcontainer/namespaces/init.go index dc983161f..b53c56668 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/namespaces/init.go @@ -1,6 +1,6 @@ // +build linux -package nsinit +package namespaces import ( "fmt" diff --git a/pkg/libcontainer/nsinit/pid.go b/pkg/libcontainer/namespaces/pid.go similarity index 97% rename from pkg/libcontainer/nsinit/pid.go rename to pkg/libcontainer/namespaces/pid.go index bba2f10e1..8d97ec146 100644 --- a/pkg/libcontainer/nsinit/pid.go +++ b/pkg/libcontainer/namespaces/pid.go @@ -1,4 +1,4 @@ -package nsinit +package namespaces import ( "fmt" diff --git a/pkg/libcontainer/nsinit/std_term.go b/pkg/libcontainer/namespaces/std_term.go similarity index 97% rename from pkg/libcontainer/nsinit/std_term.go rename to pkg/libcontainer/namespaces/std_term.go index 2b8201a71..324336af2 100644 --- a/pkg/libcontainer/nsinit/std_term.go +++ b/pkg/libcontainer/namespaces/std_term.go @@ -1,4 +1,4 @@ -package nsinit +package namespaces import ( "io" diff --git a/pkg/libcontainer/nsinit/sync_pipe.go b/pkg/libcontainer/namespaces/sync_pipe.go similarity index 98% rename from pkg/libcontainer/nsinit/sync_pipe.go rename to pkg/libcontainer/namespaces/sync_pipe.go index d0bfdda86..e12ed447f 100644 --- a/pkg/libcontainer/nsinit/sync_pipe.go +++ b/pkg/libcontainer/namespaces/sync_pipe.go @@ -1,4 +1,4 @@ -package nsinit +package namespaces import ( "encoding/json" diff --git a/pkg/libcontainer/nsinit/term.go b/pkg/libcontainer/namespaces/term.go similarity index 95% rename from pkg/libcontainer/nsinit/term.go rename to pkg/libcontainer/namespaces/term.go index 5fc801ab5..2a50bf855 100644 --- a/pkg/libcontainer/nsinit/term.go +++ b/pkg/libcontainer/namespaces/term.go @@ -1,4 +1,4 @@ -package nsinit +package namespaces import ( "io" diff --git a/pkg/libcontainer/nsinit/tty_term.go b/pkg/libcontainer/namespaces/tty_term.go similarity index 98% rename from pkg/libcontainer/nsinit/tty_term.go rename to pkg/libcontainer/namespaces/tty_term.go index fc6e1ab49..272cf2cd6 100644 --- a/pkg/libcontainer/nsinit/tty_term.go +++ b/pkg/libcontainer/namespaces/tty_term.go @@ -1,4 +1,4 @@ -package nsinit +package namespaces import ( "io" diff --git a/pkg/libcontainer/nsinit/unsupported.go b/pkg/libcontainer/namespaces/unsupported.go similarity index 97% rename from pkg/libcontainer/nsinit/unsupported.go rename to pkg/libcontainer/namespaces/unsupported.go index 51509f79a..b459b4d2f 100644 --- a/pkg/libcontainer/nsinit/unsupported.go +++ b/pkg/libcontainer/namespaces/unsupported.go @@ -1,6 +1,6 @@ // +build !linux -package nsinit +package namespaces import ( "github.com/dotcloud/docker/pkg/libcontainer" diff --git a/pkg/libcontainer/nsinit/nsinit/main.go b/pkg/libcontainer/nsinit/main.go similarity index 83% rename from pkg/libcontainer/nsinit/nsinit/main.go rename to pkg/libcontainer/nsinit/main.go index 5d968375e..6659a1310 100644 --- a/pkg/libcontainer/nsinit/nsinit/main.go +++ b/pkg/libcontainer/nsinit/main.go @@ -13,7 +13,7 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/cgroups/fs" - "github.com/dotcloud/docker/pkg/libcontainer/nsinit" + "github.com/dotcloud/docker/pkg/libcontainer/namespaces" ) var ( @@ -40,9 +40,9 @@ func main() { } if nspid > 0 { - exitCode, err = nsinit.ExecIn(container, nspid, os.Args[2:]) + exitCode, err = namespaces.ExecIn(container, nspid, os.Args[2:]) } else { - term := nsinit.NewTerminal(os.Stdin, os.Stdout, os.Stderr, container.Tty) + term := namespaces.NewTerminal(os.Stdin, os.Stdout, os.Stderr, container.Tty) exitCode, err = startContainer(container, term, dataPath, os.Args[2:]) } @@ -61,12 +61,12 @@ func main() { if err != nil { log.Fatal(err) } - syncPipe, err := nsinit.NewSyncPipeFromFd(0, uintptr(pipeFd)) + syncPipe, err := namespaces.NewSyncPipeFromFd(0, uintptr(pipeFd)) if err != nil { log.Fatalf("unable to create sync pipe: %s", err) } - if err := nsinit.Init(container, rootfs, console, syncPipe, os.Args[2:]); err != nil { + if err := namespaces.Init(container, rootfs, console, syncPipe, os.Args[2:]); err != nil { log.Fatalf("unable to initialize for container: %s", err) } case "stats": @@ -124,7 +124,7 @@ func readPid() (int, error) { // error. // // Signals sent to the current process will be forwarded to container. -func startContainer(container *libcontainer.Container, term nsinit.Terminal, dataPath string, args []string) (int, error) { +func startContainer(container *libcontainer.Container, term namespaces.Terminal, dataPath string, args []string) (int, error) { var ( cmd *exec.Cmd sigc = make(chan os.Signal, 10) @@ -133,7 +133,7 @@ func startContainer(container *libcontainer.Container, term nsinit.Terminal, dat signal.Notify(sigc) createCommand := func(container *libcontainer.Container, console, rootfs, dataPath, init string, pipe *os.File, args []string) *exec.Cmd { - cmd = nsinit.DefaultCreateCommand(container, console, rootfs, dataPath, init, pipe, args) + cmd = namespaces.DefaultCreateCommand(container, console, rootfs, dataPath, init, pipe, args) return cmd } @@ -145,7 +145,7 @@ func startContainer(container *libcontainer.Container, term nsinit.Terminal, dat }() } - return nsinit.Exec(container, term, "", dataPath, args, createCommand, startCallback) + return namespaces.Exec(container, term, "", dataPath, args, createCommand, startCallback) } // returns the container stats in json format. From 2819677c215bd409b4ce4da51f0ddfb303760796 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sun, 1 Jun 2014 16:48:04 -0400 Subject: [PATCH 371/400] Initial links for Docker Hub rename Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- docs/mkdocs.yml | 16 ++--- docs/s3_website.json | 3 +- docs/sources/articles/basics.md | 4 +- docs/sources/docker-hub/accounts.md | 31 ++++++++++ .../{docker-io => docker-hub}/builds.md | 58 +++++++++++-------- .../sources/{docker-io => docker-hub}/home.md | 10 ++-- .../{docker-io => docker-hub}/index.md | 2 +- .../{docker-io => docker-hub}/repos.md | 19 +++--- docs/sources/docker-io/accounts.md | 32 ---------- docs/sources/examples/mongodb.md | 15 +++-- docs/sources/examples/nodejs_web_app.md | 6 +- docs/sources/examples/postgresql_service.md | 2 +- docs/sources/examples/running_riak_service.md | 4 +- docs/sources/faq.md | 2 +- docs/sources/index.md | 2 +- .../introduction/understanding-docker.md | 18 +++--- docs/sources/reference/api.md | 2 +- docs/sources/reference/api/docker-io_api.md | 10 ++-- .../reference/api/docker_remote_api_v1.0.md | 2 +- .../reference/api/docker_remote_api_v1.1.md | 2 +- .../reference/api/docker_remote_api_v1.10.md | 2 +- .../reference/api/docker_remote_api_v1.11.md | 2 +- .../reference/api/docker_remote_api_v1.12.md | 2 +- .../reference/api/docker_remote_api_v1.2.md | 2 +- .../reference/api/docker_remote_api_v1.3.md | 2 +- .../reference/api/docker_remote_api_v1.4.md | 2 +- .../reference/api/docker_remote_api_v1.5.md | 2 +- .../reference/api/docker_remote_api_v1.6.md | 2 +- .../reference/api/docker_remote_api_v1.7.md | 2 +- .../reference/api/docker_remote_api_v1.8.md | 2 +- .../reference/api/docker_remote_api_v1.9.md | 2 +- docs/sources/reference/commandline/cli.md | 14 ++--- docs/sources/terms/registry.md | 2 +- .../userguide/{dockerio.md => dockerhub.md} | 24 ++++---- docs/sources/userguide/dockerimages.md | 22 +++---- docs/sources/userguide/dockerizing.md | 2 +- docs/sources/userguide/dockerrepos.md | 52 ++++++++--------- docs/sources/userguide/dockervolumes.md | 4 +- docs/sources/userguide/index.md | 26 ++++----- docs/sources/userguide/usingdocker.md | 2 +- 40 files changed, 208 insertions(+), 202 deletions(-) create mode 100644 docs/sources/docker-hub/accounts.md rename docs/sources/{docker-io => docker-hub}/builds.md (78%) rename docs/sources/{docker-io => docker-hub}/home.md (54%) rename docs/sources/{docker-io => docker-hub}/index.md (87%) rename docs/sources/{docker-io => docker-hub}/repos.md (83%) delete mode 100644 docs/sources/docker-io/accounts.md rename docs/sources/userguide/{dockerio.md => dockerhub.md} (72%) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 2835cd9dd..03483abd0 100755 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -51,19 +51,19 @@ pages: # User Guide: - ['userguide/index.md', 'User Guide', 'The Docker User Guide' ] -- ['userguide/dockerio.md', 'User Guide', 'Getting Started with Docker.io' ] +- ['userguide/dockerhub.md', 'User Guide', 'Getting Started with Docker Hub' ] - ['userguide/dockerizing.md', 'User Guide', 'Dockerizing Applications' ] - ['userguide/usingdocker.md', 'User Guide', 'Working with Containers' ] - ['userguide/dockerimages.md', 'User Guide', 'Working with Docker Images' ] - ['userguide/dockerlinks.md', 'User Guide', 'Linking containers together' ] - ['userguide/dockervolumes.md', 'User Guide', 'Managing data in containers' ] -- ['userguide/dockerrepos.md', 'User Guide', 'Working with Docker.io' ] +- ['userguide/dockerrepos.md', 'User Guide', 'Working with Docker Hub' ] -# Docker.io docs: -- ['docker-io/index.md', 'Docker.io', 'Docker.io' ] -- ['docker-io/accounts.md', 'Docker.io', 'Accounts'] -- ['docker-io/repos.md', 'Docker.io', 'Repositories'] -- ['docker-io/builds.md', 'Docker.io', 'Automated Builds'] +# Docker Hub docs: +- ['docker-hub/index.md', 'Docker Hub', 'Docker Hub' ] +- ['docker-hub/accounts.md', 'Docker Hub', 'Accounts'] +- ['docker-hub/repos.md', 'Docker Hub', 'Repositories'] +- ['docker-hub/builds.md', 'Docker Hub', 'Automated Builds'] # Examples: - ['examples/index.md', '**HIDDEN**'] @@ -99,7 +99,7 @@ pages: - ['faq.md', 'Reference', 'FAQ'] - ['reference/run.md', 'Reference', 'Run Reference'] - ['reference/api/index.md', '**HIDDEN**'] -- ['reference/api/docker-io_api.md', 'Reference', 'Docker.io API'] +- ['reference/api/docker-io_api.md', 'Reference', 'Docker Hub API'] - ['reference/api/registry_api.md', 'Reference', 'Docker Registry API'] - ['reference/api/registry_index_spec.md', 'Reference', 'Registry & Index Spec'] - ['reference/api/docker_remote_api.md', 'Reference', 'Docker Remote API'] diff --git a/docs/s3_website.json b/docs/s3_website.json index eab6ae820..1e6aeea75 100644 --- a/docs/s3_website.json +++ b/docs/s3_website.json @@ -18,7 +18,8 @@ { "Condition": { "KeyPrefixEquals": "use/working_with_links_names/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "userguide/dockerlinks/" } }, { "Condition": { "KeyPrefixEquals": "use/workingwithrepository/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "userguide/dockerrepos/" } }, { "Condition": { "KeyPrefixEquals": "use/port_redirection" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "userguide/dockerlinks/" } }, - { "Condition": { "KeyPrefixEquals": "use/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "examples/" } } + { "Condition": { "KeyPrefixEquals": "use/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "examples/" } }, + { "Condition": { "KeyPrefixEquals": "docker-io/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "docker-hub/" } } ] } diff --git a/docs/sources/articles/basics.md b/docs/sources/articles/basics.md index cd4a9df65..e93156965 100644 --- a/docs/sources/articles/basics.md +++ b/docs/sources/articles/basics.md @@ -26,8 +26,8 @@ for installation instructions. $ sudo docker pull ubuntu This will find the `ubuntu` image by name on -[*Docker.io*](/userguide/dockerrepos/#find-public-images-on-dockerio) -and download it from [Docker.io](https://index.docker.io) to a local +[*Docker Hub*](/userguide/dockerrepos/#find-public-images-on-docker-hub) +and download it from [Docker Hub](https://hub.docker.com) to a local image cache. > **Note**: diff --git a/docs/sources/docker-hub/accounts.md b/docs/sources/docker-hub/accounts.md new file mode 100644 index 000000000..43df62a9b --- /dev/null +++ b/docs/sources/docker-hub/accounts.md @@ -0,0 +1,31 @@ +page_title: Accounts on Docker Hub +page_description: Docker Hub accounts +page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub, docs, documentation + +# Accounts on Docker Hub + +## Docker Hub Accounts + +You can `search` for Docker images and `pull` them from [Docker Hub](https://hub.docker.com) +without signing in or even having an account. However, in order to `push` images, +leave comments or to *star* a repository, you are going to need a [Docker Hub](https://hub.docker.com) account. + +### Registration for a Docker Hub Account + +You can get a [Docker Hub](https://hub.docker.com) account by +[signing up for one here](https://hub.docker.com/account/signup/). A valid +email address is required to register, which you will need to verify for +account activation. + +### Email activation process + +You need to have at least one verified email address to be able to use your +[Docker Hub](https://hub.docker.com) account. If you can't find the validation email, +you can request another by visiting the [Resend Email Confirmation]( +https://hub.docker.com/account/resend-email-confirmation/) page. + +### Password reset process + +If you can't access your account for some reason, you can reset your password +from the [*Password Reset*](https://hub.docker.com/account/forgot-password/) +page. diff --git a/docs/sources/docker-io/builds.md b/docs/sources/docker-hub/builds.md similarity index 78% rename from docs/sources/docker-io/builds.md rename to docs/sources/docker-hub/builds.md index c70de4006..09876479f 100644 --- a/docs/sources/docker-io/builds.md +++ b/docs/sources/docker-hub/builds.md @@ -1,13 +1,13 @@ -page_title: Automated Builds on Docker.io -page_description: Docker.io Automated Builds -page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker.io, docs, documentation, trusted, builds, trusted builds, automated, automated builds -# Automated Builds on Docker.io +page_title: Automated Builds on Docker Hub +page_description: Docker Hub Automated Builds +page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub, docs, documentation, trusted, builds, trusted builds, automated builds +# Automated Builds on Docker Hub ## Automated Builds *Automated Builds* is a special feature allowing you to specify a source repository with a `Dockerfile` to be built by the -[Docker.io](https://index.docker.io) build clusters. The system will +[Docker Hub](https://hub.docker.com) build clusters. The system will clone your repository and build the `Dockerfile` using the repository as the context. The resulting image will then be uploaded to the registry and marked as an *Automated Build*. @@ -26,27 +26,28 @@ on both [GitHub](http://github.com) and ### Setting up Automated Builds with GitHub -In order to setup an Automated Build, you need to first link your [Docker.io]( -https://index.docker.io) account with a GitHub one. This will allow the registry -to see your repositories. +In order to setup an Automated Build, you need to first link your +[Docker Hub](https://hub.docker.com) account with a GitHub one. This +will allow the registry to see your repositories. -> *Note:* We currently request access for *read* and *write* since [Docker.io]( -> https://index.docker.io) needs to setup a GitHub service hook. Although nothing -> else is done with your account, this is how GitHub manages permissions, sorry! +> *Note:* +> We currently request access for *read* and *write* since +> [Docker Hub](https://hub.docker.com) needs to setup a GitHub service +> hook. Although nothing else is done with your account, this is how +> GitHub manages permissions, sorry! -Click on the [Automated Builds tab](https://index.docker.io/builds/) to -get started and then select [+ Add -New](https://index.docker.io/builds/add/). +Click on the [Automated Builds +tab](https://registry.hub.docker.com/builds/) to get started and then +select [+ Add New](https://registry.hub.docker.com/builds/add/). -Select the [GitHub -service](https://index.docker.io/associate/github/). +Select the [GitHub service](https://registry.hub.docker.com/associate/github/). Then follow the instructions to authorize and link your GitHub account -to Docker.io. +to Docker Hub. #### Creating an Automated Build -You can [create an Automated Build](https://index.docker.io/builds/github/select/) +You can [create an Automated Build](https://registry.hub.docker.com/builds/github/select/) from any of your public or private GitHub repositories with a `Dockerfile`. #### GitHub organizations @@ -86,29 +87,36 @@ Automated Build: ### Setting up Automated Builds with BitBucket In order to setup an Automated Build, you need to first link your -[Docker.io]( https://index.docker.io) account with a BitBucket one. This +[Docker Hub](https://hub.docker.com) account with a BitBucket one. This will allow the registry to see your repositories. -Click on the [Automated Builds tab](https://index.docker.io/builds/) to +Click on the [Automated Builds tab](https://registry.hub.docker.com/builds/) to get started and then select [+ Add -New](https://index.docker.io/builds/add/). +New](https://registry.hub.docker.com/builds/add/). Select the [BitBucket -service](https://index.docker.io/associate/bitbucket/). +service](https://registry.hub.docker.com/associate/bitbucket/). Then follow the instructions to authorize and link your BitBucket account -to Docker.io. +to Docker Hub. #### Creating an Automated Build +<<<<<<< HEAD:docs/sources/docker-io/builds.md You can [create an Automated Build](https://index.docker.io/builds/bitbucket/select/) from any of your public or private BitBucket repositories with a `Dockerfile`. +======= +You can [create a Trusted +Build](https://registry.hub.docker.com/builds/bitbucket/select/) +from any of your public or private BitBucket repositories with a +`Dockerfile`. +>>>>>>> Initial links for Docker Hub rename:docs/sources/docker-hub/builds.md ### The Dockerfile and Automated Builds During the build process, we copy the contents of your `Dockerfile`. We also -add it to the [Docker.io](https://index.docker.io) for the Docker community +add it to the [Docker Hub](https://hub.docker.com) for the Docker community to see on the repository page. ### README.md @@ -163,7 +171,7 @@ payload: "description":"my docker repo that does cool things", "is_automated":false, "full_description":"This is my full description", - "repo_url":"https://index.docker.io/u/username/reponame/", + "repo_url":"https://registry.hub.docker.com/u/username/reponame/", "owner":"username", "is_official":false, "is_private":false, diff --git a/docs/sources/docker-io/home.md b/docs/sources/docker-hub/home.md similarity index 54% rename from docs/sources/docker-io/home.md rename to docs/sources/docker-hub/home.md index d29de76fb..15baf7b83 100644 --- a/docs/sources/docker-io/home.md +++ b/docs/sources/docker-hub/home.md @@ -1,13 +1,13 @@ -page_title: The Docker.io Registry Help +page_title: The Docker Hub Registry Help page_description: The Docker Registry help documentation home -page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker.io, docs, documentation +page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub, docs, documentation -# The Docker.io Registry Help +# The Docker Hub Registry Help ## Introduction -For your questions about the [Docker.io](https://index.docker.io) registry you +For your questions about the [Docker Hub](https://hub.docker.com) registry you can use [this documentation](docs.md). If you can not find something you are looking for, please feel free to -[contact us](https://index.docker.io/help/support/). \ No newline at end of file +[contact us](https://docker.com/resources/support/). diff --git a/docs/sources/docker-io/index.md b/docs/sources/docker-hub/index.md similarity index 87% rename from docs/sources/docker-io/index.md rename to docs/sources/docker-hub/index.md index dc83f0b28..250f2b3a1 100644 --- a/docs/sources/docker-io/index.md +++ b/docs/sources/docker-hub/index.md @@ -1,4 +1,4 @@ -# Docker.io +# Docker Hub ## Contents: diff --git a/docs/sources/docker-io/repos.md b/docs/sources/docker-hub/repos.md similarity index 83% rename from docs/sources/docker-io/repos.md rename to docs/sources/docker-hub/repos.md index 11170182a..5f8cb5b0e 100644 --- a/docs/sources/docker-io/repos.md +++ b/docs/sources/docker-hub/repos.md @@ -1,16 +1,15 @@ -page_title: Repositories and Images on Docker.io -page_description: Repositories and Images on Docker.io -page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker.io, docs, documentation +page_title: Repositories and Images on Docker Hub +page_description: Repositories and Images on Docker Hub +page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub, docs, documentation -# Repositories and Images on Docker.io +# Repositories and Images on Docker Hub ## Searching for repositories and images You can `search` for all the publicly available repositories and images using Docker. If a repository is not public (i.e., private), it won't be listed on the repository search results. To see repository statuses, you can look at your -[profile page](https://index.docker.io/account/) on [Docker.io]( -https://index.docker.io). +[profile page](https://hub.docker.com) on [Docker Hub](https://hub.docker.com). ## Repositories @@ -27,8 +26,8 @@ appropriate, you can flag them for the admins' review. ### Private Docker Repositories -To work with a private repository on [Docker.io](https://index.docker.io), you -will need to add one via the [Add Repository](https://index.docker.io/account/repositories/add) +To work with a private repository on [Docker Hub](https://hub.docker.com), you +will need to add one via the [Add Repository](https://registry.hub.docker.com/account/repositories/add/) link. Once the private repository is created, you can `push` and `pull` images to and from it using Docker. @@ -45,7 +44,7 @@ designate (i.e., collaborators) from its settings page. From there, you can also switch repository status (*public* to *private*, or viceversa). You will need to have an available private repository slot open before you can do such a switch. If you don't have any, you can always upgrade -your [Docker.io](https://index.docker.io/plans/) plan. +your [Docker Hub](https://registry.hub.docker.com/plans/) plan. ### Collaborators and their role @@ -83,7 +82,7 @@ with a JSON payload similar to the example shown below. "description":"my docker repo that does cool things", "is_automated":false, "full_description":"This is my full description", - "repo_url":"https://index.docker.io/u/username/reponame/", + "repo_url":"https://registry.hub.docker.com/u/username/reponame/", "owner":"username", "is_official":false, "is_private":false, diff --git a/docs/sources/docker-io/accounts.md b/docs/sources/docker-io/accounts.md deleted file mode 100644 index cfbcd9512..000000000 --- a/docs/sources/docker-io/accounts.md +++ /dev/null @@ -1,32 +0,0 @@ -page_title: Accounts on Docker.io -page_description: Docker.io accounts -page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker.io, docs, documentation - -# Accounts on Docker.io - -## Docker.io Accounts - -You can `search` for Docker images and `pull` them from [Docker.io](https://index.docker.io) -without signing in or even having an account. However, in order to `push` images, -leave comments or to *star* a repository, you are going to need a [Docker.io]( -https://www.docker.io) account. - -### Registration for a Docker.io Account - -You can get a [Docker.io](https://index.docker.io) account by -[signing up for one here](https://www.docker.io/account/signup/). A valid -email address is required to register, which you will need to verify for -account activation. - -### Email activation process - -You need to have at least one verified email address to be able to use your -[Docker.io](https://index.docker.io) account. If you can't find the validation email, -you can request another by visiting the [Resend Email Confirmation]( -https://www.docker.io/account/resend-email-confirmation/) page. - -### Password reset process - -If you can't access your account for some reason, you can reset your password -from the [*Password Reset*](https://www.docker.io/account/forgot-password/) -page. \ No newline at end of file diff --git a/docs/sources/examples/mongodb.md b/docs/sources/examples/mongodb.md index 17d58e0db..602f55ca8 100644 --- a/docs/sources/examples/mongodb.md +++ b/docs/sources/examples/mongodb.md @@ -1,15 +1,14 @@ page_title: Dockerizing MongoDB -page_description: Creating a Docker image with MongoDB pre-installed using a Dockerfile and sharing the image on Docker.io +page_description: Creating a Docker image with MongoDB pre-installed using a Dockerfile and sharing the image on Docker Hub page_keywords: docker, dockerize, dockerizing, article, example, docker.io, platform, package, installation, networking, mongodb, containers, images, image, sharing, dockerfile, build, auto-building, virtualization, framework # Dockerizing MongoDB ## Introduction -In this example, we are going to learn how to build a Docker image -with MongoDB pre-installed. -We'll also see how to `push` that image to the [Docker.io registry]( -https://index.docker.io) and share it with others! +In this example, we are going to learn how to build a Docker image with +MongoDB pre-installed. We'll also see how to `push` that image to the +[Docker Hub registry](https://hub.docker.com) and share it with others! Using Docker and containers for deploying [MongoDB](https://www.mongodb.org/) instances will bring several benefits, such as: @@ -41,7 +40,7 @@ Although optional, it is handy to have comments at the beginning of a > the *parent* of your *Dockerized MongoDB* image. We will build our image using the latest version of Ubuntu from the -[Docker.io Ubuntu](https://index.docker.io/_/ubuntu/) repository. +[Docker Hub Ubuntu](https://registry.hub.docker.com/_/ubuntu/) repository. # Format: FROM repository[:version] FROM ubuntu:latest @@ -109,10 +108,10 @@ experimenting, it is always a good practice to tag Docker images by passing the Once this command is issued, Docker will go through the `Dockerfile` and build the image. The final image will be tagged `my/repo`. -## Pushing the MongoDB image to Docker.io +## Pushing the MongoDB image to Docker Hub All Docker image repositories can be hosted and shared on -[Docker.io](https://index.docker.io) with the `docker push` command. For this, +[Docker Hub](https://hub.docker.com) with the `docker push` command. For this, you need to be logged-in. # Log-in diff --git a/docs/sources/examples/nodejs_web_app.md b/docs/sources/examples/nodejs_web_app.md index 99946e99e..cf00e88be 100644 --- a/docs/sources/examples/nodejs_web_app.md +++ b/docs/sources/examples/nodejs_web_app.md @@ -65,9 +65,9 @@ requires to build (this example uses Docker 0.3.4): # DOCKER-VERSION 0.3.4 Next, define the parent image you want to use to build your own image on -top of. Here, we'll use [CentOS](https://index.docker.io/_/centos/) -(tag: `6.4`) available on the [Docker -index](https://index.docker.io/): +top of. Here, we'll use +[CentOS](https://registry.hub.docker.com/_/centos/) (tag: `6.4`) +available on the [Docker Hub](https://hub.docker.com/): FROM centos:6.4 diff --git a/docs/sources/examples/postgresql_service.md b/docs/sources/examples/postgresql_service.md index b931fd8ba..b9fae49d9 100644 --- a/docs/sources/examples/postgresql_service.md +++ b/docs/sources/examples/postgresql_service.md @@ -11,7 +11,7 @@ page_keywords: docker, example, package installation, postgresql ## Installing PostgreSQL on Docker Assuming there is no Docker image that suits your needs on the [Docker -Hub]( http://index.docker.io), you can create one yourself. +Hub](http://hub.docker.com), you can create one yourself. Start by creating a new `Dockerfile`: diff --git a/docs/sources/examples/running_riak_service.md b/docs/sources/examples/running_riak_service.md index 098cc9094..5909b7e2b 100644 --- a/docs/sources/examples/running_riak_service.md +++ b/docs/sources/examples/running_riak_service.md @@ -14,8 +14,8 @@ Create an empty file called `Dockerfile`: $ touch Dockerfile Next, define the parent image you want to use to build your image on top -of. We'll use [Ubuntu](https://index.docker.io/_/ubuntu/) (tag: -`latest`), which is available on [Docker Hub](http://index.docker.io): +of. We'll use [Ubuntu](https://registry.hub.docker.cm/_/ubuntu/) (tag: +`latest`), which is available on [Docker Hub](https://hub.docker.com): # Riak # diff --git a/docs/sources/faq.md b/docs/sources/faq.md index 8dbdfd184..fa7deb4e2 100644 --- a/docs/sources/faq.md +++ b/docs/sources/faq.md @@ -96,7 +96,7 @@ functionalities: all your future projects. And so on. - *Sharing.* - Docker has access to a [public registry](http://index.docker.io) where + Docker has access to a [public registry](https://hub.docker.com) where thousands of people have uploaded useful containers: anything from Redis, CouchDB, Postgres to IRC bouncers to Rails app servers to Hadoop to base images for various Linux distros. The diff --git a/docs/sources/index.md b/docs/sources/index.md index f7295d9c1..4a66dd1dd 100644 --- a/docs/sources/index.md +++ b/docs/sources/index.md @@ -12,7 +12,7 @@ sysadmins to develop, ship, and run applications. Docker consists of: * The Docker Engine - our lightweight and powerful open source container virtualization technology combined with a work flow to help you build and containerize your applications. -* [Docker.io](https://index.docker.io) - our SAAS service that helps you +* [Docker Hub](https://hub.docker.com) - our SAAS service that helps you share and manage your applications stacks. Docker enables applications to be quickly assembled from components and diff --git a/docs/sources/introduction/understanding-docker.md b/docs/sources/introduction/understanding-docker.md index e9041420a..e9f5a1bfe 100644 --- a/docs/sources/introduction/understanding-docker.md +++ b/docs/sources/introduction/understanding-docker.md @@ -70,7 +70,7 @@ resources you have. Docker has two major components: * Docker: the open source container virtualization platform. -* [Docker.io](https://index.docker.io): our Software-as-a-Service +* [Docker Hub](https://hub.docker.com): our Software-as-a-Service platform for sharing and managing Docker containers. **Note:** Docker is licensed with the open source Apache 2.0 license. @@ -119,7 +119,7 @@ portion of Docker. Docker registries hold images. These are public (or private!) stores that you can upload or download images to and from. The public Docker -registry is called [Docker.io](http://index.docker.io). It provides a +registry is called [Docker Hub](https://hub.docker.com). It provides a huge collection of existing images that you can use. These images can be images you create yourself or you can make use of images that others have previously created. You can consider Docker registries the @@ -142,7 +142,7 @@ We've learned so far that: 2. You can create Docker containers from those Docker images to run your applications. 3. You can share those Docker images via - [Docker.io](https://index.docker.io) or your own registry. + [Docker Hub](https://hub.docker.com) or your own registry. Let's look at how these elements combine together to make Docker work. @@ -169,7 +169,7 @@ own as the basis for a new image, for example if you have a base Apache image you could use this as the base of all your web application images. > **Note:** -> Docker usually gets these base images from [Docker.io](https://index.docker.io). +> Docker usually gets these base images from [Docker Hub](https://hub.docker.com). Docker images are then built from these base images using a simple descriptive set of steps we call *instructions*. Each instruction @@ -187,19 +187,19 @@ instructions and returns a final image. ### How does a Docker registry work? The Docker registry is the store for your Docker images. Once you build -a Docker image you can *push* it to a public registry [Docker.io]( -https://index.docker.io) or to your own registry running behind your +a Docker image you can *push* it to a public registry [Docker +Hub](https://hub.docker.com) or to your own registry running behind your firewall. Using the Docker client, you can search for already published images and then pull them down to your Docker host to build containers from them. -[Docker.io](https://index.docker.io) provides both public and +[Docker Hub](https://hub.docker.com) provides both public and private storage for images. Public storage is searchable and can be downloaded by anyone. Private storage is excluded from search results and only you and your users can pull them down and use them to build containers. You can [sign up for a plan -here](https://index.docker.io/plans). +here](https://registry.hub.docker.com/plans/). ### How does a container work? @@ -236,7 +236,7 @@ Docker begins with: - **Pulling the `ubuntu` image:** Docker checks for the presence of the `ubuntu` image and if it doesn't exist locally on the host, then Docker downloads it from - [Docker.io](https://index.docker.io). If the image already exists then + [Docker Hub](https://hub.docker.com). If the image already exists then Docker uses it for the new container. - **Creates a new container:** Once Docker has the image it creates a container from it: diff --git a/docs/sources/reference/api.md b/docs/sources/reference/api.md index 9f185a0e3..720d40b2e 100644 --- a/docs/sources/reference/api.md +++ b/docs/sources/reference/api.md @@ -42,7 +42,7 @@ interfaces: - [3 Authorization](registry_api/#authorization) - - [Docker.io API](index_api/) + - [Docker Hub API](index_api/) - [1. Brief introduction](index_api/#brief-introduction) - [2. Endpoints](index_api/#endpoints) - [2.1 Repository](index_api/#repository) diff --git a/docs/sources/reference/api/docker-io_api.md b/docs/sources/reference/api/docker-io_api.md index 66cf311b4..d5be332d3 100644 --- a/docs/sources/reference/api/docker-io_api.md +++ b/docs/sources/reference/api/docker-io_api.md @@ -1,12 +1,12 @@ -page_title: Docker.io API -page_description: API Documentation for the Docker.io API -page_keywords: API, Docker, index, REST, documentation, Docker.io, registry +page_title: Docker Hub API +page_description: API Documentation for the Docker Hub API +page_keywords: API, Docker, index, REST, documentation, Docker Hub, registry -# Docker.io API +# Docker Hub API ## Introduction -- This is the REST API for [Docker.io](http://index.docker.io). +- This is the REST API for [Docker Hub](https://hub.docker.com). - Authorization is done with basic auth over SSL - Not all commands require authentication, only those noted as such. diff --git a/docs/sources/reference/api/docker_remote_api_v1.0.md b/docs/sources/reference/api/docker_remote_api_v1.0.md index ba5338c5f..2f17b2a74 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.0.md +++ b/docs/sources/reference/api/docker_remote_api_v1.0.md @@ -734,7 +734,7 @@ Remove the image `name` from the filesystem `GET /images/search` -Search for an image on [Docker.io](https://index.docker.io) +Search for an image on [Docker Hub](https://hub.docker.com) **Example request**: diff --git a/docs/sources/reference/api/docker_remote_api_v1.1.md b/docs/sources/reference/api/docker_remote_api_v1.1.md index b884ee69d..e777901c6 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.1.md +++ b/docs/sources/reference/api/docker_remote_api_v1.1.md @@ -745,7 +745,7 @@ Remove the image `name` from the filesystem `GET /images/search` -Search for an image on [Docker.io](https://index.docker.io) +Search for an image on [Docker Hub](https://hub.docker.com) **Example request**: diff --git a/docs/sources/reference/api/docker_remote_api_v1.10.md b/docs/sources/reference/api/docker_remote_api_v1.10.md index 8676833af..35d6b039d 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.10.md +++ b/docs/sources/reference/api/docker_remote_api_v1.10.md @@ -932,7 +932,7 @@ Tag the image `name` into a repository `GET /images/search` -Search for an image on [Docker.io](https://index.docker.io). +Search for an image on [Docker Hub](https://hub.docker.com). > **Note**: > The response keys have changed from API v1.6 to reflect the JSON diff --git a/docs/sources/reference/api/docker_remote_api_v1.11.md b/docs/sources/reference/api/docker_remote_api_v1.11.md index b77b33b09..476ef61d9 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.11.md +++ b/docs/sources/reference/api/docker_remote_api_v1.11.md @@ -972,7 +972,7 @@ Remove the image `name` from the filesystem `GET /images/search` -Search for an image on [Docker.io](https://index.docker.io). +Search for an image on [Docker Hub](https://hub.docker.com). > **Note**: > The response keys have changed from API v1.6 to reflect the JSON diff --git a/docs/sources/reference/api/docker_remote_api_v1.12.md b/docs/sources/reference/api/docker_remote_api_v1.12.md index d941a1d45..e4316bfb5 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.12.md +++ b/docs/sources/reference/api/docker_remote_api_v1.12.md @@ -983,7 +983,7 @@ Remove the image `name` from the filesystem `GET /images/search` -Search for an image on [Docker.io](https://index.docker.io). +Search for an image on [Docker Hub](https://hub.docker.com). > **Note**: > The response keys have changed from API v1.6 to reflect the JSON diff --git a/docs/sources/reference/api/docker_remote_api_v1.2.md b/docs/sources/reference/api/docker_remote_api_v1.2.md index e231cff02..cecab5bb4 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.2.md +++ b/docs/sources/reference/api/docker_remote_api_v1.2.md @@ -772,7 +772,7 @@ Remove the image `name` from the filesystem `GET /images/search` -Search for an image on [Docker.io](https://index.docker.io) +Search for an image on [Docker Hub](https://hub.docker.com) **Example request**: diff --git a/docs/sources/reference/api/docker_remote_api_v1.3.md b/docs/sources/reference/api/docker_remote_api_v1.3.md index 71c70273f..1d60b4300 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.3.md +++ b/docs/sources/reference/api/docker_remote_api_v1.3.md @@ -821,7 +821,7 @@ Remove the image `name` from the filesystem `GET /images/search` -Search for an image on [Docker.io](https://index.docker.io) +Search for an image on [Docker Hub](https://hub.docker.com) **Example request**: diff --git a/docs/sources/reference/api/docker_remote_api_v1.4.md b/docs/sources/reference/api/docker_remote_api_v1.4.md index 253944fd9..f7d6e82c1 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.4.md +++ b/docs/sources/reference/api/docker_remote_api_v1.4.md @@ -866,7 +866,7 @@ Remove the image `name` from the filesystem `GET /images/search` -Search for an image on [Docker.io](https://index.docker.io) +Search for an image on [Docker Hub](https://hub.docker.com) **Example request**: diff --git a/docs/sources/reference/api/docker_remote_api_v1.5.md b/docs/sources/reference/api/docker_remote_api_v1.5.md index 7dc5334f4..53d970acc 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.5.md +++ b/docs/sources/reference/api/docker_remote_api_v1.5.md @@ -871,7 +871,7 @@ Remove the image `name` from the filesystem `GET /images/search` -Search for an image on [Docker.io](https://index.docker.io) +Search for an image on [Docker Hub](https://hub.docker.com) **Example request**: diff --git a/docs/sources/reference/api/docker_remote_api_v1.6.md b/docs/sources/reference/api/docker_remote_api_v1.6.md index 021a357b7..9b7cded33 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.6.md +++ b/docs/sources/reference/api/docker_remote_api_v1.6.md @@ -975,7 +975,7 @@ Remove the image `name` from the filesystem `GET /images/search` -Search for an image on [Docker.io](https://index.docker.io) +Search for an image on [Docker Hub](https://hub.docker.com) **Example request**: diff --git a/docs/sources/reference/api/docker_remote_api_v1.7.md b/docs/sources/reference/api/docker_remote_api_v1.7.md index 02073e9b6..3432e9bb2 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.7.md +++ b/docs/sources/reference/api/docker_remote_api_v1.7.md @@ -901,7 +901,7 @@ Remove the image `name` from the filesystem `GET /images/search` -Search for an image on [Docker.io](https://index.docker.io). +Search for an image on [Docker Hub](https://hub.docker.com). > **Note**: > The response keys have changed from API v1.6 to reflect the JSON diff --git a/docs/sources/reference/api/docker_remote_api_v1.8.md b/docs/sources/reference/api/docker_remote_api_v1.8.md index a691930c6..184e107cd 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.8.md +++ b/docs/sources/reference/api/docker_remote_api_v1.8.md @@ -943,7 +943,7 @@ Remove the image `name` from the filesystem `GET /images/search` -Search for an image on [Docker.io](https://index.docker.io). +Search for an image on [Docker Hub](https://hub.docker.com). > **Note**: > The response keys have changed from API v1.6 to reflect the JSON diff --git a/docs/sources/reference/api/docker_remote_api_v1.9.md b/docs/sources/reference/api/docker_remote_api_v1.9.md index d67443fd2..fc9f9b8d5 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.9.md +++ b/docs/sources/reference/api/docker_remote_api_v1.9.md @@ -946,7 +946,7 @@ Tag the image `name` into a repository `GET /images/search` -Search for an image on [Docker.io](https://index.docker.io). +Search for an image on [Docker Hub](https://hub.docker.com). > **Note**: > The response keys have changed from API v1.6 to reflect the JSON diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 16190ab1d..308e7d35d 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -739,9 +739,9 @@ Running `docker ps` showing 2 linked containers. Pull an image or a repository from the registry Most of your images will be created on top of a base image from the -[Docker.io](https://index.docker.io) registry. +[Docker Hub](https://hub.docker.com) registry. -[Docker.io](https://index.docker.io) contains many pre-built images that you +[Docker Hub](https://hub.docker.com) contains many pre-built images that you can `pull` and try without needing to define and configure your own. To download a particular image, or set of images (i.e., a repository), @@ -760,7 +760,7 @@ use `docker pull`: Push an image or a repository to the registry -Use `docker push` to share your images to the [Docker.io](https://index.docker.io) +Use `docker push` to share your images to the [Docker Hub](https://hub.docker.com) registry or to a self-hosted one. ## restart @@ -1116,7 +1116,7 @@ It is used to create a backup that can then be used with ## search -Search [Docker.io](https://index.docker.io) for images +Search [Docker Hub](https://hub.docker.com) for images Usage: docker search TERM @@ -1126,9 +1126,9 @@ Search [Docker.io](https://index.docker.io) for images -s, --stars=0 Only displays with at least xxx stars --automated=false Only show automated builds -See [*Find Public Images on Docker.io*]( -/userguide/dockerrepos/#find-public-images-on-dockerio) for -more details on finding shared images from the commandline. +See [*Find Public Images on Docker Hub*]( +/userguide/dockerrepos/#find-public-images-on-docker-hub) for +more details on finding shared images from the command line. ## start diff --git a/docs/sources/terms/registry.md b/docs/sources/terms/registry.md index 08f8e8f69..8a7e6237e 100644 --- a/docs/sources/terms/registry.md +++ b/docs/sources/terms/registry.md @@ -11,7 +11,7 @@ A Registry is a hosted service containing [*images*](/terms/image/#image-def) which responds to the Registry API. The default registry can be accessed using a browser at -[Docker.io](http://index.docker.io) or using the +[Docker Hub](https://hub.docker.com) or using the `sudo docker search` command. ## Further Reading diff --git a/docs/sources/userguide/dockerio.md b/docs/sources/userguide/dockerhub.md similarity index 72% rename from docs/sources/userguide/dockerio.md rename to docs/sources/userguide/dockerhub.md index e5c6c6dac..7e7acbef1 100644 --- a/docs/sources/userguide/dockerio.md +++ b/docs/sources/userguide/dockerhub.md @@ -1,15 +1,15 @@ -page_title: Getting started with Docker.io -page_description: Introductory guide to getting an account on Docker.io +page_title: Getting started with Docker Hub +page_description: Introductory guide to getting an account on Docker Hub page_keywords: documentation, docs, the docker guide, docker guide, docker, docker platform, virtualization framework, docker.io, central service, services, how to, container, containers, automation, collaboration, collaborators, registry, repo, repository, technology, github webhooks, trusted builds -# Getting Started with Docker.io +# Getting Started with Docker Hub -*How do I use Docker.io?* +*How do I use Docker Hub?* In this section we're going to introduce you, very quickly!, to -[Docker.io](https://index.docker.io) and create an account. +[Docker Hub](https://hub.docker.com) and create an account. -[Docker.io](https://www.docker.io) is the central hub for Docker. It +[Docker Hub](https://www.docker.io) is the central hub for Docker. It helps you to manage Docker and its components. It provides services such as: @@ -19,15 +19,15 @@ as: hooks. * Integration with GitHub and BitBucket. -Docker.io helps you collaborate with colleagues and get the most out of +Docker Hub helps you collaborate with colleagues and get the most out of Docker. -In order to use Docker.io you will need to register an account. Don't +In order to use Docker Hub you will need to register an account. Don't panic! It's totally free and really easy. -## Creating a Docker.io Account +## Creating a Docker Hub Account -There are two ways you can create a Docker.io account: +There are two ways you can create a Docker Hub account: * Via the web, or * Via the command line. @@ -41,7 +41,7 @@ choose your user name and specify some details such as an email address. ### Signup via the command line -You can also create a Docker.io account via the command line using the +You can also create a Docker Hub account via the command line using the `docker login` command. $ sudo docker login @@ -63,7 +63,7 @@ Or via the command line and the `docker login` command: $ sudo docker login -Now your Docker.io account is active and ready for you to use! +Now your Docker Hub account is active and ready for you to use! ## Next steps diff --git a/docs/sources/userguide/dockerimages.md b/docs/sources/userguide/dockerimages.md index bd8b0d2c2..19881c258 100644 --- a/docs/sources/userguide/dockerimages.md +++ b/docs/sources/userguide/dockerimages.md @@ -1,6 +1,6 @@ page_title: Working with Docker Images page_description: How to work with Docker images. -page_keywords: documentation, docs, the docker guide, docker guide, docker, docker platform, virtualization framework, docker.io, Docker images, Docker image, image management, Docker repos, Docker repositories, docker, docker tag, docker tags, Docker.io, collaboration +page_keywords: documentation, docs, the docker guide, docker guide, docker, docker platform, virtualization framework, docker.io, Docker images, Docker image, image management, Docker repos, Docker repositories, docker, docker tag, docker tags, Docker Hub, collaboration # Working with Docker Images @@ -13,14 +13,14 @@ image and the `training/webapp` image. We've also discovered that Docker stores downloaded images on the Docker host. If an image isn't already present on the host then it'll be downloaded from a registry: by default the -[Docker.io](https://index.docker.io) public registry. +[Docker Hub](https://hub.docker.com) public registry. In this section we're going to explore Docker images a bit more including: * Managing and working with images locally on your Docker host; * Creating basic images; -* Uploading images to [Docker.io](https://index.docker.io). +* Uploading images to [Docker Hub](https://hub.docker.com). ## Listing images on the host @@ -45,7 +45,7 @@ do this using the `docker images` command like so: ubuntu lucid 3db9c44f4520 4 weeks ago 183 MB We can see the images we've previously used in our [user guide](/userguide/). -Each has been downloaded from [Docker.io](https://index.docker.io) when we +Each has been downloaded from [Docker Hub](https://hub.docker.com) when we launched a container using that image. We can see three crucial pieces of information about our images in the listing. @@ -104,8 +104,8 @@ download the image. One of the features of Docker is that a lot of people have created Docker images for a variety of purposes. Many of these have been uploaded to -[Docker.io](https://index.docker.io). We can search these images on the -[Docker.io](https://index.docker.io) website. +[Docker Hub](https://hub.docker.com). We can search these images on the +[Docker Hub](https://hub.docker.com) website. ![indexsearch](/userguide/search.png) @@ -359,12 +359,12 @@ Let's see our new tag using the `docker images` command. ouruser/sinatra devel 5db5f8471261 11 hours ago 446.7 MB ouruser/sinatra v2 5db5f8471261 11 hours ago 446.7 MB -## Push an image to Docker.io +## Push an image to Docker Hub -Once you've built or created a new image you can push it to [Docker.io]( -https://index.docker.io) using the `docker push` command. This allows you to -share it with others, either publicly, or push it into [a private -repository](https://index.docker.io/plans/). +Once you've built or created a new image you can push it to [Docker +Hub](https://hub.docker.com) using the `docker push` command. This +allows you to share it with others, either publicly, or push it into [a +private repository](https://registry.hub.docker.com/plans/). $ sudo docker push ouruser/sinatra The push refers to a repository [ouruser/sinatra] (len: 1) diff --git a/docs/sources/userguide/dockerizing.md b/docs/sources/userguide/dockerizing.md index 79a2066c6..be3ccd444 100644 --- a/docs/sources/userguide/dockerizing.md +++ b/docs/sources/userguide/dockerizing.md @@ -30,7 +30,7 @@ operating system image. When you specify an image, Docker looks first for the image on your Docker host. If it can't find it then it downloads the image from the public -image registry: [Docker.io](https://index.docker.io). +image registry: [Docker Hub](https://hub.docker.com). Next we told Docker what command to run inside our new container: diff --git a/docs/sources/userguide/dockerrepos.md b/docs/sources/userguide/dockerrepos.md index d18ec13cc..5ebad9698 100644 --- a/docs/sources/userguide/dockerrepos.md +++ b/docs/sources/userguide/dockerrepos.md @@ -1,8 +1,8 @@ -page_title: Working with Docker.io -page_description: Learning how to use Docker.io to manage images and work flow -page_keywords: repo, Docker.io, Docker Hub, registry, index, repositories, usage, pull image, push image, image, documentation +page_title: Working with Docker Hub +page_description: Learning how to use Docker Hub to manage images and work flow +page_keywords: repo, Docker Hub, Docker Hub, registry, index, repositories, usage, pull image, push image, image, documentation -# Working with Docker.io +# Working with Docker Hub So far we've seen a lot about how to use Docker on the command line and your local host. We've seen [how to pull down @@ -10,10 +10,10 @@ images](/userguide/usingdocker/) that you can run your containers from and we've seen how to [create your own images](/userguide/dockerimages). Now we're going to learn a bit more about -[Docker.io](https://index.docker.io) and how you can use it to enhance +[Docker Hub](https://hub.docker.com) and how you can use it to enhance your Docker work flows. -[Docker.io](https://index.docker.io) is the public registry that Docker +[Docker Hub](https://hub.docker.com) is the public registry that Docker Inc maintains. It contains a huge collection of images, over 15,000, that you can download and use to build your containers. It also provides authentication, structure (you can setup teams and organizations), work @@ -21,7 +21,7 @@ flow tools like webhooks and build triggers as well as privacy features like private repositories for storing images you don't want to publicly share. -## Docker commands and Docker.io +## Docker commands and Docker Hub Docker acts as a client for these services via the `docker search`, `pull`, `login` and `push` commands. @@ -29,7 +29,7 @@ Docker acts as a client for these services via the `docker search`, ## Searching for images As we've already seen we can search the -[Docker.io](https://index.docker.io) registry via it's search interface +[Docker Hub](https://hub.docker.com) registry via it's search interface or using the command line interface. Searching can find images by name, user name or description: @@ -57,15 +57,15 @@ Once you have found the image you want, you can download it: The image is now available to run a container from. -## Contributing to Docker.io +## Contributing to Docker Hub -Anyone can pull public images from the [Docker.io](http://index.docker.io) +Anyone can pull public images from the [Docker Hub](https://hub.docker.com) registry, but if you would like to share your own images, then you must register a user first as we saw in the [first section of the Docker User -Guide](/userguide/dockerio/). +Guide](/userguide/dockerhub/). To refresh your memory, you can create your user name and login to -[Docker.io](https://index.docker.io/account/signup/), or by running: +[Docker Hub](https://hub.docker.com/account/signup/), or by running: $ sudo docker login @@ -85,7 +85,7 @@ you in. Now you're ready to commit and push your own images! > Your authentication credentials will be stored in the [`.dockercfg` > authentication file](#authentication-file) in your home directory. -## Pushing a repository to Docker.io +## Pushing a repository to Docker Hub In order to push an repository to its registry you need to have named an image, or committed your container to a named image as we saw @@ -98,9 +98,9 @@ or tag. The image will then be uploaded and available for use. -## Features of Docker.io +## Features of Docker Hub -Now let's look at some of the features of Docker.io. You can find more +Now let's look at some of the features of Docker Hub. You can find more information [here](/docker-io/). * Private repositories @@ -111,29 +111,29 @@ information [here](/docker-io/). ## Private Repositories Sometimes you have images you don't want to make public and share with -everyone. So Docker.io allows you to have private repositories. You can -sign up for a plan [here](https://index.docker.io/plans/). +everyone. So Docker Hub allows you to have private repositories. You can +sign up for a plan [here](https://registry.hub.docker.com/plans/). ## Organizations and teams One of the useful aspects of private repositories is that you can share -them only with members of your organization or team. Docker.io lets you +them only with members of your organization or team. Docker Hub lets you create organizations where you can collaborate with your colleagues and manage private repositories. You can create and manage an organization -[here](https://index.docker.io/account/organizations/). +[here](https://registry.hub.docker.com/account/organizations/). ## Automated Builds Automated Builds automate the building and updating of images from [GitHub](https://www.github.com) -or [BitBucket](http://bitbucket.com), directly on Docker.io. It works by adding a commit hook to +or [BitBucket](http://bitbucket.com), directly on Docker Hub. It works by adding a commit hook to your selected GitHub or BitBucket repository, triggering a build and update when you push a commit. ### To setup an Automated Build -1. Create a [Docker.io account](https://index.docker.io/) and login. -2. Link your GitHub or BitBucket account through the [`Link Accounts`](https://index.docker.io/account/accounts/) menu. -3. [Configure an Automated Build](https://index.docker.io/builds/). +1. Create a [Docker Hub account](https://hub.docker.com/) and login. +2. Link your GitHub or BitBucket account through the [`Link Accounts`](https://registry.hub.docker.com/account/accounts/) menu. +3. [Configure an Automated Build](https://registry.hub.docker.com/builds/). 4. Pick a GitHub or BitBucket project that has a `Dockerfile` that you want to build. 5. Pick the branch you want to build (the default is the `master` branch). 6. Give the Automated Build a name. @@ -142,12 +142,12 @@ commit. Once the Automated Build is configured it will automatically trigger a build, and in a few minutes, if there are no errors, you will see your -new Automated Build on the [Docker.io](https://index.docker.io) Registry. +new Automated Build on the [Docker Hub](https://hub.docker.com) Registry. It will stay in sync with your GitHub and BitBucket repository until you deactivate the Automated Build. If you want to see the status of your Automated Builds you can go to your -[Automated Builds page](https://index.docker.io/builds/) on the Docker.io, +[Automated Builds page](https://registry.hub.docker.io/builds/) on the Docker Hub, and it will show you the status of your builds, and the build history. Once you've created an Automated Build you can deactivate or delete it. You @@ -160,7 +160,7 @@ to point to specific `Dockerfile`'s or Git branches. ### Build Triggers -Automated Builds can also be triggered via a URL on Docker.io. This +Automated Builds can also be triggered via a URL on Docker Hub. This allows you to rebuild an Automated build image on demand. ## Webhooks diff --git a/docs/sources/userguide/dockervolumes.md b/docs/sources/userguide/dockervolumes.md index 34cfe05b4..f3a461235 100644 --- a/docs/sources/userguide/dockervolumes.md +++ b/docs/sources/userguide/dockervolumes.md @@ -135,8 +135,8 @@ restore testing using your preferred tools. Now we've learned a bit more about how to use Docker we're going to see how to combine Docker with the services available on -[Docker.io](https://index.docker.io) including Automated Builds and private +[Docker Hub](https://hub.docker.com) including Automated Builds and private repositories. -Go to [Working with Docker.io](/userguide/dockerrepos). +Go to [Working with Docker Hub](/userguide/dockerrepos). diff --git a/docs/sources/userguide/index.md b/docs/sources/userguide/index.md index 715054043..54400cef7 100644 --- a/docs/sources/userguide/index.md +++ b/docs/sources/userguide/index.md @@ -19,15 +19,15 @@ We’ll teach you how to use Docker to: We've broken this guide into major sections that take you through the Docker life cycle: -## Getting Started with Docker.io +## Getting Started with Docker Hub -*How do I use Docker.io?* +*How do I use Docker Hub?* -Docker.io is the central hub for Docker. It hosts public Docker images +Docker Hub is the central hub for Docker. It hosts public Docker images and provides services to help you build and manage your Docker environment. To learn more; -Go to [Using Docker.io](/userguide/dockerio). +Go to [Using Docker Hub](/userguide/dockerhub). ## Dockerizing Applications: A "Hello World!" @@ -72,21 +72,21 @@ learning how to manage data, volumes and mounts inside our containers. Go to [Managing Data in Containers](/userguide/dockervolumes). -## Working with Docker.io +## Working with Docker Hub Now we've learned a bit more about how to use Docker we're going to see -how to combine Docker with the services available on Docker.io including -Automated Builds and private repositories. +how to combine Docker with the services available on Docker Hub including +Trusted Builds and private repositories. -Go to [Working with Docker.io](/userguide/dockerrepos). +Go to [Working with Docker Hub](/userguide/dockerrepos). ## Getting help -* [Docker homepage](http://www.docker.io/) -* [Docker.io](http://index.docker.io) -* [Docker blog](http://blog.docker.io/) -* [Docker documentation](http://docs.docker.io/) -* [Docker Getting Started Guide](http://www.docker.io/gettingstarted/) +* [Docker homepage](http://www.docker.com/) +* [Docker Hub](https://hub.docker.com/) +* [Docker blog](http://blog.docker.com/) +* [Docker documentation](http://docs.docker.com/) +* [Docker Getting Started Guide](http://www.docker.com/gettingstarted/) * [Docker code on GitHub](https://github.com/dotcloud/docker) * [Docker mailing list](https://groups.google.com/forum/#!forum/docker-user) diff --git a/docs/sources/userguide/usingdocker.md b/docs/sources/userguide/usingdocker.md index 2eaf707d1..89401b19b 100644 --- a/docs/sources/userguide/usingdocker.md +++ b/docs/sources/userguide/usingdocker.md @@ -309,7 +309,7 @@ And now our container is stopped and deleted. # Next steps Until now we've only used images that we've downloaded from -[Docker.io](https://index.docker.io) now let's get introduced to +[Docker Hub](https://hub.docker.com) now let's get introduced to building and sharing our own images. Go to [Working with Docker Images](/userguide/dockerimages). From 7a145b022a3b50b0d04cb0471d517f7c6c5efc67 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 5 May 2014 15:44:16 -0700 Subject: [PATCH 372/400] Totally remove insert feature Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- api/server/server.go | 27 ---------- daemon/container.go | 36 ------------- .../reference/api/docker_remote_api.md | 3 ++ .../reference/api/docker_remote_api_v1.11.md | 25 --------- integration/server_test.go | 22 -------- server/server.go | 51 ------------------- 6 files changed, 3 insertions(+), 161 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index a2c02f57f..61407b264 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -534,32 +534,6 @@ func getImagesSearch(eng *engine.Engine, version version.Version, w http.Respons return job.Run() } -// FIXME: 'insert' is deprecated as of 0.10, and should be removed in a future version. -func postImagesInsert(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - if vars == nil { - return fmt.Errorf("Missing parameter") - } - job := eng.Job("insert", vars["name"], r.Form.Get("url"), r.Form.Get("path")) - if version.GreaterThan("1.0") { - job.SetenvBool("json", true) - streamJSON(job, w, false) - } else { - job.Stdout.Add(w) - } - if err := job.Run(); err != nil { - if !job.Stdout.Used() { - return err - } - sf := utils.NewStreamFormatter(version.GreaterThan("1.0")) - w.Write(sf.FormatError(err)) - } - - return nil -} - func postImagesPush(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") @@ -1111,7 +1085,6 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st "/commit": postCommit, "/build": postBuild, "/images/create": postImagesCreate, - "/images/{name:.*}/insert": postImagesInsert, "/images/load": postImagesLoad, "/images/{name:.*}/push": postImagesPush, "/images/{name:.*}/tag": postImagesTag, diff --git a/daemon/container.go b/daemon/container.go index bb02b37f5..b4a33e3e1 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -84,42 +84,6 @@ type Container struct { activeLinks map[string]*links.Link } -// Inject the io.Reader at the given path. Note: do not close the reader -func (container *Container) Inject(file io.Reader, pth string) error { - if err := container.Mount(); err != nil { - return fmt.Errorf("inject: error mounting container %s: %s", container.ID, err) - } - defer container.Unmount() - - // Return error if path exists - destPath := container.getResourcePath(pth) - if _, err := os.Stat(destPath); err == nil { - // Since err is nil, the path could be stat'd and it exists - return fmt.Errorf("%s exists", pth) - } else if !os.IsNotExist(err) { - // Expect err might be that the file doesn't exist, so - // if it's some other error, return that. - - return err - } - - // Make sure the directory exists - if err := os.MkdirAll(container.getResourcePath(path.Dir(pth)), 0755); err != nil { - return err - } - - dest, err := os.Create(destPath) - if err != nil { - return err - } - defer dest.Close() - - if _, err := io.Copy(dest, file); err != nil { - return err - } - return nil -} - func (container *Container) FromDisk() error { data, err := ioutil.ReadFile(container.jsonPath()) if err != nil { diff --git a/docs/sources/reference/api/docker_remote_api.md b/docs/sources/reference/api/docker_remote_api.md index 355102a70..9db9a9757 100644 --- a/docs/sources/reference/api/docker_remote_api.md +++ b/docs/sources/reference/api/docker_remote_api.md @@ -63,6 +63,9 @@ Trusted builds are now Automated Builds - `is_trusted` is now `is_automated`. **New!** You can now ping the server via the `_ping` endpoint. +**Removed Insert Endpoint** +The insert endpoint has been removed. + `GET /events` **New!** diff --git a/docs/sources/reference/api/docker_remote_api_v1.11.md b/docs/sources/reference/api/docker_remote_api_v1.11.md index b77b33b09..e72545b6f 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.11.md +++ b/docs/sources/reference/api/docker_remote_api_v1.11.md @@ -756,31 +756,6 @@ Create an image, either by pull it from the registry or by importing it - **200** – no error - **500** – server error -### Insert a file in an image - -`POST /images/(name)/insert` - -Insert a file from `url` in the image `name` at `path` - - **Example request**: - - POST /images/test/insert?path=/usr&url=myurl HTTP/1.1 - - **Example response**: - - HTTP/1.1 200 OK - Content-Type: application/json - - {"status":"Inserting..."} - {"status":"Inserting", "progress":"1/? (n/a)", "progressDetail":{"current":1}} - {"error":"Invalid..."} - ... - - Status Codes: - - - **200** – no error - - **500** – server error - ### Inspect an image `GET /images/(name)/json` diff --git a/integration/server_test.go b/integration/server_test.go index 3752c9b7e..151490cdc 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -398,28 +398,6 @@ func TestImagesFilter(t *testing.T) { } } -// FIXE: 'insert' is deprecated and should be removed in a future version. -func TestImageInsert(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) - - // bad image name fails - if err := srv.Eng.Job("insert", "foo", "https://www.docker.io/static/img/docker-top-logo.png", "/foo").Run(); err == nil { - t.Fatal("expected an error and got none") - } - - // bad url fails - if err := srv.Eng.Job("insert", unitTestImageID, "http://bad_host_name_that_will_totally_fail.com/", "/foo").Run(); err == nil { - t.Fatal("expected an error and got none") - } - - // success returns nil - if err := srv.Eng.Job("insert", unitTestImageID, "https://www.docker.io/static/img/docker-top-logo.png", "/foo").Run(); err != nil { - t.Fatalf("expected no error, but got %v", err) - } -} - func TestListContainers(t *testing.T) { eng := NewTestEngine(t) srv := mkServerFromEngine(eng, t) diff --git a/server/server.go b/server/server.go index b439c0f2d..93c6d39ba 100644 --- a/server/server.go +++ b/server/server.go @@ -138,7 +138,6 @@ func InitServer(job *engine.Job) engine.Status { "history": srv.ImageHistory, "viz": srv.ImagesViz, "container_copy": srv.ContainerCopy, - "insert": srv.ImageInsert, "attach": srv.ContainerAttach, "logs": srv.ContainerLogs, "changes": srv.ContainerChanges, @@ -645,56 +644,6 @@ func (srv *Server) recursiveLoad(eng *engine.Engine, address, tmpImageDir string return nil } -// FIXME: 'insert' is deprecated and should be removed in a future version. -func (srv *Server) ImageInsert(job *engine.Job) engine.Status { - fmt.Fprintf(job.Stderr, "Warning: '%s' is deprecated and will be removed in a future version. Please use 'build' and 'ADD' instead.\n", job.Name) - if len(job.Args) != 3 { - return job.Errorf("Usage: %s IMAGE URL PATH\n", job.Name) - } - - var ( - name = job.Args[0] - url = job.Args[1] - path = job.Args[2] - ) - - sf := utils.NewStreamFormatter(job.GetenvBool("json")) - - out := utils.NewWriteFlusher(job.Stdout) - img, err := srv.daemon.Repositories().LookupImage(name) - if err != nil { - return job.Error(err) - } - - file, err := utils.Download(url) - if err != nil { - return job.Error(err) - } - defer file.Body.Close() - - config, _, _, err := runconfig.Parse([]string{img.ID, "echo", "insert", url, path}, srv.daemon.SystemConfig()) - if err != nil { - return job.Error(err) - } - - c, _, err := srv.daemon.Create(config, "") - if err != nil { - return job.Error(err) - } - - if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf, false, utils.TruncateID(img.ID), "Downloading"), path); err != nil { - return job.Error(err) - } - // FIXME: Handle custom repo, tag comment, author - img, err = srv.daemon.Commit(c, "", "", img.Comment, img.Author, nil) - if err != nil { - out.Write(sf.FormatError(err)) - return engine.StatusErr - } - out.Write(sf.FormatStatus("", img.ID)) - return engine.StatusOK -} - func (srv *Server) ImagesViz(job *engine.Job) engine.Status { images, _ := srv.daemon.Graph().Map() if images == nil { From 0a725ea28259f8a0f9a1de5730fd99675b942dde Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Sat, 24 May 2014 01:06:14 +0000 Subject: [PATCH 373/400] Adding initial version of C-based nsenter for allowing execin in libcontainer. Docker-DCO-1.1-Signed-off-by: Victor Marmol (github: vmarmol) --- pkg/libcontainer/namespaces/execin.go | 116 +++++--------------- pkg/libcontainer/namespaces/nsenter.go | 142 +++++++++++++++++++++++++ pkg/libcontainer/nsinit/main.go | 63 +++++++++-- 3 files changed, 224 insertions(+), 97 deletions(-) create mode 100644 pkg/libcontainer/namespaces/nsenter.go diff --git a/pkg/libcontainer/namespaces/execin.go b/pkg/libcontainer/namespaces/execin.go index 09bf40582..699c67dbc 100644 --- a/pkg/libcontainer/namespaces/execin.go +++ b/pkg/libcontainer/namespaces/execin.go @@ -3,119 +3,55 @@ package namespaces import ( - "fmt" + "encoding/json" "os" - "path/filepath" "strconv" - "syscall" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/mount" "github.com/dotcloud/docker/pkg/system" ) // ExecIn uses an existing pid and joins the pid's namespaces with the new command. -func ExecIn(container *libcontainer.Container, nspid int, args []string) (int, error) { +func ExecIn(container *libcontainer.Container, nspid int, args []string) error { // clear the current processes env and replace it with the environment // defined on the container if err := LoadContainerEnvironment(container); err != nil { - return -1, err + return err } - for key, enabled := range container.Namespaces { - // skip the PID namespace on unshare because it it not supported - if enabled && key != "NEWPID" { - if ns := libcontainer.GetNamespace(key); ns != nil { - if err := system.Unshare(ns.Value); err != nil { - return -1, err - } - } - } - } - fds, err := getNsFds(nspid, container) - closeFds := func() { - for _, f := range fds { - system.Closefd(f) - } - } + // TODO(vmarmol): If this gets too long, send it over a pipe to the child. + // Marshall the container into JSON since it won't be available in the namespace. + containerJson, err := json.Marshal(container) if err != nil { - closeFds() - return -1, err + return err } + + // TODO(vmarmol): Move this to the container JSON. processLabel, err := label.GetPidCon(nspid) if err != nil { - closeFds() - return -1, err - } - // foreach namespace fd, use setns to join an existing container's namespaces - for _, fd := range fds { - if fd > 0 { - if err := system.Setns(fd, 0); err != nil { - closeFds() - return -1, fmt.Errorf("setns %s", err) - } - } - system.Closefd(fd) + return err } - // if the container has a new pid and mount namespace we need to - // remount proc and sys to pick up the changes - if container.Namespaces["NEWNS"] && container.Namespaces["NEWPID"] { - pid, err := system.Fork() - if err != nil { - return -1, err - } - if pid == 0 { - // TODO: make all raw syscalls to be fork safe - if err := system.Unshare(syscall.CLONE_NEWNS); err != nil { - return -1, err - } - if err := mount.RemountProc(); err != nil { - return -1, fmt.Errorf("remount proc %s", err) - } - if err := mount.RemountSys(); err != nil { - return -1, fmt.Errorf("remount sys %s", err) - } - goto dropAndExec - } - proc, err := os.FindProcess(pid) - if err != nil { - return -1, err - } - state, err := proc.Wait() - if err != nil { - return -1, err - } - os.Exit(state.Sys().(syscall.WaitStatus).ExitStatus()) - } -dropAndExec: - if err := FinalizeNamespace(container); err != nil { - return -1, err - } - err = label.SetProcessLabel(processLabel) - if err != nil { - return -1, err - } - if err := system.Execv(args[0], args[0:], container.Env); err != nil { - return -1, err + // Enter the namespace and then finish setup + finalArgs := []string{os.Args[0], "nsenter", strconv.Itoa(nspid), processLabel, string(containerJson)} + finalArgs = append(finalArgs, args...) + if err := system.Execv(finalArgs[0], finalArgs[0:], container.Env); err != nil { + return err } panic("unreachable") } -func getNsFds(pid int, container *libcontainer.Container) ([]uintptr, error) { - fds := []uintptr{} - - for key, enabled := range container.Namespaces { - if enabled { - if ns := libcontainer.GetNamespace(key); ns != nil { - f, err := os.OpenFile(filepath.Join("/proc/", strconv.Itoa(pid), "ns", ns.File), os.O_RDONLY, 0) - if err != nil { - return fds, err - } - fds = append(fds, f.Fd()) - } - } +// NsEnter is run after entering the namespace. +func NsEnter(container *libcontainer.Container, processLabel string, nspid int, args []string) error { + if err := FinalizeNamespace(container); err != nil { + return err } - return fds, nil + if err := label.SetProcessLabel(processLabel); err != nil { + return err + } + if err := system.Execv(args[0], args[0:], os.Environ()); err != nil { + return err + } + panic("unreachable") } diff --git a/pkg/libcontainer/namespaces/nsenter.go b/pkg/libcontainer/namespaces/nsenter.go new file mode 100644 index 000000000..c5dd2e795 --- /dev/null +++ b/pkg/libcontainer/namespaces/nsenter.go @@ -0,0 +1,142 @@ +package namespaces + +/* +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const kBufSize = 256; + +void get_args(int *argc, char ***argv) { + // Read argv + int fd = open("/proc/self/cmdline", O_RDONLY); + + // Read the whole commandline. + ssize_t contents_size = 0; + ssize_t contents_offset = 0; + char *contents = NULL; + ssize_t bytes_read = 0; + do { + contents_size += kBufSize; + contents = (char *) realloc(contents, contents_size); + bytes_read = read(fd, contents + contents_offset, contents_size - contents_offset); + contents_offset += bytes_read; + } while (bytes_read > 0); + close(fd); + + // Parse the commandline into an argv. /proc/self/cmdline has \0 delimited args. + ssize_t i; + *argc = 0; + for (i = 0; i < contents_offset; i++) { + if (contents[i] == '\0') { + (*argc)++; + } + } + *argv = (char **) malloc(sizeof(char *) * ((*argc) + 1)); + int idx; + for (idx = 0; idx < (*argc); idx++) { + (*argv)[idx] = contents; + contents += strlen(contents) + 1; + } + (*argv)[*argc] = NULL; +} + +void nsenter() { + int argc; + char **argv; + get_args(&argc, &argv); + + // Ignore if this is not for us. + if (argc < 2 || strcmp(argv[1], "nsenter") != 0) { + return; + } + + // USAGE: nsenter ... + if (argc < 6) { + fprintf(stderr, "nsenter: Incorrect usage, not enough arguments\n"); + exit(1); + } + pid_t init_pid = strtol(argv[2], NULL, 10); + if (errno != 0 || init_pid <= 0) { + fprintf(stderr, "nsenter: Failed to parse PID from \"%s\" with error: \"%s\"\n", argv[2], strerror(errno)); + exit(1); + } + argc -= 3; + argv += 3; + + // Setns on all supported namespaces. + char ns_dir[kBufSize]; + memset(ns_dir, 0, kBufSize); + if (snprintf(ns_dir, kBufSize - 1, "/proc/%d/ns/", init_pid) < 0) { + fprintf(stderr, "nsenter: Error getting ns dir path with error: \"%s\"\n", strerror(errno)); + exit(1); + } + struct dirent *dent; + DIR *dir = opendir(ns_dir); + if (dir == NULL) { + fprintf(stderr, "nsenter: Failed to open directory \"%s\" with error: \"%s\"\n", ns_dir, strerror(errno)); + exit(1); + } + while((dent = readdir(dir)) != NULL) { + if(strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) { + continue; + } + + // Get and open the namespace for the init we are joining.. + char buf[kBufSize]; + memset(buf, 0, kBufSize); + strncat(buf, ns_dir, kBufSize - 1); + strncat(buf, dent->d_name, kBufSize - 1); + int fd = open(buf, O_RDONLY); + if (fd == -1) { + fprintf(stderr, "nsenter: Failed to open ns file \"%s\" for ns \"%s\" with error: \"%s\"\n", buf, dent->d_name, strerror(errno)); + exit(1); + } + + // Set the namespace. + if (setns(fd, 0) == -1) { + fprintf(stderr, "nsenter: Failed to setns for \"%s\" with error: \"%s\"\n", dent->d_name, strerror(errno)); + exit(1); + } + close(fd); + } + closedir(dir); + + // We must fork to actually enter the PID namespace. + int child = fork(); + if (child == 0) { + // Finish executing, let the Go runtime take over. + return; + } else { + // Parent, wait for the child. + int status = 0; + if (waitpid(child, &status, 0) == -1) { + fprintf(stderr, "nsenter: Failed to waitpid with error: \"%s\"\n", strerror(errno)); + exit(1); + } + + // Forward the child's exit code or re-send its death signal. + if (WIFEXITED(status)) { + exit(WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + kill(getpid(), WTERMSIG(status)); + } + exit(1); + } + + return; +} + +__attribute__((constructor)) init() { + nsenter(); +} +*/ +import "C" diff --git a/pkg/libcontainer/nsinit/main.go b/pkg/libcontainer/nsinit/main.go index 6659a1310..bddc1992f 100644 --- a/pkg/libcontainer/nsinit/main.go +++ b/pkg/libcontainer/nsinit/main.go @@ -27,20 +27,20 @@ func main() { log.Fatalf("invalid number of arguments %d", len(os.Args)) } - container, err := loadContainer() - if err != nil { - log.Fatalf("unable to load container: %s", err) - } - switch os.Args[1] { case "exec": // this is executed outside of the namespace in the cwd + container, err := loadContainer() + if err != nil { + log.Fatalf("unable to load container: %s", err) + } + var nspid, exitCode int if nspid, err = readPid(); err != nil && !os.IsNotExist(err) { log.Fatalf("unable to read pid: %s", err) } if nspid > 0 { - exitCode, err = namespaces.ExecIn(container, nspid, os.Args[2:]) + err = namespaces.ExecIn(container, nspid, os.Args[2:]) } else { term := namespaces.NewTerminal(os.Stdin, os.Stdout, os.Stderr, container.Tty) exitCode, err = startContainer(container, term, dataPath, os.Args[2:]) @@ -50,7 +50,36 @@ func main() { log.Fatalf("failed to exec: %s", err) } os.Exit(exitCode) + case "nsenter": // this is executed inside the namespace. + // nsinit nsenter ... + if len(os.Args) < 6 { + log.Fatalf("incorrect usage: nsinit nsenter ...") + } + + container, err := loadContainerFromJson(os.Args[4]) + if err != nil { + log.Fatalf("unable to load container: %s", err) + } + + nspid, err := strconv.Atoi(os.Args[2]) + if err != nil { + log.Fatalf("unable to read pid: %s from %q", err, os.Args[2]) + } + + if nspid <= 0 { + log.Fatalf("cannot enter into namespaces without valid pid: %q", nspid) + } + + err = namespaces.NsEnter(container, os.Args[3], nspid, os.Args[5:]) + if err != nil { + log.Fatalf("failed to nsenter: %s", err) + } case "init": // this is executed inside of the namespace to setup the container + container, err := loadContainer() + if err != nil { + log.Fatalf("unable to load container: %s", err) + } + // by default our current dir is always our rootfs rootfs, err := os.Getwd() if err != nil { @@ -70,6 +99,11 @@ func main() { log.Fatalf("unable to initialize for container: %s", err) } case "stats": + container, err := loadContainer() + if err != nil { + log.Fatalf("unable to load container: %s", err) + } + // returns the stats of the current container. stats, err := getContainerStats(container) if err != nil { @@ -80,6 +114,11 @@ func main() { os.Exit(0) case "spec": + container, err := loadContainer() + if err != nil { + log.Fatalf("unable to load container: %s", err) + } + // returns the spec of the current container. spec, err := getContainerSpec(container) if err != nil { @@ -90,13 +129,14 @@ func main() { os.Exit(0) default: - log.Fatalf("command not supported for nsinit %s", os.Args[0]) + log.Fatalf("command not supported for nsinit %s", os.Args[1]) } } func loadContainer() (*libcontainer.Container, error) { f, err := os.Open(filepath.Join(dataPath, "container.json")) if err != nil { + log.Printf("Path: %q", filepath.Join(dataPath, "container.json")) return nil, err } defer f.Close() @@ -108,6 +148,15 @@ func loadContainer() (*libcontainer.Container, error) { return container, nil } +func loadContainerFromJson(rawData string) (*libcontainer.Container, error) { + container := &libcontainer.Container{} + err := json.Unmarshal([]byte(rawData), container) + if err != nil { + return nil, err + } + return container, nil +} + func readPid() (int, error) { data, err := ioutil.ReadFile(filepath.Join(dataPath, "pid")) if err != nil { From 8497d1274b046804999699ccb66b11a3249906a1 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 4 Jun 2014 17:54:00 -0700 Subject: [PATCH 374/400] Move env load to nsenter Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/namespaces/execin.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pkg/libcontainer/namespaces/execin.go b/pkg/libcontainer/namespaces/execin.go index 699c67dbc..4d5671e77 100644 --- a/pkg/libcontainer/namespaces/execin.go +++ b/pkg/libcontainer/namespaces/execin.go @@ -14,12 +14,6 @@ import ( // ExecIn uses an existing pid and joins the pid's namespaces with the new command. func ExecIn(container *libcontainer.Container, nspid int, args []string) error { - // clear the current processes env and replace it with the environment - // defined on the container - if err := LoadContainerEnvironment(container); err != nil { - return err - } - // TODO(vmarmol): If this gets too long, send it over a pipe to the child. // Marshall the container into JSON since it won't be available in the namespace. containerJson, err := json.Marshal(container) @@ -36,7 +30,7 @@ func ExecIn(container *libcontainer.Container, nspid int, args []string) error { // Enter the namespace and then finish setup finalArgs := []string{os.Args[0], "nsenter", strconv.Itoa(nspid), processLabel, string(containerJson)} finalArgs = append(finalArgs, args...) - if err := system.Execv(finalArgs[0], finalArgs[0:], container.Env); err != nil { + if err := system.Execv(finalArgs[0], finalArgs[0:], os.Environ()); err != nil { return err } panic("unreachable") @@ -44,13 +38,18 @@ func ExecIn(container *libcontainer.Container, nspid int, args []string) error { // NsEnter is run after entering the namespace. func NsEnter(container *libcontainer.Container, processLabel string, nspid int, args []string) error { + // clear the current processes env and replace it with the environment + // defined on the container + if err := LoadContainerEnvironment(container); err != nil { + return err + } if err := FinalizeNamespace(container); err != nil { return err } if err := label.SetProcessLabel(processLabel); err != nil { return err } - if err := system.Execv(args[0], args[0:], os.Environ()); err != nil { + if err := system.Execv(args[0], args[0:], container.Env); err != nil { return err } panic("unreachable") From 7eb508633db93213404292bd4fd21b6855f45bea Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 4 Jun 2014 18:03:17 -0700 Subject: [PATCH 375/400] Exclude the user namespace for setns Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/namespaces/nsenter.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/libcontainer/namespaces/nsenter.go b/pkg/libcontainer/namespaces/nsenter.go index c5dd2e795..d5c2e761b 100644 --- a/pkg/libcontainer/namespaces/nsenter.go +++ b/pkg/libcontainer/namespaces/nsenter.go @@ -85,8 +85,9 @@ void nsenter() { fprintf(stderr, "nsenter: Failed to open directory \"%s\" with error: \"%s\"\n", ns_dir, strerror(errno)); exit(1); } + while((dent = readdir(dir)) != NULL) { - if(strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) { + if(strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0 || strcmp(dent->d_name, "user") == 0) { continue; } From 0d1906feea511cbfd1695adbb0b39dfdfae2caeb Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Wed, 4 Jun 2014 18:30:10 -0700 Subject: [PATCH 376/400] Move doc section in Remote API about removing insert from v1.11 to v1.12 Docker-DCO-1.1-Signed-off-by: Tibor Vass (github: tiborvass) --- docs/sources/reference/api/docker_remote_api.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/reference/api/docker_remote_api.md b/docs/sources/reference/api/docker_remote_api.md index 9db9a9757..9d4069f70 100644 --- a/docs/sources/reference/api/docker_remote_api.md +++ b/docs/sources/reference/api/docker_remote_api.md @@ -50,6 +50,9 @@ All the JSON keys are now in CamelCase **New!** Trusted builds are now Automated Builds - `is_trusted` is now `is_automated`. +**Removed Insert Endpoint** +The insert endpoint has been removed. + ## v1.11 ### Full Documentation @@ -63,9 +66,6 @@ Trusted builds are now Automated Builds - `is_trusted` is now `is_automated`. **New!** You can now ping the server via the `_ping` endpoint. -**Removed Insert Endpoint** -The insert endpoint has been removed. - `GET /events` **New!** From 13f07b636f014331a5d5dc0eac40d6ac9e5c9263 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 31 Mar 2014 09:30:20 +0200 Subject: [PATCH 377/400] pkg/units: Add FromHumanSize This does the "reverse" of HumanSize, i.e. maps a string to an int64 using SI prefixes for the extension. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- pkg/units/size.go | 36 ++++++++++++++++++++++++++++++++++++ pkg/units/size_test.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/pkg/units/size.go b/pkg/units/size.go index 99c880096..480ec2f14 100644 --- a/pkg/units/size.go +++ b/pkg/units/size.go @@ -21,6 +21,42 @@ func HumanSize(size int64) string { return fmt.Sprintf("%.4g %s", sizef, units[i]) } +// FromHumanSize returns an integer from a human-readable specification of a size +// using SI standard (eg. "44kB", "17MB") +func FromHumanSize(size string) (int64, error) { + re, error := regexp.Compile("^(\\d+)([kKmMgGtTpP])?[bB]?$") + if error != nil { + return -1, fmt.Errorf("%s does not specify not a size", size) + } + + matches := re.FindStringSubmatch(size) + + if len(matches) != 3 { + return -1, fmt.Errorf("Invalid size: '%s'", size) + } + + theSize, error := strconv.ParseInt(matches[1], 10, 0) + if error != nil { + return -1, error + } + + unit := strings.ToLower(matches[2]) + + if unit == "k" { + theSize *= 1000 + } else if unit == "m" { + theSize *= 1000 * 1000 + } else if unit == "g" { + theSize *= 1000 * 1000 * 1000 + } else if unit == "t" { + theSize *= 1000 * 1000 * 1000 * 1000 + } else if unit == "p" { + theSize *= 1000 * 1000 * 1000 * 1000 * 1000 + } + + return theSize, nil +} + // Parses a human-readable string representing an amount of RAM // in bytes, kibibytes, mebibytes or gibibytes, and returns the // number of bytes, or -1 if the string is unparseable. diff --git a/pkg/units/size_test.go b/pkg/units/size_test.go index 958a4ca13..5240bbd9f 100644 --- a/pkg/units/size_test.go +++ b/pkg/units/size_test.go @@ -20,6 +20,41 @@ func TestHumanSize(t *testing.T) { } } +func TestFromHumanSize(t *testing.T) { + assertFromHumanSize(t, "32", false, 32) + assertFromHumanSize(t, "32b", false, 32) + assertFromHumanSize(t, "32B", false, 32) + assertFromHumanSize(t, "32k", false, 32*1000) + assertFromHumanSize(t, "32K", false, 32*1000) + assertFromHumanSize(t, "32kb", false, 32*1000) + assertFromHumanSize(t, "32Kb", false, 32*1000) + assertFromHumanSize(t, "32Mb", false, 32*1000*1000) + assertFromHumanSize(t, "32Gb", false, 32*1000*1000*1000) + assertFromHumanSize(t, "32Tb", false, 32*1000*1000*1000*1000) + assertFromHumanSize(t, "8Pb", false, 8*1000*1000*1000*1000*1000) + + assertFromHumanSize(t, "", true, -1) + assertFromHumanSize(t, "hello", true, -1) + assertFromHumanSize(t, "-32", true, -1) + assertFromHumanSize(t, " 32 ", true, -1) + assertFromHumanSize(t, "32 mb", true, -1) + assertFromHumanSize(t, "32m b", true, -1) + assertFromHumanSize(t, "32bm", true, -1) +} + +func assertFromHumanSize(t *testing.T, size string, expectError bool, expectedBytes int64) { + actualBytes, err := FromHumanSize(size) + if (err != nil) && !expectError { + t.Errorf("Unexpected error parsing '%s': %s", size, err) + } + if (err == nil) && expectError { + t.Errorf("Expected to get an error parsing '%s', but got none (bytes=%d)", size, actualBytes) + } + if actualBytes != expectedBytes { + t.Errorf("Expected '%s' to parse as %d bytes, got %d", size, expectedBytes, actualBytes) + } +} + func TestRAMInBytes(t *testing.T) { assertRAMInBytes(t, "32", false, 32) assertRAMInBytes(t, "32b", false, 32) From 948e54ac455f88c79524dcf117df80f2d4c3f96c Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 27 Mar 2014 17:45:02 +0100 Subject: [PATCH 378/400] devmapper: Fail init with ErrNotSupported if simple devmapper call fails If we can't even get the current device mapper driver version, then we cleanly fail the devmapper driver as not supported and fall back on the next one. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- daemon/graphdriver/devmapper/deviceset.go | 7 +++ daemon/graphdriver/devmapper/devmapper.go | 20 +++++++++ .../devmapper/devmapper_wrapper.go | 45 ++++++++++++------- 3 files changed, 55 insertions(+), 17 deletions(-) diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 4de7858c1..f36faca90 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -17,6 +17,7 @@ import ( "syscall" "time" + "github.com/dotcloud/docker/daemon/graphdriver" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/utils" ) @@ -506,6 +507,12 @@ func (devices *DeviceSet) ResizePool(size int64) error { func (devices *DeviceSet) initDevmapper(doInit bool) error { logInit(devices) + _, err := getDriverVersion() + if err != nil { + // Can't even get driver version, assume not supported + return graphdriver.ErrNotSupported + } + if err := os.MkdirAll(devices.metadataDir(), 0700); err != nil && !os.IsExist(err) { return err } diff --git a/daemon/graphdriver/devmapper/devmapper.go b/daemon/graphdriver/devmapper/devmapper.go index d78ae1ef8..a6602c276 100644 --- a/daemon/graphdriver/devmapper/devmapper.go +++ b/daemon/graphdriver/devmapper/devmapper.go @@ -52,6 +52,7 @@ var ( ErrTaskAddTarget = errors.New("dm_task_add_target failed") ErrTaskSetSector = errors.New("dm_task_set_sector failed") ErrTaskGetInfo = errors.New("dm_task_get_info failed") + ErrTaskGetDriverVersion = errors.New("dm_task_get_driver_version failed") ErrTaskSetCookie = errors.New("dm_task_set_cookie failed") ErrNilCookie = errors.New("cookie ptr can't be nil") ErrAttachLoopbackDevice = errors.New("loopback mounting failed") @@ -178,6 +179,14 @@ func (t *Task) GetInfo() (*Info, error) { return info, nil } +func (t *Task) GetDriverVersion() (string, error) { + res := DmTaskGetDriverVersion(t.unmanaged) + if res == "" { + return "", ErrTaskGetDriverVersion + } + return res, nil +} + func (t *Task) GetNextTarget(next uintptr) (nextPtr uintptr, start uint64, length uint64, targetType string, params string) { @@ -394,6 +403,17 @@ func getInfo(name string) (*Info, error) { return task.GetInfo() } +func getDriverVersion() (string, error) { + task := TaskCreate(DeviceVersion) + if task == nil { + return "", fmt.Errorf("Can't create DeviceVersion task") + } + if err := task.Run(); err != nil { + return "", err + } + return task.GetDriverVersion() +} + func getStatus(name string) (uint64, uint64, string, string, error) { task, err := createTask(DeviceStatus, name) if task == nil { diff --git a/daemon/graphdriver/devmapper/devmapper_wrapper.go b/daemon/graphdriver/devmapper/devmapper_wrapper.go index bf558affc..9f1b5a605 100644 --- a/daemon/graphdriver/devmapper/devmapper_wrapper.go +++ b/daemon/graphdriver/devmapper/devmapper_wrapper.go @@ -85,23 +85,24 @@ const ( ) var ( - DmGetLibraryVersion = dmGetLibraryVersionFct - DmGetNextTarget = dmGetNextTargetFct - DmLogInitVerbose = dmLogInitVerboseFct - DmSetDevDir = dmSetDevDirFct - DmTaskAddTarget = dmTaskAddTargetFct - DmTaskCreate = dmTaskCreateFct - DmTaskDestroy = dmTaskDestroyFct - DmTaskGetInfo = dmTaskGetInfoFct - DmTaskRun = dmTaskRunFct - DmTaskSetAddNode = dmTaskSetAddNodeFct - DmTaskSetCookie = dmTaskSetCookieFct - DmTaskSetMessage = dmTaskSetMessageFct - DmTaskSetName = dmTaskSetNameFct - DmTaskSetRo = dmTaskSetRoFct - DmTaskSetSector = dmTaskSetSectorFct - DmUdevWait = dmUdevWaitFct - LogWithErrnoInit = logWithErrnoInitFct + DmGetLibraryVersion = dmGetLibraryVersionFct + DmGetNextTarget = dmGetNextTargetFct + DmLogInitVerbose = dmLogInitVerboseFct + DmSetDevDir = dmSetDevDirFct + DmTaskAddTarget = dmTaskAddTargetFct + DmTaskCreate = dmTaskCreateFct + DmTaskDestroy = dmTaskDestroyFct + DmTaskGetInfo = dmTaskGetInfoFct + DmTaskGetDriverVersion = dmTaskGetDriverVersionFct + DmTaskRun = dmTaskRunFct + DmTaskSetAddNode = dmTaskSetAddNodeFct + DmTaskSetCookie = dmTaskSetCookieFct + DmTaskSetMessage = dmTaskSetMessageFct + DmTaskSetName = dmTaskSetNameFct + DmTaskSetRo = dmTaskSetRoFct + DmTaskSetSector = dmTaskSetSectorFct + DmUdevWait = dmUdevWaitFct + LogWithErrnoInit = logWithErrnoInitFct ) func free(p *C.char) { @@ -184,6 +185,16 @@ func dmTaskGetInfoFct(task *CDmTask, info *Info) int { return int(C.dm_task_get_info((*C.struct_dm_task)(task), &Cinfo)) } +func dmTaskGetDriverVersionFct(task *CDmTask) string { + buffer := C.malloc(128) + defer C.free(buffer) + res := C.dm_task_get_driver_version((*C.struct_dm_task)(task), (*C.char)(buffer), 128) + if res == 0 { + return "" + } + return C.GoString((*C.char)(buffer)) +} + func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr { var ( Cstart, Clength C.uint64_t From 822ea97ffcf10645720bb93108a60f8b9ce9931d Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Jun 2014 10:34:20 +0200 Subject: [PATCH 379/400] Add --storage-opt graph driver option and pass through to driver This lets you add storage specific options for the daemon. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- daemon/daemon.go | 4 ++-- daemon/graphdriver/aufs/aufs.go | 2 +- daemon/graphdriver/aufs/aufs_test.go | 2 +- daemon/graphdriver/btrfs/btrfs.go | 2 +- daemon/graphdriver/devmapper/driver.go | 2 +- daemon/graphdriver/driver.go | 14 +++++++------- daemon/graphdriver/graphtest/graphtest.go | 2 +- daemon/graphdriver/vfs/driver.go | 2 +- daemonconfig/config.go | 5 +++++ docker/docker.go | 3 +++ docs/sources/reference/commandline/cli.md | 1 + graph/tags_unit_test.go | 2 +- integration/graph_test.go | 2 +- 13 files changed, 26 insertions(+), 17 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index 8879d019e..2c2b04694 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -780,7 +780,7 @@ func NewDaemonFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (*D graphdriver.DefaultDriver = config.GraphDriver // Load storage driver - driver, err := graphdriver.New(config.Root) + driver, err := graphdriver.New(config.Root, config.GraphOptions) if err != nil { return nil, err } @@ -809,7 +809,7 @@ func NewDaemonFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (*D // We don't want to use a complex driver like aufs or devmapper // for volumes, just a plain filesystem - volumesDriver, err := graphdriver.GetDriver("vfs", config.Root) + volumesDriver, err := graphdriver.GetDriver("vfs", config.Root, config.GraphOptions) if err != nil { return nil, err } diff --git a/daemon/graphdriver/aufs/aufs.go b/daemon/graphdriver/aufs/aufs.go index 79de4cda7..43c312827 100644 --- a/daemon/graphdriver/aufs/aufs.go +++ b/daemon/graphdriver/aufs/aufs.go @@ -57,7 +57,7 @@ type Driver struct { // New returns a new AUFS driver. // An error is returned if AUFS is not supported. -func Init(root string) (graphdriver.Driver, error) { +func Init(root string, options []string) (graphdriver.Driver, error) { // Try to load the aufs kernel module if err := supportsAufs(); err != nil { return nil, graphdriver.ErrNotSupported diff --git a/daemon/graphdriver/aufs/aufs_test.go b/daemon/graphdriver/aufs/aufs_test.go index dab5aecc4..b3bad410a 100644 --- a/daemon/graphdriver/aufs/aufs_test.go +++ b/daemon/graphdriver/aufs/aufs_test.go @@ -17,7 +17,7 @@ var ( ) func testInit(dir string, t *testing.T) graphdriver.Driver { - d, err := Init(dir) + d, err := Init(dir, nil) if err != nil { if err == graphdriver.ErrNotSupported { t.Skip(err) diff --git a/daemon/graphdriver/btrfs/btrfs.go b/daemon/graphdriver/btrfs/btrfs.go index 56299b18e..ba3ecba76 100644 --- a/daemon/graphdriver/btrfs/btrfs.go +++ b/daemon/graphdriver/btrfs/btrfs.go @@ -22,7 +22,7 @@ func init() { graphdriver.Register("btrfs", Init) } -func Init(home string) (graphdriver.Driver, error) { +func Init(home string, options []string) (graphdriver.Driver, error) { rootdir := path.Dir(home) var buf syscall.Statfs_t diff --git a/daemon/graphdriver/devmapper/driver.go b/daemon/graphdriver/devmapper/driver.go index 609971cda..50bda1cd2 100644 --- a/daemon/graphdriver/devmapper/driver.go +++ b/daemon/graphdriver/devmapper/driver.go @@ -26,7 +26,7 @@ type Driver struct { home string } -func Init(home string) (graphdriver.Driver, error) { +func Init(home string, options []string) (graphdriver.Driver, error) { deviceSet, err := NewDeviceSet(home, true) if err != nil { return nil, err diff --git a/daemon/graphdriver/driver.go b/daemon/graphdriver/driver.go index 8f9c0b6d2..93d4ed253 100644 --- a/daemon/graphdriver/driver.go +++ b/daemon/graphdriver/driver.go @@ -15,7 +15,7 @@ const ( FsMagicAufs = FsMagic(0x61756673) ) -type InitFunc func(root string) (Driver, error) +type InitFunc func(root string, options []string) (Driver, error) type Driver interface { String() string @@ -69,23 +69,23 @@ func Register(name string, initFunc InitFunc) error { return nil } -func GetDriver(name, home string) (Driver, error) { +func GetDriver(name, home string, options []string) (Driver, error) { if initFunc, exists := drivers[name]; exists { - return initFunc(path.Join(home, name)) + return initFunc(path.Join(home, name), options) } return nil, ErrNotSupported } -func New(root string) (driver Driver, err error) { +func New(root string, options []string) (driver Driver, err error) { for _, name := range []string{os.Getenv("DOCKER_DRIVER"), DefaultDriver} { if name != "" { - return GetDriver(name, root) + return GetDriver(name, root, options) } } // Check for priority drivers first for _, name := range priority { - driver, err = GetDriver(name, root) + driver, err = GetDriver(name, root, options) if err != nil { if err == ErrNotSupported || err == ErrPrerequisites || err == ErrIncompatibleFS { continue @@ -97,7 +97,7 @@ func New(root string) (driver Driver, err error) { // Check all registered drivers if no priority driver is found for _, initFunc := range drivers { - if driver, err = initFunc(root); err != nil { + if driver, err = initFunc(root, options); err != nil { if err == ErrNotSupported || err == ErrPrerequisites || err == ErrIncompatibleFS { continue } diff --git a/daemon/graphdriver/graphtest/graphtest.go b/daemon/graphdriver/graphtest/graphtest.go index 56ea8f7d4..d53878c45 100644 --- a/daemon/graphdriver/graphtest/graphtest.go +++ b/daemon/graphdriver/graphtest/graphtest.go @@ -29,7 +29,7 @@ func newDriver(t *testing.T, name string) *Driver { t.Fatal(err) } - d, err := graphdriver.GetDriver(name, root) + d, err := graphdriver.GetDriver(name, root, nil) if err != nil { if err == graphdriver.ErrNotSupported || err == graphdriver.ErrPrerequisites { t.Skip("Driver %s not supported", name) diff --git a/daemon/graphdriver/vfs/driver.go b/daemon/graphdriver/vfs/driver.go index 7473aa659..992af0e14 100644 --- a/daemon/graphdriver/vfs/driver.go +++ b/daemon/graphdriver/vfs/driver.go @@ -12,7 +12,7 @@ func init() { graphdriver.Register("vfs", Init) } -func Init(home string) (graphdriver.Driver, error) { +func Init(home string, options []string) (graphdriver.Driver, error) { d := &Driver{ home: home, } diff --git a/daemonconfig/config.go b/daemonconfig/config.go index 619bfe582..9f77d84a5 100644 --- a/daemonconfig/config.go +++ b/daemonconfig/config.go @@ -25,6 +25,7 @@ type Config struct { BridgeIP string InterContainerCommunication bool GraphDriver string + GraphOptions []string ExecDriver string Mtu int DisableNetwork bool @@ -49,6 +50,10 @@ func ConfigFromJob(job *engine.Job) *Config { ExecDriver: job.Getenv("ExecDriver"), EnableSelinuxSupport: job.GetenvBool("EnableSelinuxSupport"), } + if graphOpts := job.GetenvList("GraphOptions"); graphOpts != nil { + config.GraphOptions = graphOpts + } + if dns := job.GetenvList("Dns"); dns != nil { config.Dns = dns } diff --git a/docker/docker.go b/docker/docker.go index 4215ed3a9..56bcb04e4 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -41,6 +41,7 @@ func main() { var ( flVersion = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit") flDaemon = flag.Bool([]string{"d", "-daemon"}, false, "Enable daemon mode") + flGraphOpts opts.ListOpts flDebug = flag.Bool([]string{"D", "-debug"}, false, "Enable debug mode") flAutoRestart = flag.Bool([]string{"r", "-restart"}, true, "Restart previously running containers") bridgeName = flag.String([]string{"b", "-bridge"}, "", "Attach containers to a pre-existing network bridge\nuse 'none' to disable container networking") @@ -69,6 +70,7 @@ func main() { flag.Var(&flDns, []string{"#dns", "-dns"}, "Force docker to use specific DNS servers") flag.Var(&flDnsSearch, []string{"-dns-search"}, "Force Docker to use specific DNS search domains") flag.Var(&flHosts, []string{"H", "-host"}, "The socket(s) to bind to in daemon mode\nspecified using one or more tcp://host:port, unix:///path/to/socket, fd://* or fd://socketfd.") + flag.Var(&flGraphOpts, []string{"-storage-opt"}, "Set storage driver options") flag.Parse() @@ -156,6 +158,7 @@ func main() { job.Setenv("DefaultIp", *flDefaultIp) job.SetenvBool("InterContainerCommunication", *flInterContainerComm) job.Setenv("GraphDriver", *flGraphDriver) + job.SetenvList("GraphOptions", flGraphOpts.GetAll()) job.Setenv("ExecDriver", *flExecDriver) job.SetenvInt("Mtu", *flMtu) job.SetenvBool("EnableSelinuxSupport", *flSelinuxEnabled) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 16190ab1d..fd5d140a0 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -73,6 +73,7 @@ expect an integer, and they can only be specified once. -p, --pidfile="/var/run/docker.pid" Path to use for daemon PID file -r, --restart=true Restart previously running containers -s, --storage-driver="" Force the docker runtime to use a specific storage driver + --storage-opt=[] Set storage driver options --selinux-enabled=false Enable selinux support --tls=false Use TLS; implied by tls-verify flags --tlscacert="/home/sven/.docker/ca.pem" Trust only remotes providing a certificate signed by the CA given here diff --git a/graph/tags_unit_test.go b/graph/tags_unit_test.go index bc438131c..42e097724 100644 --- a/graph/tags_unit_test.go +++ b/graph/tags_unit_test.go @@ -36,7 +36,7 @@ func fakeTar() (io.Reader, error) { } func mkTestTagStore(root string, t *testing.T) *TagStore { - driver, err := graphdriver.New(root) + driver, err := graphdriver.New(root, nil) if err != nil { t.Fatal(err) } diff --git a/integration/graph_test.go b/integration/graph_test.go index c29055edf..dc056f7e1 100644 --- a/integration/graph_test.go +++ b/integration/graph_test.go @@ -293,7 +293,7 @@ func tempGraph(t *testing.T) (*graph.Graph, graphdriver.Driver) { if err != nil { t.Fatal(err) } - driver, err := graphdriver.New(tmp) + driver, err := graphdriver.New(tmp, nil) if err != nil { t.Fatal(err) } From 7f5ba068f438ee159bfca7396de4987bcae45809 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 27 Mar 2014 17:48:32 +0100 Subject: [PATCH 380/400] devmapper: Add --storage-opt options for basic devicemapper settings This allows setting these settings to be passed: dm.basesize dm.loopdatasize dm.loopmetadatasize Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- daemon/graphdriver/devmapper/README.md | 73 +++++++++++++++++++++++ daemon/graphdriver/devmapper/deviceset.go | 54 ++++++++++++++--- daemon/graphdriver/devmapper/driver.go | 2 +- 3 files changed, 121 insertions(+), 8 deletions(-) create mode 100644 daemon/graphdriver/devmapper/README.md diff --git a/daemon/graphdriver/devmapper/README.md b/daemon/graphdriver/devmapper/README.md new file mode 100644 index 000000000..f2c855c89 --- /dev/null +++ b/daemon/graphdriver/devmapper/README.md @@ -0,0 +1,73 @@ +## devicemapper - a storage backend based on Device Mapper + +### Theory of operation + +The device mapper graphdriver uses the device mapper thin provisioning +module (dm-thinp) to implement CoW snapshots. For each devicemapper +graph locaion (typically `/var/lib/docker/devicemapper`, $graph below) +a thin pool is created based on two block devices, one for data and +one for metadata. By default these block devices are created +automatically by using loopback mounts of automatically creates sparse +files. + +The default loopback files used are `$graph/devicemapper/data` and +`$graph/devicemapper/metadata`. Additional metadata required to map +from docker entities to the corresponding devicemapper volumes is +stored in the `$graph/devicemapper/json` file (encoded as Json). + +In order to support multiple devicemapper graphs on a system the thin +pool will be named something like: `docker-0:33-19478248-pool`, where +the `0:30` part is the minor/major device nr and `19478248` is the +inode number of the $graph directory. + +On the thin pool docker automatically creates a base thin device, +called something like `docker-0:33-19478248-base` of a fixed +size. This is automatically formated on creation and contains just an +empty filesystem. This device is the base of all docker images and +containers. All base images are snapshots of this device and those +images are then in turn used as snapshots for other images and +eventually containers. + +### options + +The devicemapper backend supports some options that you can specify +when starting the docker daemon using the --storage-opt flags. +This uses the `dm` prefix and would be used somthing like `docker -d --storage-opt dm.foo=bar`. + +Here is the list of supported options: + + * `dm.basesize` + + Specifies the size to use when creating the base device, which + limits the size of images and containers. The default value is + 10G. Note, thin devices are inherently "sparse", so a 10G device + which is mostly empty doesn't use 10 GB of space on the + pool. However, the filesystem will use more space for the empty + case the larger the device is. + + Example use: + + ``docker -d --storage-opt dm.basesize=20G`` + + * `dm.loopdatasize` + + Specifies the size to use when creating the loopback file for the + "data" device which is used for the thin pool. The default size is + 100G. Note that the file is sparse, so it will not initially take + up this much space. + + Example use: + + ``docker -d --storage-opt dm.loopdatasize=200G`` + + * `dm.loopmetadatasize` + + Specifies the size to use when creating the loopback file for the + "metadadata" device which is used for the thin pool. The default size is + 2G. Note that the file is sparse, so it will not initially take + up this much space. + + Example use: + + ``docker -d --storage-opt dm.loopmetadatasize=4G`` + diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index f36faca90..430f5c2e6 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -13,12 +13,14 @@ import ( "path" "path/filepath" "strconv" + "strings" "sync" "syscall" "time" "github.com/dotcloud/docker/daemon/graphdriver" "github.com/dotcloud/docker/pkg/label" + "github.com/dotcloud/docker/pkg/units" "github.com/dotcloud/docker/utils" ) @@ -65,6 +67,11 @@ type DeviceSet struct { TransactionId uint64 NewTransactionId uint64 nextDeviceId int + + // Options + dataLoopbackSize int64 + metaDataLoopbackSize int64 + baseFsSize uint64 } type DiskUsage struct { @@ -378,8 +385,8 @@ func (devices *DeviceSet) setupBaseImage() error { // Ids are 24bit, so wrap around devices.nextDeviceId = (id + 1) & 0xffffff - utils.Debugf("Registering base device (id %v) with FS size %v", id, DefaultBaseFsSize) - info, err := devices.registerDevice(id, "", DefaultBaseFsSize) + utils.Debugf("Registering base device (id %v) with FS size %v", id, devices.baseFsSize) + info, err := devices.registerDevice(id, "", devices.baseFsSize) if err != nil { _ = deleteDevice(devices.getPoolDevName(), id) utils.Debugf("\n--->Err: %s\n", err) @@ -567,12 +574,12 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { } createdLoopback = !hasData || !hasMetadata - data, err := devices.ensureImage("data", DefaultDataLoopbackSize) + data, err := devices.ensureImage("data", devices.dataLoopbackSize) if err != nil { utils.Debugf("Error device ensureImage (data): %s\n", err) return err } - metadata, err := devices.ensureImage("metadata", DefaultMetaDataLoopbackSize) + metadata, err := devices.ensureImage("metadata", devices.metaDataLoopbackSize) if err != nil { utils.Debugf("Error device ensureImage (metadata): %s\n", err) return err @@ -1091,12 +1098,45 @@ func (devices *DeviceSet) Status() *Status { return status } -func NewDeviceSet(root string, doInit bool) (*DeviceSet, error) { +func NewDeviceSet(root string, doInit bool, options []string) (*DeviceSet, error) { SetDevDir("/dev") devices := &DeviceSet{ - root: root, - MetaData: MetaData{Devices: make(map[string]*DevInfo)}, + root: root, + MetaData: MetaData{Devices: make(map[string]*DevInfo)}, + dataLoopbackSize: DefaultDataLoopbackSize, + metaDataLoopbackSize: DefaultMetaDataLoopbackSize, + baseFsSize: DefaultBaseFsSize, + } + + for _, option := range options { + key, val, err := utils.ParseKeyValueOpt(option) + if err != nil { + return nil, err + } + key = strings.ToLower(key) + switch key { + case "dm.basesize": + size, err := units.FromHumanSize(val) + if err != nil { + return nil, err + } + devices.baseFsSize = uint64(size) + case "dm.loopdatasize": + size, err := units.FromHumanSize(val) + if err != nil { + return nil, err + } + devices.dataLoopbackSize = size + case "dm.loopmetadatasize": + size, err := units.FromHumanSize(val) + if err != nil { + return nil, err + } + devices.metaDataLoopbackSize = size + default: + return nil, fmt.Errorf("Unknown option %s\n", key) + } } if err := devices.initDevmapper(doInit); err != nil { diff --git a/daemon/graphdriver/devmapper/driver.go b/daemon/graphdriver/devmapper/driver.go index 50bda1cd2..5bfd8ee65 100644 --- a/daemon/graphdriver/devmapper/driver.go +++ b/daemon/graphdriver/devmapper/driver.go @@ -27,7 +27,7 @@ type Driver struct { } func Init(home string, options []string) (graphdriver.Driver, error) { - deviceSet, err := NewDeviceSet(home, true) + deviceSet, err := NewDeviceSet(home, true, options) if err != nil { return nil, err } From 10083f414017636065aa50610f07784738df8e7a Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 18 Mar 2014 12:26:42 +0100 Subject: [PATCH 381/400] devicemapper: Probe what filesystem to use when mounting Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- daemon/graphdriver/devmapper/deviceset.go | 9 ++++- daemon/graphdriver/devmapper/mount.go | 47 +++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 430f5c2e6..5e055f6db 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -921,11 +921,16 @@ func (devices *DeviceSet) MountDevice(hash, path, mountLabel string) error { var flags uintptr = syscall.MS_MGC_VAL + fstype, err := ProbeFsType(info.DevName()) + if err != nil { + return err + } + mountOptions := label.FormatMountLabel("discard", mountLabel) - err = syscall.Mount(info.DevName(), path, "ext4", flags, mountOptions) + err = syscall.Mount(info.DevName(), path, fstype, flags, mountOptions) if err != nil && err == syscall.EINVAL { mountOptions = label.FormatMountLabel("", mountLabel) - err = syscall.Mount(info.DevName(), path, "ext4", flags, mountOptions) + err = syscall.Mount(info.DevName(), path, fstype, flags, mountOptions) } if err != nil { return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err) diff --git a/daemon/graphdriver/devmapper/mount.go b/daemon/graphdriver/devmapper/mount.go index 6de9e46c8..4ec0698c6 100644 --- a/daemon/graphdriver/devmapper/mount.go +++ b/daemon/graphdriver/devmapper/mount.go @@ -3,6 +3,8 @@ package devmapper import ( + "bytes" + "fmt" "os" "path/filepath" "syscall" @@ -27,3 +29,48 @@ func Mounted(mountpoint string) (bool, error) { parentSt := parent.Sys().(*syscall.Stat_t) return mntpointSt.Dev != parentSt.Dev, nil } + +type probeData struct { + fsName string + magic string + offset uint64 +} + +func ProbeFsType(device string) (string, error) { + probes := []probeData{ + {"btrfs", "_BHRfS_M", 0x10040}, + {"ext4", "\123\357", 0x438}, + {"xfs", "XFSB", 0}, + } + + maxLen := uint64(0) + for _, p := range probes { + l := p.offset + uint64(len(p.magic)) + if l > maxLen { + maxLen = l + } + } + + file, err := os.Open(device) + if err != nil { + return "", err + } + + buffer := make([]byte, maxLen) + l, err := file.Read(buffer) + if err != nil { + return "", err + } + file.Close() + if uint64(l) != maxLen { + return "", fmt.Errorf("unable to detect filesystem type of %s, short read", device) + } + + for _, p := range probes { + if bytes.Equal([]byte(p.magic), buffer[p.offset:p.offset+uint64(len(p.magic))]) { + return p.fsName, nil + } + } + + return "", fmt.Errorf("Unknown filesystem type on %s", device) +} From 807bc2cd049d97f31eab54ce3d5719d63240e3e7 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 18 Mar 2014 14:23:43 +0100 Subject: [PATCH 382/400] devmapper: Allow specifying filesystem for thin devices This adds the following --storage-opts for the daemon: dm.fs: The filesystem to use for the base image dm.mkfsarg: Add an argument to the mkfs command for the base image dm.mountopt: Add a mount option for devicemapper mount Currently supported filesystems are xfs and ext4. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- daemon/graphdriver/devmapper/README.md | 24 +++++++++++ daemon/graphdriver/devmapper/deviceset.go | 51 +++++++++++++++++++---- daemon/graphdriver/devmapper/mount.go | 10 +++++ 3 files changed, 78 insertions(+), 7 deletions(-) diff --git a/daemon/graphdriver/devmapper/README.md b/daemon/graphdriver/devmapper/README.md index f2c855c89..4d32a1435 100644 --- a/daemon/graphdriver/devmapper/README.md +++ b/daemon/graphdriver/devmapper/README.md @@ -71,3 +71,27 @@ Here is the list of supported options: ``docker -d --storage-opt dm.loopmetadatasize=4G`` + * `dm.fs` + + Specifies the filesystem type to use for the base device. The supported + options are "ext4" and "xfs". The default is "ext4" + + Example use: + + ``docker -d --storage-opt dm.fs=xfs`` + + * `dm.mkfsarg` + + Specifies extra mkfs arguments to be used when creating the base device. + + Example use: + + ``docker -d --storage-opt "dm.mkfsarg=-O ^has_journal"`` + + * `dm.mountopt` + + Specifies extra mount options used when mounting the thin devices. + + Example use: + + ``docker -d --storage-opt dm.mountopt=nodiscard`` diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 5e055f6db..13f68f61e 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -72,6 +72,9 @@ type DeviceSet struct { dataLoopbackSize int64 metaDataLoopbackSize int64 baseFsSize uint64 + filesystem string + mountOptions string + mkfsArgs []string } type DiskUsage struct { @@ -281,14 +284,30 @@ func (devices *DeviceSet) activateDeviceIfNeeded(info *DevInfo) error { func (devices *DeviceSet) createFilesystem(info *DevInfo) error { devname := info.DevName() - err := exec.Command("mkfs.ext4", "-E", "nodiscard,lazy_itable_init=0,lazy_journal_init=0", devname).Run() - if err != nil { - err = exec.Command("mkfs.ext4", "-E", "nodiscard,lazy_itable_init=0", devname).Run() + args := []string{} + for _, arg := range devices.mkfsArgs { + args = append(args, arg) + } + + args = append(args, devname) + + var err error + switch devices.filesystem { + case "xfs": + err = exec.Command("mkfs.xfs", args...).Run() + case "ext4": + err = exec.Command("mkfs.ext4", append([]string{"-E", "nodiscard,lazy_itable_init=0,lazy_journal_init=0"}, args...)...).Run() + if err != nil { + err = exec.Command("mkfs.ext4", append([]string{"-E", "nodiscard,lazy_itable_init=0"}, args...)...).Run() + } + default: + err = fmt.Errorf("Unsupported filesystem type %s", devices.filesystem) } if err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } + return nil } @@ -926,11 +945,19 @@ func (devices *DeviceSet) MountDevice(hash, path, mountLabel string) error { return err } - mountOptions := label.FormatMountLabel("discard", mountLabel) - err = syscall.Mount(info.DevName(), path, fstype, flags, mountOptions) + options := "" + + if fstype == "xfs" { + // XFS needs nouuid or it can't mount filesystems with the same fs + options = joinMountOptions(options, "nouuid") + } + + options = joinMountOptions(options, devices.mountOptions) + options = joinMountOptions(options, label.FormatMountLabel("", mountLabel)) + + err = syscall.Mount(info.DevName(), path, fstype, flags, joinMountOptions("discard", options)) if err != nil && err == syscall.EINVAL { - mountOptions = label.FormatMountLabel("", mountLabel) - err = syscall.Mount(info.DevName(), path, fstype, flags, mountOptions) + err = syscall.Mount(info.DevName(), path, fstype, flags, options) } if err != nil { return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err) @@ -1112,6 +1139,7 @@ func NewDeviceSet(root string, doInit bool, options []string) (*DeviceSet, error dataLoopbackSize: DefaultDataLoopbackSize, metaDataLoopbackSize: DefaultMetaDataLoopbackSize, baseFsSize: DefaultBaseFsSize, + filesystem: "ext4", } for _, option := range options { @@ -1139,6 +1167,15 @@ func NewDeviceSet(root string, doInit bool, options []string) (*DeviceSet, error return nil, err } devices.metaDataLoopbackSize = size + case "dm.fs": + if val != "ext4" && val != "xfs" { + return nil, fmt.Errorf("Unsupported filesystem %s\n", val) + } + devices.filesystem = val + case "dm.mkfsarg": + devices.mkfsArgs = append(devices.mkfsArgs, val) + case "dm.mountopt": + devices.mountOptions = joinMountOptions(devices.mountOptions, val) default: return nil, fmt.Errorf("Unknown option %s\n", key) } diff --git a/daemon/graphdriver/devmapper/mount.go b/daemon/graphdriver/devmapper/mount.go index 4ec0698c6..c9ff216d5 100644 --- a/daemon/graphdriver/devmapper/mount.go +++ b/daemon/graphdriver/devmapper/mount.go @@ -74,3 +74,13 @@ func ProbeFsType(device string) (string, error) { return "", fmt.Errorf("Unknown filesystem type on %s", device) } + +func joinMountOptions(a, b string) string { + if a == "" { + return b + } + if b == "" { + return a + } + return a + "," + b +} From a226168a8b877d632cb87c95dd0288f6092b9d8f Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 28 Mar 2014 15:36:55 +0100 Subject: [PATCH 383/400] devmapper: Add options for specifying block devices This adds dm.datadev and dm.metadatadev options that you can use with --storage-opt to set to specific devices to use for the thin provisioning pool. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- daemon/graphdriver/devmapper/README.md | 30 +++++++ daemon/graphdriver/devmapper/deviceset.go | 96 ++++++++++++++++------- 2 files changed, 97 insertions(+), 29 deletions(-) diff --git a/daemon/graphdriver/devmapper/README.md b/daemon/graphdriver/devmapper/README.md index 4d32a1435..53b7174db 100644 --- a/daemon/graphdriver/devmapper/README.md +++ b/daemon/graphdriver/devmapper/README.md @@ -95,3 +95,33 @@ Here is the list of supported options: Example use: ``docker -d --storage-opt dm.mountopt=nodiscard`` + + * `dm.datadev` + + Specifies a custom blockdevice to use for data for the thin pool. + + If using a block device for device mapper storage, ideally both + datadev and metadatadev should be specified to completely avoid + using the loopback device. + + Example use: + + ``docker -d --storage-opt dm.datadev=/dev/sdb1 --storage-opt dm.metadatadev=/dev/sdc1`` + + * `dm.metadatadev` + + Specifies a custom blockdevice to use for metadata for the thin + pool. + + For best performance the metadata should be on a different spindle + than the data, or even better on an SSD. + + If setting up a new metadata pool it is required to be valid. This + can be achieved by zeroing the first 4k to indicate empty + metadata, like this: + + ``dd if=/dev/zero of=$metadata_dev bs=4096 count=1``` + + Example use: + + ``docker -d --storage-opt dm.datadev=/dev/sdb1 --storage-opt dm.metadatadev=/dev/sdc1`` diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 13f68f61e..497e1102c 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -75,6 +75,8 @@ type DeviceSet struct { filesystem string mountOptions string mkfsArgs []string + dataDevice string + metadataDevice string } type DiskUsage struct { @@ -581,42 +583,74 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { if info.Exists == 0 { utils.Debugf("Pool doesn't exist. Creating it.") - hasData := devices.hasImage("data") - hasMetadata := devices.hasImage("metadata") + var ( + dataFile *os.File + metadataFile *os.File + ) - if !doInit && !hasData { - return errors.New("Loopback data file not found") + if devices.dataDevice == "" { + // Make sure the sparse images exist in /devicemapper/data + + hasData := devices.hasImage("data") + + if !doInit && !hasData { + return errors.New("Loopback data file not found") + } + + if !hasData { + createdLoopback = true + } + + data, err := devices.ensureImage("data", devices.dataLoopbackSize) + if err != nil { + utils.Debugf("Error device ensureImage (data): %s\n", err) + return err + } + + dataFile, err = attachLoopDevice(data) + if err != nil { + utils.Debugf("\n--->Err: %s\n", err) + return err + } + defer dataFile.Close() + } else { + dataFile, err = os.OpenFile(devices.dataDevice, os.O_RDWR, 0600) + if err != nil { + return err + } } - if !doInit && !hasMetadata { - return errors.New("Loopback metadata file not found") - } + if devices.metadataDevice == "" { + // Make sure the sparse images exist in /devicemapper/metadata - createdLoopback = !hasData || !hasMetadata - data, err := devices.ensureImage("data", devices.dataLoopbackSize) - if err != nil { - utils.Debugf("Error device ensureImage (data): %s\n", err) - return err - } - metadata, err := devices.ensureImage("metadata", devices.metaDataLoopbackSize) - if err != nil { - utils.Debugf("Error device ensureImage (metadata): %s\n", err) - return err - } + hasMetadata := devices.hasImage("metadata") - dataFile, err := attachLoopDevice(data) - if err != nil { - utils.Debugf("\n--->Err: %s\n", err) - return err - } - defer dataFile.Close() + if !doInit && !hasMetadata { + return errors.New("Loopback metadata file not found") + } - metadataFile, err := attachLoopDevice(metadata) - if err != nil { - utils.Debugf("\n--->Err: %s\n", err) - return err + if !hasMetadata { + createdLoopback = true + } + + metadata, err := devices.ensureImage("metadata", devices.metaDataLoopbackSize) + if err != nil { + utils.Debugf("Error device ensureImage (metadata): %s\n", err) + return err + } + + metadataFile, err = attachLoopDevice(metadata) + if err != nil { + utils.Debugf("\n--->Err: %s\n", err) + return err + } + defer metadataFile.Close() + } else { + metadataFile, err = os.OpenFile(devices.metadataDevice, os.O_RDWR, 0600) + if err != nil { + return err + } } - defer metadataFile.Close() if err := createPool(devices.getPoolName(), dataFile, metadataFile); err != nil { utils.Debugf("\n--->Err: %s\n", err) @@ -1176,6 +1210,10 @@ func NewDeviceSet(root string, doInit bool, options []string) (*DeviceSet, error devices.mkfsArgs = append(devices.mkfsArgs, val) case "dm.mountopt": devices.mountOptions = joinMountOptions(devices.mountOptions, val) + case "dm.metadatadev": + devices.metadataDevice = val + case "dm.datadev": + devices.dataDevice = val default: return nil, fmt.Errorf("Unknown option %s\n", key) } From 0434a2ce64c0ce07e97e9a516cef226be67d5f5b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 1 Apr 2014 15:41:25 +0200 Subject: [PATCH 384/400] devmapper: Add blkdiscard option and disable it on raw devices The blkdiscard hack we do on container/image delete is pretty slow, but required to restore space to the "host" root filesystem. However, it is pretty useless on raw devices, and you may not need it in development either. In a simple test of the devicemapper backend on loopback the time to delete 20 container went from 11 seconds to 0.4 seconds with --storage-opt blkdiscard=false. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- daemon/graphdriver/devmapper/README.md | 16 +++++++++++++ daemon/graphdriver/devmapper/deviceset.go | 28 ++++++++++++++++++----- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/daemon/graphdriver/devmapper/README.md b/daemon/graphdriver/devmapper/README.md index 53b7174db..ccff3f021 100644 --- a/daemon/graphdriver/devmapper/README.md +++ b/daemon/graphdriver/devmapper/README.md @@ -125,3 +125,19 @@ Here is the list of supported options: Example use: ``docker -d --storage-opt dm.datadev=/dev/sdb1 --storage-opt dm.metadatadev=/dev/sdc1`` + + * `dm.blkdiscard` + + Enables or disables the use of blkdiscard when removing + devicemapper devices. This is enabled by default (only) if using + loopback devices and is required to res-parsify the loopback file + on image/container removal. + + Disabling this on loopback can lead to *much* faster container + removal times, but will make the space used in /var/lib/docker + directory not be returned to the system for other use when + containers are removed. + + Example use: + + ``docker -d --storage-opt dm.blkdiscard=false`` diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 497e1102c..a930bf2b9 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -77,6 +77,7 @@ type DeviceSet struct { mkfsArgs []string dataDevice string metadataDevice string + doBlkDiscard bool } type DiskUsage struct { @@ -713,12 +714,14 @@ func (devices *DeviceSet) AddDevice(hash, baseHash string) error { } func (devices *DeviceSet) deleteDevice(info *DevInfo) error { - // This is a workaround for the kernel not discarding block so - // on the thin pool when we remove a thinp device, so we do it - // manually - if err := devices.activateDeviceIfNeeded(info); err == nil { - if err := BlockDeviceDiscard(info.DevName()); err != nil { - utils.Debugf("Error discarding block on device: %s (ignoring)\n", err) + if devices.doBlkDiscard { + // This is a workaround for the kernel not discarding block so + // on the thin pool when we remove a thinp device, so we do it + // manually + if err := devices.activateDeviceIfNeeded(info); err == nil { + if err := BlockDeviceDiscard(info.DevName()); err != nil { + utils.Debugf("Error discarding block on device: %s (ignoring)\n", err) + } } } @@ -1174,8 +1177,10 @@ func NewDeviceSet(root string, doInit bool, options []string) (*DeviceSet, error metaDataLoopbackSize: DefaultMetaDataLoopbackSize, baseFsSize: DefaultBaseFsSize, filesystem: "ext4", + doBlkDiscard: true, } + foundBlkDiscard := false for _, option := range options { key, val, err := utils.ParseKeyValueOpt(option) if err != nil { @@ -1214,11 +1219,22 @@ func NewDeviceSet(root string, doInit bool, options []string) (*DeviceSet, error devices.metadataDevice = val case "dm.datadev": devices.dataDevice = val + case "dm.blkdiscard": + foundBlkDiscard = true + devices.doBlkDiscard, err = strconv.ParseBool(val) + if err != nil { + return nil, err + } default: return nil, fmt.Errorf("Unknown option %s\n", key) } } + // By default, don't do blk discard hack on raw devices, its rarely useful and is expensive + if !foundBlkDiscard && devices.dataDevice != "" { + devices.doBlkDiscard = false + } + if err := devices.initDevmapper(doInit); err != nil { return nil, err } From 8c646ebf16bd0e2618cbca430bc129ee7416a21c Mon Sep 17 00:00:00 2001 From: cpuguy83 Date: Thu, 5 Jun 2014 09:50:27 -0400 Subject: [PATCH 385/400] Fixes incorrect API spec for items moved to HostConfig DNS and VolumesFrom were moved to HostConfig and as such are part of the container start and not create. For some reason 0.10 docs are correct (except for a missing quote in the JSON") but 0.11 and latest are not. Docker-DCO-1.1-Signed-off-by: cpuguy83 (github: cpuguy83) --- docs/sources/reference/api/docker_remote_api_v1.10.md | 2 +- docs/sources/reference/api/docker_remote_api_v1.11.md | 5 +++-- docs/sources/reference/api/docker_remote_api_v1.12.md | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/sources/reference/api/docker_remote_api_v1.10.md b/docs/sources/reference/api/docker_remote_api_v1.10.md index 8676833af..9c39611b3 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.10.md +++ b/docs/sources/reference/api/docker_remote_api_v1.10.md @@ -371,7 +371,7 @@ Start the container `id` "PublishAllPorts":false, "Privileged":false "Dns": ["8.8.8.8"], - "VolumesFrom: ["parent", "other:ro"] + "VolumesFrom": ["parent", "other:ro"] } **Example response**: diff --git a/docs/sources/reference/api/docker_remote_api_v1.11.md b/docs/sources/reference/api/docker_remote_api_v1.11.md index e72545b6f..f639557e1 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.11.md +++ b/docs/sources/reference/api/docker_remote_api_v1.11.md @@ -123,7 +123,6 @@ Create a container "Cmd":[ "date" ], - "Dns":null, "Image":"base", "Volumes":{ "/tmp": {} @@ -410,7 +409,9 @@ Start the container `id` "LxcConf":{"lxc.utsname":"docker"}, "PortBindings":{ "22/tcp": [{ "HostPort": "11022" }] }, "PublishAllPorts":false, - "Privileged":false + "Privileged":false, + "Dns": ["8.8.8.8"], + "VolumesFrom": ["parent", "other:ro"] } **Example response**: diff --git a/docs/sources/reference/api/docker_remote_api_v1.12.md b/docs/sources/reference/api/docker_remote_api_v1.12.md index d941a1d45..08a06a45d 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.12.md +++ b/docs/sources/reference/api/docker_remote_api_v1.12.md @@ -124,12 +124,10 @@ Create a container "Cmd":[ "date" ], - "Dns":null, "Image":"base", "Volumes":{ "/tmp": {} }, - "VolumesFrom":"", "WorkingDir":"", "DisableNetwork": false, "ExposedPorts":{ @@ -411,7 +409,9 @@ Start the container `id` "LxcConf":{"lxc.utsname":"docker"}, "PortBindings":{ "22/tcp": [{ "HostPort": "11022" }] }, "PublishAllPorts":false, - "Privileged":false + "Privileged":false, + "Dns": ["8.8.8.8"], + "VolumesFrom": ["parent", "other:ro"] } **Example response**: From 04620e01547d6526023b49494919f3eb3fcf900a Mon Sep 17 00:00:00 2001 From: cpuguy83 Date: Thu, 5 Jun 2014 14:10:52 -0400 Subject: [PATCH 386/400] Fixes broken ssh server example Docker-DCO-1.1-Signed-off-by: cpuguy83 (github: cpuguy83) --- docs/sources/examples/running_ssh_service.Dockerfile | 7 +++---- docs/sources/examples/running_ssh_service.md | 5 ++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/sources/examples/running_ssh_service.Dockerfile b/docs/sources/examples/running_ssh_service.Dockerfile index dd2acb7a4..978e61042 100644 --- a/docs/sources/examples/running_ssh_service.Dockerfile +++ b/docs/sources/examples/running_ssh_service.Dockerfile @@ -2,16 +2,15 @@ # # VERSION 0.0.1 -FROM ubuntu +FROM debian MAINTAINER Thatcher R. Peskens "thatcher@dotcloud.com" # make sure the package repository is up to date -RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list RUN apt-get update RUN apt-get install -y openssh-server -RUN mkdir /var/run/sshd +RUN mkdir /var/run/sshd RUN echo 'root:screencast' |chpasswd EXPOSE 22 -CMD /usr/sbin/sshd -D +CMD ["/usr/sbin/sshd", "-D"] diff --git a/docs/sources/examples/running_ssh_service.md b/docs/sources/examples/running_ssh_service.md index 5be2c7053..27439f998 100644 --- a/docs/sources/examples/running_ssh_service.md +++ b/docs/sources/examples/running_ssh_service.md @@ -12,11 +12,10 @@ quick access to a test container. # # VERSION 0.0.1 - FROM ubuntu + FROM debian MAINTAINER Thatcher R. Peskens "thatcher@dotcloud.com" # make sure the package repository is up to date - RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list RUN apt-get update RUN apt-get install -y openssh-server @@ -24,7 +23,7 @@ quick access to a test container. RUN echo 'root:screencast' |chpasswd EXPOSE 22 - CMD /usr/sbin/sshd -D + CMD ["/usr/sbin/sshd", "-D"] Build the image using: From d08c965e2c6ed223e10773ac77f95b7b6e273e0a Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 5 Jun 2014 15:08:54 -0400 Subject: [PATCH 387/400] Clarify effect of docker start on started container in CLI docs This behavior changed from v0.9 to v0.10, so document it to prevent any confusion. Docker-DCO-1.1-Signed-off-by: Matthew Heon (github: mheon) --- docs/sources/reference/commandline/cli.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 829f13b9a..3e61c94ad 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1139,6 +1139,9 @@ more details on finding shared images from the commandline. -a, --attach=false Attach container's stdout/stderr and forward all signals to the process -i, --interactive=false Attach container's stdin +When run on a container that has already been started, +takes no action and succeeds unconditionally. + ## stop Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...] From a12453186323372f4d4e23980d3f1caa2de91e5a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 5 Jun 2014 18:37:37 +0000 Subject: [PATCH 388/400] only forward auth to trusted locations Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- registry/registry.go | 29 +++++++++++++++- registry/registry_test.go | 71 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index 7bcf06601..8d1a9f228 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -756,9 +756,36 @@ type Registry struct { indexEndpoint string } +func trustedLocation(req *http.Request) bool { + var ( + trusteds = []string{"docker.com", "docker.io"} + hostname = strings.SplitN(req.Host, ":", 2)[0] + ) + if req.URL.Scheme != "https" { + return false + } + + for _, trusted := range trusteds { + if strings.HasSuffix(hostname, trusted) { + return true + } + } + return false +} + func AddRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error { if via != nil && via[0] != nil { - req.Header = via[0].Header + if trustedLocation(req) && trustedLocation(via[0]) { + req.Header = via[0].Header + } else { + for k, v := range via[0].Header { + if k != "Authorization" { + for _, vv := range v { + req.Header.Add(k, vv) + } + } + } + } } return nil } diff --git a/registry/registry_test.go b/registry/registry_test.go index e207359e6..2857ab4a4 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -2,10 +2,12 @@ package registry import ( "fmt" - "github.com/dotcloud/docker/utils" + "net/http" "net/url" "strings" "testing" + + "github.com/dotcloud/docker/utils" ) var ( @@ -231,3 +233,70 @@ func TestValidRepositoryName(t *testing.T) { t.Fail() } } + +func TestTrustedLocation(t *testing.T) { + for _, url := range []string{"http://example.com", "https://example.com:7777", "http://docker.io", "http://test.docker.io"} { + req, _ := http.NewRequest("GET", url, nil) + if trustedLocation(req) == true { + t.Fatalf("'%s' shouldn't be detected as a trusted location", url) + } + } + + for _, url := range []string{"https://docker.io", "https://test.docker.io:80"} { + req, _ := http.NewRequest("GET", url, nil) + if trustedLocation(req) == false { + t.Fatalf("'%s' should be detected as a trusted location", url) + } + } +} + +func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) { + for _, urls := range [][]string{ + {"http://docker.io", "https://docker.com"}, + {"https://foo.docker.io:7777", "http://bar.docker.com"}, + {"https://foo.docker.io", "https://example.com"}, + } { + reqFrom, _ := http.NewRequest("GET", urls[0], nil) + reqFrom.Header.Add("Content-Type", "application/json") + reqFrom.Header.Add("Authorization", "super_secret") + reqTo, _ := http.NewRequest("GET", urls[1], nil) + + AddRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom}) + + if len(reqTo.Header) != 1 { + t.Fatal("Expected 1 headers, got %d", len(reqTo.Header)) + } + + if reqTo.Header.Get("Content-Type") != "application/json" { + t.Fatal("'Content-Type' should be 'application/json'") + } + + if reqTo.Header.Get("Authorization") != "" { + t.Fatal("'Authorization' should be empty") + } + } + + for _, urls := range [][]string{ + {"https://docker.io", "https://docker.com"}, + {"https://foo.docker.io:7777", "https://bar.docker.com"}, + } { + reqFrom, _ := http.NewRequest("GET", urls[0], nil) + reqFrom.Header.Add("Content-Type", "application/json") + reqFrom.Header.Add("Authorization", "super_secret") + reqTo, _ := http.NewRequest("GET", urls[1], nil) + + AddRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom}) + + if len(reqTo.Header) != 2 { + t.Fatal("Expected 2 headers, got %d", len(reqTo.Header)) + } + + if reqTo.Header.Get("Content-Type") != "application/json" { + t.Fatal("'Content-Type' should be 'application/json'") + } + + if reqTo.Header.Get("Authorization") != "super_secret" { + t.Fatal("'Authorization' should be 'super_secret'") + } + } +} From 1a3d43c23ec7bbb1aa206581acd0497c47e29a2f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 4 Jun 2014 16:46:30 -0700 Subject: [PATCH 389/400] Update nsinit to be nicer to work with and test Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/exec.go | 74 ++++++++++ pkg/libcontainer/nsinit/init.go | 43 ++++++ pkg/libcontainer/nsinit/main.go | 231 ++++--------------------------- pkg/libcontainer/nsinit/spec.go | 38 +++++ pkg/libcontainer/nsinit/stats.go | 42 ++++++ pkg/libcontainer/nsinit/utils.go | 52 +++++++ 6 files changed, 277 insertions(+), 203 deletions(-) create mode 100644 pkg/libcontainer/nsinit/exec.go create mode 100644 pkg/libcontainer/nsinit/init.go create mode 100644 pkg/libcontainer/nsinit/spec.go create mode 100644 pkg/libcontainer/nsinit/stats.go create mode 100644 pkg/libcontainer/nsinit/utils.go diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go new file mode 100644 index 000000000..ef33d3a5a --- /dev/null +++ b/pkg/libcontainer/nsinit/exec.go @@ -0,0 +1,74 @@ +package main + +import ( + "fmt" + "log" + "os" + "os/exec" + "os/signal" + + "github.com/codegangsta/cli" + "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/namespaces" +) + +var execCommand = cli.Command{ + Name: "exec", + Usage: "execute a new command inside a container", + Action: execAction, +} + +func execAction(context *cli.Context) { + var ( + err error + nspid, exitCode int + ) + + if nspid, err = readPid(); err != nil && !os.IsNotExist(err) { + log.Fatalf("unable to read pid: %s", err) + } + + if nspid > 0 { + exitCode, err = namespaces.ExecIn(container, nspid, []string(context.Args())) + } else { + term := namespaces.NewTerminal(os.Stdin, os.Stdout, os.Stderr, container.Tty) + exitCode, err = startContainer(container, term, dataPath, []string(context.Args())) + } + + if err != nil { + log.Fatalf("failed to exec: %s", err) + } + + os.Exit(exitCode) +} + +// startContainer starts the container. Returns the exit status or -1 and an +// error. +// +// Signals sent to the current process will be forwarded to container. +func startContainer(container *libcontainer.Container, term namespaces.Terminal, dataPath string, args []string) (int, error) { + var ( + cmd *exec.Cmd + sigc = make(chan os.Signal, 10) + ) + + signal.Notify(sigc) + + createCommand := func(container *libcontainer.Container, console, rootfs, dataPath, init string, pipe *os.File, args []string) *exec.Cmd { + cmd = namespaces.DefaultCreateCommand(container, console, rootfs, dataPath, init, pipe, args) + if logPath != "" { + cmd.Env = append(cmd.Env, fmt.Sprintf("log=%s", logPath)) + } + return cmd + } + + startCallback := func() { + go func() { + for sig := range sigc { + cmd.Process.Signal(sig) + } + }() + } + + return namespaces.Exec(container, term, "", dataPath, args, createCommand, startCallback) +} diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go new file mode 100644 index 000000000..561b93bb7 --- /dev/null +++ b/pkg/libcontainer/nsinit/init.go @@ -0,0 +1,43 @@ +package main + +import ( + "log" + "os" + "strconv" + + "github.com/codegangsta/cli" + "github.com/dotcloud/docker/pkg/libcontainer/namespaces" +) + +var ( + dataPath = os.Getenv("data_path") + console = os.Getenv("console") + rawPipeFd = os.Getenv("pipe") + + initCommand = cli.Command{ + Name: "init", + Usage: "runs the init process inside the namespace", + Action: initAction, + } +) + +func initAction(context *cli.Context) { + rootfs, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + + pipeFd, err := strconv.Atoi(rawPipeFd) + if err != nil { + log.Fatal(err) + } + + syncPipe, err := namespaces.NewSyncPipeFromFd(0, uintptr(pipeFd)) + if err != nil { + log.Fatalf("unable to create sync pipe: %s", err) + } + + if err := namespaces.Init(container, rootfs, console, syncPipe, []string(context.Args())); err != nil { + log.Fatalf("unable to initialize for container: %s", err) + } +} diff --git a/pkg/libcontainer/nsinit/main.go b/pkg/libcontainer/nsinit/main.go index bddc1992f..29b80bad4 100644 --- a/pkg/libcontainer/nsinit/main.go +++ b/pkg/libcontainer/nsinit/main.go @@ -1,220 +1,45 @@ package main import ( - "encoding/json" - "fmt" - "io/ioutil" "log" "os" - "os/exec" - "os/signal" - "path/filepath" - "strconv" + "github.com/codegangsta/cli" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/cgroups/fs" - "github.com/dotcloud/docker/pkg/libcontainer/namespaces" ) var ( - dataPath = os.Getenv("data_path") - console = os.Getenv("console") - rawPipeFd = os.Getenv("pipe") + container *libcontainer.Container + logPath = os.Getenv("log") ) +func preload(context *cli.Context) (err error) { + container, err = loadContainer() + if err != nil { + return err + } + + if logPath != "" { + } + + return nil +} + func main() { - if len(os.Args) < 2 { - log.Fatalf("invalid number of arguments %d", len(os.Args)) + app := cli.NewApp() + app.Name = "nsinit" + app.Version = "0.1" + app.Author = "libcontainer maintainers" + + app.Before = preload + app.Commands = []cli.Command{ + execCommand, + initCommand, + statsCommand, + specCommand, } - switch os.Args[1] { - case "exec": // this is executed outside of the namespace in the cwd - container, err := loadContainer() - if err != nil { - log.Fatalf("unable to load container: %s", err) - } - - var nspid, exitCode int - if nspid, err = readPid(); err != nil && !os.IsNotExist(err) { - log.Fatalf("unable to read pid: %s", err) - } - - if nspid > 0 { - err = namespaces.ExecIn(container, nspid, os.Args[2:]) - } else { - term := namespaces.NewTerminal(os.Stdin, os.Stdout, os.Stderr, container.Tty) - exitCode, err = startContainer(container, term, dataPath, os.Args[2:]) - } - - if err != nil { - log.Fatalf("failed to exec: %s", err) - } - os.Exit(exitCode) - case "nsenter": // this is executed inside the namespace. - // nsinit nsenter ... - if len(os.Args) < 6 { - log.Fatalf("incorrect usage: nsinit nsenter ...") - } - - container, err := loadContainerFromJson(os.Args[4]) - if err != nil { - log.Fatalf("unable to load container: %s", err) - } - - nspid, err := strconv.Atoi(os.Args[2]) - if err != nil { - log.Fatalf("unable to read pid: %s from %q", err, os.Args[2]) - } - - if nspid <= 0 { - log.Fatalf("cannot enter into namespaces without valid pid: %q", nspid) - } - - err = namespaces.NsEnter(container, os.Args[3], nspid, os.Args[5:]) - if err != nil { - log.Fatalf("failed to nsenter: %s", err) - } - case "init": // this is executed inside of the namespace to setup the container - container, err := loadContainer() - if err != nil { - log.Fatalf("unable to load container: %s", err) - } - - // by default our current dir is always our rootfs - rootfs, err := os.Getwd() - if err != nil { - log.Fatal(err) - } - - pipeFd, err := strconv.Atoi(rawPipeFd) - if err != nil { - log.Fatal(err) - } - syncPipe, err := namespaces.NewSyncPipeFromFd(0, uintptr(pipeFd)) - if err != nil { - log.Fatalf("unable to create sync pipe: %s", err) - } - - if err := namespaces.Init(container, rootfs, console, syncPipe, os.Args[2:]); err != nil { - log.Fatalf("unable to initialize for container: %s", err) - } - case "stats": - container, err := loadContainer() - if err != nil { - log.Fatalf("unable to load container: %s", err) - } - - // returns the stats of the current container. - stats, err := getContainerStats(container) - if err != nil { - log.Printf("Failed to get stats - %v\n", err) - os.Exit(1) - } - fmt.Printf("Stats:\n%v\n", stats) - os.Exit(0) - - case "spec": - container, err := loadContainer() - if err != nil { - log.Fatalf("unable to load container: %s", err) - } - - // returns the spec of the current container. - spec, err := getContainerSpec(container) - if err != nil { - log.Printf("Failed to get spec - %v\n", err) - os.Exit(1) - } - fmt.Printf("Spec:\n%v\n", spec) - os.Exit(0) - - default: - log.Fatalf("command not supported for nsinit %s", os.Args[1]) + if err := app.Run(os.Args); err != nil { + log.Fatal(err) } } - -func loadContainer() (*libcontainer.Container, error) { - f, err := os.Open(filepath.Join(dataPath, "container.json")) - if err != nil { - log.Printf("Path: %q", filepath.Join(dataPath, "container.json")) - return nil, err - } - defer f.Close() - - var container *libcontainer.Container - if err := json.NewDecoder(f).Decode(&container); err != nil { - return nil, err - } - return container, nil -} - -func loadContainerFromJson(rawData string) (*libcontainer.Container, error) { - container := &libcontainer.Container{} - err := json.Unmarshal([]byte(rawData), container) - if err != nil { - return nil, err - } - return container, nil -} - -func readPid() (int, error) { - data, err := ioutil.ReadFile(filepath.Join(dataPath, "pid")) - if err != nil { - return -1, err - } - pid, err := strconv.Atoi(string(data)) - if err != nil { - return -1, err - } - return pid, nil -} - -// startContainer starts the container. Returns the exit status or -1 and an -// error. -// -// Signals sent to the current process will be forwarded to container. -func startContainer(container *libcontainer.Container, term namespaces.Terminal, dataPath string, args []string) (int, error) { - var ( - cmd *exec.Cmd - sigc = make(chan os.Signal, 10) - ) - - signal.Notify(sigc) - - createCommand := func(container *libcontainer.Container, console, rootfs, dataPath, init string, pipe *os.File, args []string) *exec.Cmd { - cmd = namespaces.DefaultCreateCommand(container, console, rootfs, dataPath, init, pipe, args) - return cmd - } - - startCallback := func() { - go func() { - for sig := range sigc { - cmd.Process.Signal(sig) - } - }() - } - - return namespaces.Exec(container, term, "", dataPath, args, createCommand, startCallback) -} - -// returns the container stats in json format. -func getContainerStats(container *libcontainer.Container) (string, error) { - stats, err := fs.GetStats(container.Cgroups) - if err != nil { - return "", err - } - out, err := json.MarshalIndent(stats, "", "\t") - if err != nil { - return "", err - } - return string(out), nil -} - -// returns the container spec in json format. -func getContainerSpec(container *libcontainer.Container) (string, error) { - spec, err := json.MarshalIndent(container, "", "\t") - if err != nil { - return "", err - } - return string(spec), nil -} diff --git a/pkg/libcontainer/nsinit/spec.go b/pkg/libcontainer/nsinit/spec.go new file mode 100644 index 000000000..92a2f64cd --- /dev/null +++ b/pkg/libcontainer/nsinit/spec.go @@ -0,0 +1,38 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "os" + + "github.com/codegangsta/cli" + "github.com/dotcloud/docker/pkg/libcontainer" +) + +var specCommand = cli.Command{ + Name: "spec", + Usage: "display the container specification", + Action: specAction, +} + +func specAction(context *cli.Context) { + // returns the spec of the current container. + spec, err := getContainerSpec(container) + if err != nil { + log.Printf("Failed to get spec - %v\n", err) + os.Exit(1) + } + fmt.Printf("Spec:\n%v\n", spec) + os.Exit(0) + +} + +// returns the container spec in json format. +func getContainerSpec(container *libcontainer.Container) (string, error) { + spec, err := json.MarshalIndent(container, "", "\t") + if err != nil { + return "", err + } + return string(spec), nil +} diff --git a/pkg/libcontainer/nsinit/stats.go b/pkg/libcontainer/nsinit/stats.go new file mode 100644 index 000000000..0e930823b --- /dev/null +++ b/pkg/libcontainer/nsinit/stats.go @@ -0,0 +1,42 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "os" + + "github.com/codegangsta/cli" + "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/cgroups/fs" +) + +var statsCommand = cli.Command{ + Name: "stats", + Usage: "display statistics for the container", + Action: statsAction, +} + +func statsAction(context *cli.Context) { + // returns the stats of the current container. + stats, err := getContainerStats(container) + if err != nil { + log.Printf("Failed to get stats - %v\n", err) + os.Exit(1) + } + fmt.Printf("Stats:\n%v\n", stats) + os.Exit(0) +} + +// returns the container stats in json format. +func getContainerStats(container *libcontainer.Container) (string, error) { + stats, err := fs.GetStats(container.Cgroups) + if err != nil { + return "", err + } + out, err := json.MarshalIndent(stats, "", "\t") + if err != nil { + return "", err + } + return string(out), nil +} diff --git a/pkg/libcontainer/nsinit/utils.go b/pkg/libcontainer/nsinit/utils.go new file mode 100644 index 000000000..fc311a351 --- /dev/null +++ b/pkg/libcontainer/nsinit/utils.go @@ -0,0 +1,52 @@ +package main + +import ( + "encoding/json" + "io/ioutil" + "log" + "os" + "path/filepath" + "strconv" + + "github.com/dotcloud/docker/pkg/libcontainer" +) + +func loadContainer() (*libcontainer.Container, error) { + f, err := os.Open(filepath.Join(dataPath, "container.json")) + if err != nil { + return nil, err + } + defer f.Close() + + var container *libcontainer.Container + if err := json.NewDecoder(f).Decode(&container); err != nil { + return nil, err + } + + return container, nil +} + +func readPid() (int, error) { + data, err := ioutil.ReadFile(filepath.Join(dataPath, "pid")) + if err != nil { + return -1, err + } + + pid, err := strconv.Atoi(string(data)) + if err != nil { + return -1, err + } + + return pid, nil +} + +func openLog(name string) error { + f, err := os.OpenFile(name, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0755) + if err != nil { + return err + } + + log.SetOutput(f) + + return nil +} From f3621531579495e7de911004d47e077d421fcfb8 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 5 Jun 2014 14:28:09 -0700 Subject: [PATCH 390/400] Update for nsenter Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/exec.go | 12 +++++---- pkg/libcontainer/nsinit/init.go | 5 ++++ pkg/libcontainer/nsinit/main.go | 17 +++++-------- pkg/libcontainer/nsinit/nsenter.go | 40 ++++++++++++++++++++++++++++++ pkg/libcontainer/nsinit/spec.go | 14 ++++++----- pkg/libcontainer/nsinit/stats.go | 14 +++++++---- pkg/libcontainer/nsinit/utils.go | 10 ++++++++ 7 files changed, 85 insertions(+), 27 deletions(-) create mode 100644 pkg/libcontainer/nsinit/nsenter.go diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index ef33d3a5a..d4ce1ca8c 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -19,17 +19,19 @@ var execCommand = cli.Command{ } func execAction(context *cli.Context) { - var ( - err error - nspid, exitCode int - ) + var nspid, exitCode int + + container, err := loadContainer() + if err != nil { + log.Fatal(err) + } if nspid, err = readPid(); err != nil && !os.IsNotExist(err) { log.Fatalf("unable to read pid: %s", err) } if nspid > 0 { - exitCode, err = namespaces.ExecIn(container, nspid, []string(context.Args())) + err = namespaces.ExecIn(container, nspid, []string(context.Args())) } else { term := namespaces.NewTerminal(os.Stdin, os.Stdout, os.Stderr, container.Tty) exitCode, err = startContainer(container, term, dataPath, []string(context.Args())) diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 561b93bb7..20096f021 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -22,6 +22,11 @@ var ( ) func initAction(context *cli.Context) { + container, err := loadContainer() + if err != nil { + log.Fatal(err) + } + rootfs, err := os.Getwd() if err != nil { log.Fatal(err) diff --git a/pkg/libcontainer/nsinit/main.go b/pkg/libcontainer/nsinit/main.go index 29b80bad4..20132de0e 100644 --- a/pkg/libcontainer/nsinit/main.go +++ b/pkg/libcontainer/nsinit/main.go @@ -5,21 +5,15 @@ import ( "os" "github.com/codegangsta/cli" - "github.com/dotcloud/docker/pkg/libcontainer" ) -var ( - container *libcontainer.Container - logPath = os.Getenv("log") -) - -func preload(context *cli.Context) (err error) { - container, err = loadContainer() - if err != nil { - return err - } +var logPath = os.Getenv("log") +func preload(context *cli.Context) error { if logPath != "" { + if err := openLog(logPath); err != nil { + return err + } } return nil @@ -37,6 +31,7 @@ func main() { initCommand, statsCommand, specCommand, + nsenterCommand, } if err := app.Run(os.Args); err != nil { diff --git a/pkg/libcontainer/nsinit/nsenter.go b/pkg/libcontainer/nsinit/nsenter.go new file mode 100644 index 000000000..54644282d --- /dev/null +++ b/pkg/libcontainer/nsinit/nsenter.go @@ -0,0 +1,40 @@ +package main + +import ( + "log" + "strconv" + + "github.com/codegangsta/cli" + "github.com/dotcloud/docker/pkg/libcontainer/namespaces" +) + +var nsenterCommand = cli.Command{ + Name: "nsenter", + Usage: "init process for entering an existing namespace", + Action: nsenterAction, +} + +func nsenterAction(context *cli.Context) { + args := context.Args() + if len(args) < 4 { + log.Fatalf("incorrect usage: ...") + } + + container, err := loadContainerFromJson(args.Get(2)) + if err != nil { + log.Fatalf("unable to load container: %s", err) + } + + nspid, err := strconv.Atoi(args.Get(0)) + if err != nil { + log.Fatalf("unable to read pid: %s from %q", err, args.Get(0)) + } + + if nspid <= 0 { + log.Fatalf("cannot enter into namespaces without valid pid: %q", nspid) + } + + if err := namespaces.NsEnter(container, args.Get(1), nspid, args[3:]); err != nil { + log.Fatalf("failed to nsenter: %s", err) + } +} diff --git a/pkg/libcontainer/nsinit/spec.go b/pkg/libcontainer/nsinit/spec.go index 92a2f64cd..2eb4da9fc 100644 --- a/pkg/libcontainer/nsinit/spec.go +++ b/pkg/libcontainer/nsinit/spec.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "log" - "os" "github.com/codegangsta/cli" "github.com/dotcloud/docker/pkg/libcontainer" @@ -17,15 +16,17 @@ var specCommand = cli.Command{ } func specAction(context *cli.Context) { - // returns the spec of the current container. + container, err := loadContainer() + if err != nil { + log.Fatal(err) + } + spec, err := getContainerSpec(container) if err != nil { - log.Printf("Failed to get spec - %v\n", err) - os.Exit(1) + log.Fatalf("Failed to get spec - %v\n", err) } - fmt.Printf("Spec:\n%v\n", spec) - os.Exit(0) + fmt.Printf("Spec:\n%v\n", spec) } // returns the container spec in json format. @@ -34,5 +35,6 @@ func getContainerSpec(container *libcontainer.Container) (string, error) { if err != nil { return "", err } + return string(spec), nil } diff --git a/pkg/libcontainer/nsinit/stats.go b/pkg/libcontainer/nsinit/stats.go index 0e930823b..023b40a82 100644 --- a/pkg/libcontainer/nsinit/stats.go +++ b/pkg/libcontainer/nsinit/stats.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "log" - "os" "github.com/codegangsta/cli" "github.com/dotcloud/docker/pkg/libcontainer" @@ -18,14 +17,17 @@ var statsCommand = cli.Command{ } func statsAction(context *cli.Context) { - // returns the stats of the current container. + container, err := loadContainer() + if err != nil { + log.Fatal(err) + } + stats, err := getContainerStats(container) if err != nil { - log.Printf("Failed to get stats - %v\n", err) - os.Exit(1) + log.Fatalf("Failed to get stats - %v\n", err) } + fmt.Printf("Stats:\n%v\n", stats) - os.Exit(0) } // returns the container stats in json format. @@ -34,9 +36,11 @@ func getContainerStats(container *libcontainer.Container) (string, error) { if err != nil { return "", err } + out, err := json.MarshalIndent(stats, "", "\t") if err != nil { return "", err } + return string(out), nil } diff --git a/pkg/libcontainer/nsinit/utils.go b/pkg/libcontainer/nsinit/utils.go index fc311a351..9926e2721 100644 --- a/pkg/libcontainer/nsinit/utils.go +++ b/pkg/libcontainer/nsinit/utils.go @@ -50,3 +50,13 @@ func openLog(name string) error { return nil } + +func loadContainerFromJson(rawData string) (*libcontainer.Container, error) { + var container *libcontainer.Container + + if err := json.Unmarshal([]byte(rawData), &container); err != nil { + return nil, err + } + + return container, nil +} From fad14eda3af8b4ffa59f74dc5c6583d22bcc9471 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 5 Jun 2014 14:52:06 -0700 Subject: [PATCH 391/400] Revert "Initial links for Docker Hub rename" This reverts commit 2819677c215bd409b4ce4da51f0ddfb303760796. Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- docs/mkdocs.yml | 16 ++--- docs/s3_website.json | 3 +- docs/sources/articles/basics.md | 4 +- docs/sources/docker-hub/accounts.md | 31 ---------- docs/sources/docker-io/accounts.md | 32 ++++++++++ .../{docker-hub => docker-io}/builds.md | 58 ++++++++----------- .../sources/{docker-hub => docker-io}/home.md | 10 ++-- .../{docker-hub => docker-io}/index.md | 2 +- .../{docker-hub => docker-io}/repos.md | 19 +++--- docs/sources/examples/mongodb.md | 15 ++--- docs/sources/examples/nodejs_web_app.md | 6 +- docs/sources/examples/postgresql_service.md | 2 +- docs/sources/examples/running_riak_service.md | 4 +- docs/sources/faq.md | 2 +- docs/sources/index.md | 2 +- .../introduction/understanding-docker.md | 18 +++--- docs/sources/reference/api.md | 2 +- docs/sources/reference/api/docker-io_api.md | 10 ++-- .../reference/api/docker_remote_api_v1.0.md | 2 +- .../reference/api/docker_remote_api_v1.1.md | 2 +- .../reference/api/docker_remote_api_v1.10.md | 2 +- .../reference/api/docker_remote_api_v1.11.md | 2 +- .../reference/api/docker_remote_api_v1.12.md | 2 +- .../reference/api/docker_remote_api_v1.2.md | 2 +- .../reference/api/docker_remote_api_v1.3.md | 2 +- .../reference/api/docker_remote_api_v1.4.md | 2 +- .../reference/api/docker_remote_api_v1.5.md | 2 +- .../reference/api/docker_remote_api_v1.6.md | 2 +- .../reference/api/docker_remote_api_v1.7.md | 2 +- .../reference/api/docker_remote_api_v1.8.md | 2 +- .../reference/api/docker_remote_api_v1.9.md | 2 +- docs/sources/reference/commandline/cli.md | 14 ++--- docs/sources/terms/registry.md | 2 +- docs/sources/userguide/dockerimages.md | 22 +++---- .../userguide/{dockerhub.md => dockerio.md} | 24 ++++---- docs/sources/userguide/dockerizing.md | 2 +- docs/sources/userguide/dockerrepos.md | 52 ++++++++--------- docs/sources/userguide/dockervolumes.md | 4 +- docs/sources/userguide/index.md | 26 ++++----- docs/sources/userguide/usingdocker.md | 2 +- 40 files changed, 202 insertions(+), 208 deletions(-) delete mode 100644 docs/sources/docker-hub/accounts.md create mode 100644 docs/sources/docker-io/accounts.md rename docs/sources/{docker-hub => docker-io}/builds.md (78%) rename docs/sources/{docker-hub => docker-io}/home.md (54%) rename docs/sources/{docker-hub => docker-io}/index.md (87%) rename docs/sources/{docker-hub => docker-io}/repos.md (83%) rename docs/sources/userguide/{dockerhub.md => dockerio.md} (72%) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 03483abd0..2835cd9dd 100755 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -51,19 +51,19 @@ pages: # User Guide: - ['userguide/index.md', 'User Guide', 'The Docker User Guide' ] -- ['userguide/dockerhub.md', 'User Guide', 'Getting Started with Docker Hub' ] +- ['userguide/dockerio.md', 'User Guide', 'Getting Started with Docker.io' ] - ['userguide/dockerizing.md', 'User Guide', 'Dockerizing Applications' ] - ['userguide/usingdocker.md', 'User Guide', 'Working with Containers' ] - ['userguide/dockerimages.md', 'User Guide', 'Working with Docker Images' ] - ['userguide/dockerlinks.md', 'User Guide', 'Linking containers together' ] - ['userguide/dockervolumes.md', 'User Guide', 'Managing data in containers' ] -- ['userguide/dockerrepos.md', 'User Guide', 'Working with Docker Hub' ] +- ['userguide/dockerrepos.md', 'User Guide', 'Working with Docker.io' ] -# Docker Hub docs: -- ['docker-hub/index.md', 'Docker Hub', 'Docker Hub' ] -- ['docker-hub/accounts.md', 'Docker Hub', 'Accounts'] -- ['docker-hub/repos.md', 'Docker Hub', 'Repositories'] -- ['docker-hub/builds.md', 'Docker Hub', 'Automated Builds'] +# Docker.io docs: +- ['docker-io/index.md', 'Docker.io', 'Docker.io' ] +- ['docker-io/accounts.md', 'Docker.io', 'Accounts'] +- ['docker-io/repos.md', 'Docker.io', 'Repositories'] +- ['docker-io/builds.md', 'Docker.io', 'Automated Builds'] # Examples: - ['examples/index.md', '**HIDDEN**'] @@ -99,7 +99,7 @@ pages: - ['faq.md', 'Reference', 'FAQ'] - ['reference/run.md', 'Reference', 'Run Reference'] - ['reference/api/index.md', '**HIDDEN**'] -- ['reference/api/docker-io_api.md', 'Reference', 'Docker Hub API'] +- ['reference/api/docker-io_api.md', 'Reference', 'Docker.io API'] - ['reference/api/registry_api.md', 'Reference', 'Docker Registry API'] - ['reference/api/registry_index_spec.md', 'Reference', 'Registry & Index Spec'] - ['reference/api/docker_remote_api.md', 'Reference', 'Docker Remote API'] diff --git a/docs/s3_website.json b/docs/s3_website.json index 1e6aeea75..eab6ae820 100644 --- a/docs/s3_website.json +++ b/docs/s3_website.json @@ -18,8 +18,7 @@ { "Condition": { "KeyPrefixEquals": "use/working_with_links_names/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "userguide/dockerlinks/" } }, { "Condition": { "KeyPrefixEquals": "use/workingwithrepository/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "userguide/dockerrepos/" } }, { "Condition": { "KeyPrefixEquals": "use/port_redirection" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "userguide/dockerlinks/" } }, - { "Condition": { "KeyPrefixEquals": "use/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "examples/" } }, - { "Condition": { "KeyPrefixEquals": "docker-io/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "docker-hub/" } } + { "Condition": { "KeyPrefixEquals": "use/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "examples/" } } ] } diff --git a/docs/sources/articles/basics.md b/docs/sources/articles/basics.md index e93156965..cd4a9df65 100644 --- a/docs/sources/articles/basics.md +++ b/docs/sources/articles/basics.md @@ -26,8 +26,8 @@ for installation instructions. $ sudo docker pull ubuntu This will find the `ubuntu` image by name on -[*Docker Hub*](/userguide/dockerrepos/#find-public-images-on-docker-hub) -and download it from [Docker Hub](https://hub.docker.com) to a local +[*Docker.io*](/userguide/dockerrepos/#find-public-images-on-dockerio) +and download it from [Docker.io](https://index.docker.io) to a local image cache. > **Note**: diff --git a/docs/sources/docker-hub/accounts.md b/docs/sources/docker-hub/accounts.md deleted file mode 100644 index 43df62a9b..000000000 --- a/docs/sources/docker-hub/accounts.md +++ /dev/null @@ -1,31 +0,0 @@ -page_title: Accounts on Docker Hub -page_description: Docker Hub accounts -page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub, docs, documentation - -# Accounts on Docker Hub - -## Docker Hub Accounts - -You can `search` for Docker images and `pull` them from [Docker Hub](https://hub.docker.com) -without signing in or even having an account. However, in order to `push` images, -leave comments or to *star* a repository, you are going to need a [Docker Hub](https://hub.docker.com) account. - -### Registration for a Docker Hub Account - -You can get a [Docker Hub](https://hub.docker.com) account by -[signing up for one here](https://hub.docker.com/account/signup/). A valid -email address is required to register, which you will need to verify for -account activation. - -### Email activation process - -You need to have at least one verified email address to be able to use your -[Docker Hub](https://hub.docker.com) account. If you can't find the validation email, -you can request another by visiting the [Resend Email Confirmation]( -https://hub.docker.com/account/resend-email-confirmation/) page. - -### Password reset process - -If you can't access your account for some reason, you can reset your password -from the [*Password Reset*](https://hub.docker.com/account/forgot-password/) -page. diff --git a/docs/sources/docker-io/accounts.md b/docs/sources/docker-io/accounts.md new file mode 100644 index 000000000..cfbcd9512 --- /dev/null +++ b/docs/sources/docker-io/accounts.md @@ -0,0 +1,32 @@ +page_title: Accounts on Docker.io +page_description: Docker.io accounts +page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker.io, docs, documentation + +# Accounts on Docker.io + +## Docker.io Accounts + +You can `search` for Docker images and `pull` them from [Docker.io](https://index.docker.io) +without signing in or even having an account. However, in order to `push` images, +leave comments or to *star* a repository, you are going to need a [Docker.io]( +https://www.docker.io) account. + +### Registration for a Docker.io Account + +You can get a [Docker.io](https://index.docker.io) account by +[signing up for one here](https://www.docker.io/account/signup/). A valid +email address is required to register, which you will need to verify for +account activation. + +### Email activation process + +You need to have at least one verified email address to be able to use your +[Docker.io](https://index.docker.io) account. If you can't find the validation email, +you can request another by visiting the [Resend Email Confirmation]( +https://www.docker.io/account/resend-email-confirmation/) page. + +### Password reset process + +If you can't access your account for some reason, you can reset your password +from the [*Password Reset*](https://www.docker.io/account/forgot-password/) +page. \ No newline at end of file diff --git a/docs/sources/docker-hub/builds.md b/docs/sources/docker-io/builds.md similarity index 78% rename from docs/sources/docker-hub/builds.md rename to docs/sources/docker-io/builds.md index 09876479f..c70de4006 100644 --- a/docs/sources/docker-hub/builds.md +++ b/docs/sources/docker-io/builds.md @@ -1,13 +1,13 @@ -page_title: Automated Builds on Docker Hub -page_description: Docker Hub Automated Builds -page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub, docs, documentation, trusted, builds, trusted builds, automated builds -# Automated Builds on Docker Hub +page_title: Automated Builds on Docker.io +page_description: Docker.io Automated Builds +page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker.io, docs, documentation, trusted, builds, trusted builds, automated, automated builds +# Automated Builds on Docker.io ## Automated Builds *Automated Builds* is a special feature allowing you to specify a source repository with a `Dockerfile` to be built by the -[Docker Hub](https://hub.docker.com) build clusters. The system will +[Docker.io](https://index.docker.io) build clusters. The system will clone your repository and build the `Dockerfile` using the repository as the context. The resulting image will then be uploaded to the registry and marked as an *Automated Build*. @@ -26,28 +26,27 @@ on both [GitHub](http://github.com) and ### Setting up Automated Builds with GitHub -In order to setup an Automated Build, you need to first link your -[Docker Hub](https://hub.docker.com) account with a GitHub one. This -will allow the registry to see your repositories. +In order to setup an Automated Build, you need to first link your [Docker.io]( +https://index.docker.io) account with a GitHub one. This will allow the registry +to see your repositories. -> *Note:* -> We currently request access for *read* and *write* since -> [Docker Hub](https://hub.docker.com) needs to setup a GitHub service -> hook. Although nothing else is done with your account, this is how -> GitHub manages permissions, sorry! +> *Note:* We currently request access for *read* and *write* since [Docker.io]( +> https://index.docker.io) needs to setup a GitHub service hook. Although nothing +> else is done with your account, this is how GitHub manages permissions, sorry! -Click on the [Automated Builds -tab](https://registry.hub.docker.com/builds/) to get started and then -select [+ Add New](https://registry.hub.docker.com/builds/add/). +Click on the [Automated Builds tab](https://index.docker.io/builds/) to +get started and then select [+ Add +New](https://index.docker.io/builds/add/). -Select the [GitHub service](https://registry.hub.docker.com/associate/github/). +Select the [GitHub +service](https://index.docker.io/associate/github/). Then follow the instructions to authorize and link your GitHub account -to Docker Hub. +to Docker.io. #### Creating an Automated Build -You can [create an Automated Build](https://registry.hub.docker.com/builds/github/select/) +You can [create an Automated Build](https://index.docker.io/builds/github/select/) from any of your public or private GitHub repositories with a `Dockerfile`. #### GitHub organizations @@ -87,36 +86,29 @@ Automated Build: ### Setting up Automated Builds with BitBucket In order to setup an Automated Build, you need to first link your -[Docker Hub](https://hub.docker.com) account with a BitBucket one. This +[Docker.io]( https://index.docker.io) account with a BitBucket one. This will allow the registry to see your repositories. -Click on the [Automated Builds tab](https://registry.hub.docker.com/builds/) to +Click on the [Automated Builds tab](https://index.docker.io/builds/) to get started and then select [+ Add -New](https://registry.hub.docker.com/builds/add/). +New](https://index.docker.io/builds/add/). Select the [BitBucket -service](https://registry.hub.docker.com/associate/bitbucket/). +service](https://index.docker.io/associate/bitbucket/). Then follow the instructions to authorize and link your BitBucket account -to Docker Hub. +to Docker.io. #### Creating an Automated Build -<<<<<<< HEAD:docs/sources/docker-io/builds.md You can [create an Automated Build](https://index.docker.io/builds/bitbucket/select/) from any of your public or private BitBucket repositories with a `Dockerfile`. -======= -You can [create a Trusted -Build](https://registry.hub.docker.com/builds/bitbucket/select/) -from any of your public or private BitBucket repositories with a -`Dockerfile`. ->>>>>>> Initial links for Docker Hub rename:docs/sources/docker-hub/builds.md ### The Dockerfile and Automated Builds During the build process, we copy the contents of your `Dockerfile`. We also -add it to the [Docker Hub](https://hub.docker.com) for the Docker community +add it to the [Docker.io](https://index.docker.io) for the Docker community to see on the repository page. ### README.md @@ -171,7 +163,7 @@ payload: "description":"my docker repo that does cool things", "is_automated":false, "full_description":"This is my full description", - "repo_url":"https://registry.hub.docker.com/u/username/reponame/", + "repo_url":"https://index.docker.io/u/username/reponame/", "owner":"username", "is_official":false, "is_private":false, diff --git a/docs/sources/docker-hub/home.md b/docs/sources/docker-io/home.md similarity index 54% rename from docs/sources/docker-hub/home.md rename to docs/sources/docker-io/home.md index 15baf7b83..d29de76fb 100644 --- a/docs/sources/docker-hub/home.md +++ b/docs/sources/docker-io/home.md @@ -1,13 +1,13 @@ -page_title: The Docker Hub Registry Help +page_title: The Docker.io Registry Help page_description: The Docker Registry help documentation home -page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub, docs, documentation +page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker.io, docs, documentation -# The Docker Hub Registry Help +# The Docker.io Registry Help ## Introduction -For your questions about the [Docker Hub](https://hub.docker.com) registry you +For your questions about the [Docker.io](https://index.docker.io) registry you can use [this documentation](docs.md). If you can not find something you are looking for, please feel free to -[contact us](https://docker.com/resources/support/). +[contact us](https://index.docker.io/help/support/). \ No newline at end of file diff --git a/docs/sources/docker-hub/index.md b/docs/sources/docker-io/index.md similarity index 87% rename from docs/sources/docker-hub/index.md rename to docs/sources/docker-io/index.md index 250f2b3a1..dc83f0b28 100644 --- a/docs/sources/docker-hub/index.md +++ b/docs/sources/docker-io/index.md @@ -1,4 +1,4 @@ -# Docker Hub +# Docker.io ## Contents: diff --git a/docs/sources/docker-hub/repos.md b/docs/sources/docker-io/repos.md similarity index 83% rename from docs/sources/docker-hub/repos.md rename to docs/sources/docker-io/repos.md index 5f8cb5b0e..11170182a 100644 --- a/docs/sources/docker-hub/repos.md +++ b/docs/sources/docker-io/repos.md @@ -1,15 +1,16 @@ -page_title: Repositories and Images on Docker Hub -page_description: Repositories and Images on Docker Hub -page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub, docs, documentation +page_title: Repositories and Images on Docker.io +page_description: Repositories and Images on Docker.io +page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker.io, docs, documentation -# Repositories and Images on Docker Hub +# Repositories and Images on Docker.io ## Searching for repositories and images You can `search` for all the publicly available repositories and images using Docker. If a repository is not public (i.e., private), it won't be listed on the repository search results. To see repository statuses, you can look at your -[profile page](https://hub.docker.com) on [Docker Hub](https://hub.docker.com). +[profile page](https://index.docker.io/account/) on [Docker.io]( +https://index.docker.io). ## Repositories @@ -26,8 +27,8 @@ appropriate, you can flag them for the admins' review. ### Private Docker Repositories -To work with a private repository on [Docker Hub](https://hub.docker.com), you -will need to add one via the [Add Repository](https://registry.hub.docker.com/account/repositories/add/) +To work with a private repository on [Docker.io](https://index.docker.io), you +will need to add one via the [Add Repository](https://index.docker.io/account/repositories/add) link. Once the private repository is created, you can `push` and `pull` images to and from it using Docker. @@ -44,7 +45,7 @@ designate (i.e., collaborators) from its settings page. From there, you can also switch repository status (*public* to *private*, or viceversa). You will need to have an available private repository slot open before you can do such a switch. If you don't have any, you can always upgrade -your [Docker Hub](https://registry.hub.docker.com/plans/) plan. +your [Docker.io](https://index.docker.io/plans/) plan. ### Collaborators and their role @@ -82,7 +83,7 @@ with a JSON payload similar to the example shown below. "description":"my docker repo that does cool things", "is_automated":false, "full_description":"This is my full description", - "repo_url":"https://registry.hub.docker.com/u/username/reponame/", + "repo_url":"https://index.docker.io/u/username/reponame/", "owner":"username", "is_official":false, "is_private":false, diff --git a/docs/sources/examples/mongodb.md b/docs/sources/examples/mongodb.md index 602f55ca8..17d58e0db 100644 --- a/docs/sources/examples/mongodb.md +++ b/docs/sources/examples/mongodb.md @@ -1,14 +1,15 @@ page_title: Dockerizing MongoDB -page_description: Creating a Docker image with MongoDB pre-installed using a Dockerfile and sharing the image on Docker Hub +page_description: Creating a Docker image with MongoDB pre-installed using a Dockerfile and sharing the image on Docker.io page_keywords: docker, dockerize, dockerizing, article, example, docker.io, platform, package, installation, networking, mongodb, containers, images, image, sharing, dockerfile, build, auto-building, virtualization, framework # Dockerizing MongoDB ## Introduction -In this example, we are going to learn how to build a Docker image with -MongoDB pre-installed. We'll also see how to `push` that image to the -[Docker Hub registry](https://hub.docker.com) and share it with others! +In this example, we are going to learn how to build a Docker image +with MongoDB pre-installed. +We'll also see how to `push` that image to the [Docker.io registry]( +https://index.docker.io) and share it with others! Using Docker and containers for deploying [MongoDB](https://www.mongodb.org/) instances will bring several benefits, such as: @@ -40,7 +41,7 @@ Although optional, it is handy to have comments at the beginning of a > the *parent* of your *Dockerized MongoDB* image. We will build our image using the latest version of Ubuntu from the -[Docker Hub Ubuntu](https://registry.hub.docker.com/_/ubuntu/) repository. +[Docker.io Ubuntu](https://index.docker.io/_/ubuntu/) repository. # Format: FROM repository[:version] FROM ubuntu:latest @@ -108,10 +109,10 @@ experimenting, it is always a good practice to tag Docker images by passing the Once this command is issued, Docker will go through the `Dockerfile` and build the image. The final image will be tagged `my/repo`. -## Pushing the MongoDB image to Docker Hub +## Pushing the MongoDB image to Docker.io All Docker image repositories can be hosted and shared on -[Docker Hub](https://hub.docker.com) with the `docker push` command. For this, +[Docker.io](https://index.docker.io) with the `docker push` command. For this, you need to be logged-in. # Log-in diff --git a/docs/sources/examples/nodejs_web_app.md b/docs/sources/examples/nodejs_web_app.md index cf00e88be..99946e99e 100644 --- a/docs/sources/examples/nodejs_web_app.md +++ b/docs/sources/examples/nodejs_web_app.md @@ -65,9 +65,9 @@ requires to build (this example uses Docker 0.3.4): # DOCKER-VERSION 0.3.4 Next, define the parent image you want to use to build your own image on -top of. Here, we'll use -[CentOS](https://registry.hub.docker.com/_/centos/) (tag: `6.4`) -available on the [Docker Hub](https://hub.docker.com/): +top of. Here, we'll use [CentOS](https://index.docker.io/_/centos/) +(tag: `6.4`) available on the [Docker +index](https://index.docker.io/): FROM centos:6.4 diff --git a/docs/sources/examples/postgresql_service.md b/docs/sources/examples/postgresql_service.md index b9fae49d9..b931fd8ba 100644 --- a/docs/sources/examples/postgresql_service.md +++ b/docs/sources/examples/postgresql_service.md @@ -11,7 +11,7 @@ page_keywords: docker, example, package installation, postgresql ## Installing PostgreSQL on Docker Assuming there is no Docker image that suits your needs on the [Docker -Hub](http://hub.docker.com), you can create one yourself. +Hub]( http://index.docker.io), you can create one yourself. Start by creating a new `Dockerfile`: diff --git a/docs/sources/examples/running_riak_service.md b/docs/sources/examples/running_riak_service.md index 5909b7e2b..098cc9094 100644 --- a/docs/sources/examples/running_riak_service.md +++ b/docs/sources/examples/running_riak_service.md @@ -14,8 +14,8 @@ Create an empty file called `Dockerfile`: $ touch Dockerfile Next, define the parent image you want to use to build your image on top -of. We'll use [Ubuntu](https://registry.hub.docker.cm/_/ubuntu/) (tag: -`latest`), which is available on [Docker Hub](https://hub.docker.com): +of. We'll use [Ubuntu](https://index.docker.io/_/ubuntu/) (tag: +`latest`), which is available on [Docker Hub](http://index.docker.io): # Riak # diff --git a/docs/sources/faq.md b/docs/sources/faq.md index fa7deb4e2..8dbdfd184 100644 --- a/docs/sources/faq.md +++ b/docs/sources/faq.md @@ -96,7 +96,7 @@ functionalities: all your future projects. And so on. - *Sharing.* - Docker has access to a [public registry](https://hub.docker.com) where + Docker has access to a [public registry](http://index.docker.io) where thousands of people have uploaded useful containers: anything from Redis, CouchDB, Postgres to IRC bouncers to Rails app servers to Hadoop to base images for various Linux distros. The diff --git a/docs/sources/index.md b/docs/sources/index.md index 4a66dd1dd..f7295d9c1 100644 --- a/docs/sources/index.md +++ b/docs/sources/index.md @@ -12,7 +12,7 @@ sysadmins to develop, ship, and run applications. Docker consists of: * The Docker Engine - our lightweight and powerful open source container virtualization technology combined with a work flow to help you build and containerize your applications. -* [Docker Hub](https://hub.docker.com) - our SAAS service that helps you +* [Docker.io](https://index.docker.io) - our SAAS service that helps you share and manage your applications stacks. Docker enables applications to be quickly assembled from components and diff --git a/docs/sources/introduction/understanding-docker.md b/docs/sources/introduction/understanding-docker.md index e9f5a1bfe..e9041420a 100644 --- a/docs/sources/introduction/understanding-docker.md +++ b/docs/sources/introduction/understanding-docker.md @@ -70,7 +70,7 @@ resources you have. Docker has two major components: * Docker: the open source container virtualization platform. -* [Docker Hub](https://hub.docker.com): our Software-as-a-Service +* [Docker.io](https://index.docker.io): our Software-as-a-Service platform for sharing and managing Docker containers. **Note:** Docker is licensed with the open source Apache 2.0 license. @@ -119,7 +119,7 @@ portion of Docker. Docker registries hold images. These are public (or private!) stores that you can upload or download images to and from. The public Docker -registry is called [Docker Hub](https://hub.docker.com). It provides a +registry is called [Docker.io](http://index.docker.io). It provides a huge collection of existing images that you can use. These images can be images you create yourself or you can make use of images that others have previously created. You can consider Docker registries the @@ -142,7 +142,7 @@ We've learned so far that: 2. You can create Docker containers from those Docker images to run your applications. 3. You can share those Docker images via - [Docker Hub](https://hub.docker.com) or your own registry. + [Docker.io](https://index.docker.io) or your own registry. Let's look at how these elements combine together to make Docker work. @@ -169,7 +169,7 @@ own as the basis for a new image, for example if you have a base Apache image you could use this as the base of all your web application images. > **Note:** -> Docker usually gets these base images from [Docker Hub](https://hub.docker.com). +> Docker usually gets these base images from [Docker.io](https://index.docker.io). Docker images are then built from these base images using a simple descriptive set of steps we call *instructions*. Each instruction @@ -187,19 +187,19 @@ instructions and returns a final image. ### How does a Docker registry work? The Docker registry is the store for your Docker images. Once you build -a Docker image you can *push* it to a public registry [Docker -Hub](https://hub.docker.com) or to your own registry running behind your +a Docker image you can *push* it to a public registry [Docker.io]( +https://index.docker.io) or to your own registry running behind your firewall. Using the Docker client, you can search for already published images and then pull them down to your Docker host to build containers from them. -[Docker Hub](https://hub.docker.com) provides both public and +[Docker.io](https://index.docker.io) provides both public and private storage for images. Public storage is searchable and can be downloaded by anyone. Private storage is excluded from search results and only you and your users can pull them down and use them to build containers. You can [sign up for a plan -here](https://registry.hub.docker.com/plans/). +here](https://index.docker.io/plans). ### How does a container work? @@ -236,7 +236,7 @@ Docker begins with: - **Pulling the `ubuntu` image:** Docker checks for the presence of the `ubuntu` image and if it doesn't exist locally on the host, then Docker downloads it from - [Docker Hub](https://hub.docker.com). If the image already exists then + [Docker.io](https://index.docker.io). If the image already exists then Docker uses it for the new container. - **Creates a new container:** Once Docker has the image it creates a container from it: diff --git a/docs/sources/reference/api.md b/docs/sources/reference/api.md index 720d40b2e..9f185a0e3 100644 --- a/docs/sources/reference/api.md +++ b/docs/sources/reference/api.md @@ -42,7 +42,7 @@ interfaces: - [3 Authorization](registry_api/#authorization) - - [Docker Hub API](index_api/) + - [Docker.io API](index_api/) - [1. Brief introduction](index_api/#brief-introduction) - [2. Endpoints](index_api/#endpoints) - [2.1 Repository](index_api/#repository) diff --git a/docs/sources/reference/api/docker-io_api.md b/docs/sources/reference/api/docker-io_api.md index d5be332d3..66cf311b4 100644 --- a/docs/sources/reference/api/docker-io_api.md +++ b/docs/sources/reference/api/docker-io_api.md @@ -1,12 +1,12 @@ -page_title: Docker Hub API -page_description: API Documentation for the Docker Hub API -page_keywords: API, Docker, index, REST, documentation, Docker Hub, registry +page_title: Docker.io API +page_description: API Documentation for the Docker.io API +page_keywords: API, Docker, index, REST, documentation, Docker.io, registry -# Docker Hub API +# Docker.io API ## Introduction -- This is the REST API for [Docker Hub](https://hub.docker.com). +- This is the REST API for [Docker.io](http://index.docker.io). - Authorization is done with basic auth over SSL - Not all commands require authentication, only those noted as such. diff --git a/docs/sources/reference/api/docker_remote_api_v1.0.md b/docs/sources/reference/api/docker_remote_api_v1.0.md index 2f17b2a74..ba5338c5f 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.0.md +++ b/docs/sources/reference/api/docker_remote_api_v1.0.md @@ -734,7 +734,7 @@ Remove the image `name` from the filesystem `GET /images/search` -Search for an image on [Docker Hub](https://hub.docker.com) +Search for an image on [Docker.io](https://index.docker.io) **Example request**: diff --git a/docs/sources/reference/api/docker_remote_api_v1.1.md b/docs/sources/reference/api/docker_remote_api_v1.1.md index e777901c6..b884ee69d 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.1.md +++ b/docs/sources/reference/api/docker_remote_api_v1.1.md @@ -745,7 +745,7 @@ Remove the image `name` from the filesystem `GET /images/search` -Search for an image on [Docker Hub](https://hub.docker.com) +Search for an image on [Docker.io](https://index.docker.io) **Example request**: diff --git a/docs/sources/reference/api/docker_remote_api_v1.10.md b/docs/sources/reference/api/docker_remote_api_v1.10.md index 0292c1ab2..9c39611b3 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.10.md +++ b/docs/sources/reference/api/docker_remote_api_v1.10.md @@ -932,7 +932,7 @@ Tag the image `name` into a repository `GET /images/search` -Search for an image on [Docker Hub](https://hub.docker.com). +Search for an image on [Docker.io](https://index.docker.io). > **Note**: > The response keys have changed from API v1.6 to reflect the JSON diff --git a/docs/sources/reference/api/docker_remote_api_v1.11.md b/docs/sources/reference/api/docker_remote_api_v1.11.md index 51c0def92..f639557e1 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.11.md +++ b/docs/sources/reference/api/docker_remote_api_v1.11.md @@ -948,7 +948,7 @@ Remove the image `name` from the filesystem `GET /images/search` -Search for an image on [Docker Hub](https://hub.docker.com). +Search for an image on [Docker.io](https://index.docker.io). > **Note**: > The response keys have changed from API v1.6 to reflect the JSON diff --git a/docs/sources/reference/api/docker_remote_api_v1.12.md b/docs/sources/reference/api/docker_remote_api_v1.12.md index c689ebdd5..08a06a45d 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.12.md +++ b/docs/sources/reference/api/docker_remote_api_v1.12.md @@ -983,7 +983,7 @@ Remove the image `name` from the filesystem `GET /images/search` -Search for an image on [Docker Hub](https://hub.docker.com). +Search for an image on [Docker.io](https://index.docker.io). > **Note**: > The response keys have changed from API v1.6 to reflect the JSON diff --git a/docs/sources/reference/api/docker_remote_api_v1.2.md b/docs/sources/reference/api/docker_remote_api_v1.2.md index cecab5bb4..e231cff02 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.2.md +++ b/docs/sources/reference/api/docker_remote_api_v1.2.md @@ -772,7 +772,7 @@ Remove the image `name` from the filesystem `GET /images/search` -Search for an image on [Docker Hub](https://hub.docker.com) +Search for an image on [Docker.io](https://index.docker.io) **Example request**: diff --git a/docs/sources/reference/api/docker_remote_api_v1.3.md b/docs/sources/reference/api/docker_remote_api_v1.3.md index 1d60b4300..71c70273f 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.3.md +++ b/docs/sources/reference/api/docker_remote_api_v1.3.md @@ -821,7 +821,7 @@ Remove the image `name` from the filesystem `GET /images/search` -Search for an image on [Docker Hub](https://hub.docker.com) +Search for an image on [Docker.io](https://index.docker.io) **Example request**: diff --git a/docs/sources/reference/api/docker_remote_api_v1.4.md b/docs/sources/reference/api/docker_remote_api_v1.4.md index f7d6e82c1..253944fd9 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.4.md +++ b/docs/sources/reference/api/docker_remote_api_v1.4.md @@ -866,7 +866,7 @@ Remove the image `name` from the filesystem `GET /images/search` -Search for an image on [Docker Hub](https://hub.docker.com) +Search for an image on [Docker.io](https://index.docker.io) **Example request**: diff --git a/docs/sources/reference/api/docker_remote_api_v1.5.md b/docs/sources/reference/api/docker_remote_api_v1.5.md index 53d970acc..7dc5334f4 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.5.md +++ b/docs/sources/reference/api/docker_remote_api_v1.5.md @@ -871,7 +871,7 @@ Remove the image `name` from the filesystem `GET /images/search` -Search for an image on [Docker Hub](https://hub.docker.com) +Search for an image on [Docker.io](https://index.docker.io) **Example request**: diff --git a/docs/sources/reference/api/docker_remote_api_v1.6.md b/docs/sources/reference/api/docker_remote_api_v1.6.md index 9b7cded33..021a357b7 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.6.md +++ b/docs/sources/reference/api/docker_remote_api_v1.6.md @@ -975,7 +975,7 @@ Remove the image `name` from the filesystem `GET /images/search` -Search for an image on [Docker Hub](https://hub.docker.com) +Search for an image on [Docker.io](https://index.docker.io) **Example request**: diff --git a/docs/sources/reference/api/docker_remote_api_v1.7.md b/docs/sources/reference/api/docker_remote_api_v1.7.md index 3432e9bb2..02073e9b6 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.7.md +++ b/docs/sources/reference/api/docker_remote_api_v1.7.md @@ -901,7 +901,7 @@ Remove the image `name` from the filesystem `GET /images/search` -Search for an image on [Docker Hub](https://hub.docker.com). +Search for an image on [Docker.io](https://index.docker.io). > **Note**: > The response keys have changed from API v1.6 to reflect the JSON diff --git a/docs/sources/reference/api/docker_remote_api_v1.8.md b/docs/sources/reference/api/docker_remote_api_v1.8.md index 184e107cd..a691930c6 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.8.md +++ b/docs/sources/reference/api/docker_remote_api_v1.8.md @@ -943,7 +943,7 @@ Remove the image `name` from the filesystem `GET /images/search` -Search for an image on [Docker Hub](https://hub.docker.com). +Search for an image on [Docker.io](https://index.docker.io). > **Note**: > The response keys have changed from API v1.6 to reflect the JSON diff --git a/docs/sources/reference/api/docker_remote_api_v1.9.md b/docs/sources/reference/api/docker_remote_api_v1.9.md index fc9f9b8d5..d67443fd2 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.9.md +++ b/docs/sources/reference/api/docker_remote_api_v1.9.md @@ -946,7 +946,7 @@ Tag the image `name` into a repository `GET /images/search` -Search for an image on [Docker Hub](https://hub.docker.com). +Search for an image on [Docker.io](https://index.docker.io). > **Note**: > The response keys have changed from API v1.6 to reflect the JSON diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 308e7d35d..16190ab1d 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -739,9 +739,9 @@ Running `docker ps` showing 2 linked containers. Pull an image or a repository from the registry Most of your images will be created on top of a base image from the -[Docker Hub](https://hub.docker.com) registry. +[Docker.io](https://index.docker.io) registry. -[Docker Hub](https://hub.docker.com) contains many pre-built images that you +[Docker.io](https://index.docker.io) contains many pre-built images that you can `pull` and try without needing to define and configure your own. To download a particular image, or set of images (i.e., a repository), @@ -760,7 +760,7 @@ use `docker pull`: Push an image or a repository to the registry -Use `docker push` to share your images to the [Docker Hub](https://hub.docker.com) +Use `docker push` to share your images to the [Docker.io](https://index.docker.io) registry or to a self-hosted one. ## restart @@ -1116,7 +1116,7 @@ It is used to create a backup that can then be used with ## search -Search [Docker Hub](https://hub.docker.com) for images +Search [Docker.io](https://index.docker.io) for images Usage: docker search TERM @@ -1126,9 +1126,9 @@ Search [Docker Hub](https://hub.docker.com) for images -s, --stars=0 Only displays with at least xxx stars --automated=false Only show automated builds -See [*Find Public Images on Docker Hub*]( -/userguide/dockerrepos/#find-public-images-on-docker-hub) for -more details on finding shared images from the command line. +See [*Find Public Images on Docker.io*]( +/userguide/dockerrepos/#find-public-images-on-dockerio) for +more details on finding shared images from the commandline. ## start diff --git a/docs/sources/terms/registry.md b/docs/sources/terms/registry.md index 8a7e6237e..08f8e8f69 100644 --- a/docs/sources/terms/registry.md +++ b/docs/sources/terms/registry.md @@ -11,7 +11,7 @@ A Registry is a hosted service containing [*images*](/terms/image/#image-def) which responds to the Registry API. The default registry can be accessed using a browser at -[Docker Hub](https://hub.docker.com) or using the +[Docker.io](http://index.docker.io) or using the `sudo docker search` command. ## Further Reading diff --git a/docs/sources/userguide/dockerimages.md b/docs/sources/userguide/dockerimages.md index 19881c258..bd8b0d2c2 100644 --- a/docs/sources/userguide/dockerimages.md +++ b/docs/sources/userguide/dockerimages.md @@ -1,6 +1,6 @@ page_title: Working with Docker Images page_description: How to work with Docker images. -page_keywords: documentation, docs, the docker guide, docker guide, docker, docker platform, virtualization framework, docker.io, Docker images, Docker image, image management, Docker repos, Docker repositories, docker, docker tag, docker tags, Docker Hub, collaboration +page_keywords: documentation, docs, the docker guide, docker guide, docker, docker platform, virtualization framework, docker.io, Docker images, Docker image, image management, Docker repos, Docker repositories, docker, docker tag, docker tags, Docker.io, collaboration # Working with Docker Images @@ -13,14 +13,14 @@ image and the `training/webapp` image. We've also discovered that Docker stores downloaded images on the Docker host. If an image isn't already present on the host then it'll be downloaded from a registry: by default the -[Docker Hub](https://hub.docker.com) public registry. +[Docker.io](https://index.docker.io) public registry. In this section we're going to explore Docker images a bit more including: * Managing and working with images locally on your Docker host; * Creating basic images; -* Uploading images to [Docker Hub](https://hub.docker.com). +* Uploading images to [Docker.io](https://index.docker.io). ## Listing images on the host @@ -45,7 +45,7 @@ do this using the `docker images` command like so: ubuntu lucid 3db9c44f4520 4 weeks ago 183 MB We can see the images we've previously used in our [user guide](/userguide/). -Each has been downloaded from [Docker Hub](https://hub.docker.com) when we +Each has been downloaded from [Docker.io](https://index.docker.io) when we launched a container using that image. We can see three crucial pieces of information about our images in the listing. @@ -104,8 +104,8 @@ download the image. One of the features of Docker is that a lot of people have created Docker images for a variety of purposes. Many of these have been uploaded to -[Docker Hub](https://hub.docker.com). We can search these images on the -[Docker Hub](https://hub.docker.com) website. +[Docker.io](https://index.docker.io). We can search these images on the +[Docker.io](https://index.docker.io) website. ![indexsearch](/userguide/search.png) @@ -359,12 +359,12 @@ Let's see our new tag using the `docker images` command. ouruser/sinatra devel 5db5f8471261 11 hours ago 446.7 MB ouruser/sinatra v2 5db5f8471261 11 hours ago 446.7 MB -## Push an image to Docker Hub +## Push an image to Docker.io -Once you've built or created a new image you can push it to [Docker -Hub](https://hub.docker.com) using the `docker push` command. This -allows you to share it with others, either publicly, or push it into [a -private repository](https://registry.hub.docker.com/plans/). +Once you've built or created a new image you can push it to [Docker.io]( +https://index.docker.io) using the `docker push` command. This allows you to +share it with others, either publicly, or push it into [a private +repository](https://index.docker.io/plans/). $ sudo docker push ouruser/sinatra The push refers to a repository [ouruser/sinatra] (len: 1) diff --git a/docs/sources/userguide/dockerhub.md b/docs/sources/userguide/dockerio.md similarity index 72% rename from docs/sources/userguide/dockerhub.md rename to docs/sources/userguide/dockerio.md index 7e7acbef1..e5c6c6dac 100644 --- a/docs/sources/userguide/dockerhub.md +++ b/docs/sources/userguide/dockerio.md @@ -1,15 +1,15 @@ -page_title: Getting started with Docker Hub -page_description: Introductory guide to getting an account on Docker Hub +page_title: Getting started with Docker.io +page_description: Introductory guide to getting an account on Docker.io page_keywords: documentation, docs, the docker guide, docker guide, docker, docker platform, virtualization framework, docker.io, central service, services, how to, container, containers, automation, collaboration, collaborators, registry, repo, repository, technology, github webhooks, trusted builds -# Getting Started with Docker Hub +# Getting Started with Docker.io -*How do I use Docker Hub?* +*How do I use Docker.io?* In this section we're going to introduce you, very quickly!, to -[Docker Hub](https://hub.docker.com) and create an account. +[Docker.io](https://index.docker.io) and create an account. -[Docker Hub](https://www.docker.io) is the central hub for Docker. It +[Docker.io](https://www.docker.io) is the central hub for Docker. It helps you to manage Docker and its components. It provides services such as: @@ -19,15 +19,15 @@ as: hooks. * Integration with GitHub and BitBucket. -Docker Hub helps you collaborate with colleagues and get the most out of +Docker.io helps you collaborate with colleagues and get the most out of Docker. -In order to use Docker Hub you will need to register an account. Don't +In order to use Docker.io you will need to register an account. Don't panic! It's totally free and really easy. -## Creating a Docker Hub Account +## Creating a Docker.io Account -There are two ways you can create a Docker Hub account: +There are two ways you can create a Docker.io account: * Via the web, or * Via the command line. @@ -41,7 +41,7 @@ choose your user name and specify some details such as an email address. ### Signup via the command line -You can also create a Docker Hub account via the command line using the +You can also create a Docker.io account via the command line using the `docker login` command. $ sudo docker login @@ -63,7 +63,7 @@ Or via the command line and the `docker login` command: $ sudo docker login -Now your Docker Hub account is active and ready for you to use! +Now your Docker.io account is active and ready for you to use! ## Next steps diff --git a/docs/sources/userguide/dockerizing.md b/docs/sources/userguide/dockerizing.md index be3ccd444..79a2066c6 100644 --- a/docs/sources/userguide/dockerizing.md +++ b/docs/sources/userguide/dockerizing.md @@ -30,7 +30,7 @@ operating system image. When you specify an image, Docker looks first for the image on your Docker host. If it can't find it then it downloads the image from the public -image registry: [Docker Hub](https://hub.docker.com). +image registry: [Docker.io](https://index.docker.io). Next we told Docker what command to run inside our new container: diff --git a/docs/sources/userguide/dockerrepos.md b/docs/sources/userguide/dockerrepos.md index 5ebad9698..d18ec13cc 100644 --- a/docs/sources/userguide/dockerrepos.md +++ b/docs/sources/userguide/dockerrepos.md @@ -1,8 +1,8 @@ -page_title: Working with Docker Hub -page_description: Learning how to use Docker Hub to manage images and work flow -page_keywords: repo, Docker Hub, Docker Hub, registry, index, repositories, usage, pull image, push image, image, documentation +page_title: Working with Docker.io +page_description: Learning how to use Docker.io to manage images and work flow +page_keywords: repo, Docker.io, Docker Hub, registry, index, repositories, usage, pull image, push image, image, documentation -# Working with Docker Hub +# Working with Docker.io So far we've seen a lot about how to use Docker on the command line and your local host. We've seen [how to pull down @@ -10,10 +10,10 @@ images](/userguide/usingdocker/) that you can run your containers from and we've seen how to [create your own images](/userguide/dockerimages). Now we're going to learn a bit more about -[Docker Hub](https://hub.docker.com) and how you can use it to enhance +[Docker.io](https://index.docker.io) and how you can use it to enhance your Docker work flows. -[Docker Hub](https://hub.docker.com) is the public registry that Docker +[Docker.io](https://index.docker.io) is the public registry that Docker Inc maintains. It contains a huge collection of images, over 15,000, that you can download and use to build your containers. It also provides authentication, structure (you can setup teams and organizations), work @@ -21,7 +21,7 @@ flow tools like webhooks and build triggers as well as privacy features like private repositories for storing images you don't want to publicly share. -## Docker commands and Docker Hub +## Docker commands and Docker.io Docker acts as a client for these services via the `docker search`, `pull`, `login` and `push` commands. @@ -29,7 +29,7 @@ Docker acts as a client for these services via the `docker search`, ## Searching for images As we've already seen we can search the -[Docker Hub](https://hub.docker.com) registry via it's search interface +[Docker.io](https://index.docker.io) registry via it's search interface or using the command line interface. Searching can find images by name, user name or description: @@ -57,15 +57,15 @@ Once you have found the image you want, you can download it: The image is now available to run a container from. -## Contributing to Docker Hub +## Contributing to Docker.io -Anyone can pull public images from the [Docker Hub](https://hub.docker.com) +Anyone can pull public images from the [Docker.io](http://index.docker.io) registry, but if you would like to share your own images, then you must register a user first as we saw in the [first section of the Docker User -Guide](/userguide/dockerhub/). +Guide](/userguide/dockerio/). To refresh your memory, you can create your user name and login to -[Docker Hub](https://hub.docker.com/account/signup/), or by running: +[Docker.io](https://index.docker.io/account/signup/), or by running: $ sudo docker login @@ -85,7 +85,7 @@ you in. Now you're ready to commit and push your own images! > Your authentication credentials will be stored in the [`.dockercfg` > authentication file](#authentication-file) in your home directory. -## Pushing a repository to Docker Hub +## Pushing a repository to Docker.io In order to push an repository to its registry you need to have named an image, or committed your container to a named image as we saw @@ -98,9 +98,9 @@ or tag. The image will then be uploaded and available for use. -## Features of Docker Hub +## Features of Docker.io -Now let's look at some of the features of Docker Hub. You can find more +Now let's look at some of the features of Docker.io. You can find more information [here](/docker-io/). * Private repositories @@ -111,29 +111,29 @@ information [here](/docker-io/). ## Private Repositories Sometimes you have images you don't want to make public and share with -everyone. So Docker Hub allows you to have private repositories. You can -sign up for a plan [here](https://registry.hub.docker.com/plans/). +everyone. So Docker.io allows you to have private repositories. You can +sign up for a plan [here](https://index.docker.io/plans/). ## Organizations and teams One of the useful aspects of private repositories is that you can share -them only with members of your organization or team. Docker Hub lets you +them only with members of your organization or team. Docker.io lets you create organizations where you can collaborate with your colleagues and manage private repositories. You can create and manage an organization -[here](https://registry.hub.docker.com/account/organizations/). +[here](https://index.docker.io/account/organizations/). ## Automated Builds Automated Builds automate the building and updating of images from [GitHub](https://www.github.com) -or [BitBucket](http://bitbucket.com), directly on Docker Hub. It works by adding a commit hook to +or [BitBucket](http://bitbucket.com), directly on Docker.io. It works by adding a commit hook to your selected GitHub or BitBucket repository, triggering a build and update when you push a commit. ### To setup an Automated Build -1. Create a [Docker Hub account](https://hub.docker.com/) and login. -2. Link your GitHub or BitBucket account through the [`Link Accounts`](https://registry.hub.docker.com/account/accounts/) menu. -3. [Configure an Automated Build](https://registry.hub.docker.com/builds/). +1. Create a [Docker.io account](https://index.docker.io/) and login. +2. Link your GitHub or BitBucket account through the [`Link Accounts`](https://index.docker.io/account/accounts/) menu. +3. [Configure an Automated Build](https://index.docker.io/builds/). 4. Pick a GitHub or BitBucket project that has a `Dockerfile` that you want to build. 5. Pick the branch you want to build (the default is the `master` branch). 6. Give the Automated Build a name. @@ -142,12 +142,12 @@ commit. Once the Automated Build is configured it will automatically trigger a build, and in a few minutes, if there are no errors, you will see your -new Automated Build on the [Docker Hub](https://hub.docker.com) Registry. +new Automated Build on the [Docker.io](https://index.docker.io) Registry. It will stay in sync with your GitHub and BitBucket repository until you deactivate the Automated Build. If you want to see the status of your Automated Builds you can go to your -[Automated Builds page](https://registry.hub.docker.io/builds/) on the Docker Hub, +[Automated Builds page](https://index.docker.io/builds/) on the Docker.io, and it will show you the status of your builds, and the build history. Once you've created an Automated Build you can deactivate or delete it. You @@ -160,7 +160,7 @@ to point to specific `Dockerfile`'s or Git branches. ### Build Triggers -Automated Builds can also be triggered via a URL on Docker Hub. This +Automated Builds can also be triggered via a URL on Docker.io. This allows you to rebuild an Automated build image on demand. ## Webhooks diff --git a/docs/sources/userguide/dockervolumes.md b/docs/sources/userguide/dockervolumes.md index f3a461235..34cfe05b4 100644 --- a/docs/sources/userguide/dockervolumes.md +++ b/docs/sources/userguide/dockervolumes.md @@ -135,8 +135,8 @@ restore testing using your preferred tools. Now we've learned a bit more about how to use Docker we're going to see how to combine Docker with the services available on -[Docker Hub](https://hub.docker.com) including Automated Builds and private +[Docker.io](https://index.docker.io) including Automated Builds and private repositories. -Go to [Working with Docker Hub](/userguide/dockerrepos). +Go to [Working with Docker.io](/userguide/dockerrepos). diff --git a/docs/sources/userguide/index.md b/docs/sources/userguide/index.md index 54400cef7..715054043 100644 --- a/docs/sources/userguide/index.md +++ b/docs/sources/userguide/index.md @@ -19,15 +19,15 @@ We’ll teach you how to use Docker to: We've broken this guide into major sections that take you through the Docker life cycle: -## Getting Started with Docker Hub +## Getting Started with Docker.io -*How do I use Docker Hub?* +*How do I use Docker.io?* -Docker Hub is the central hub for Docker. It hosts public Docker images +Docker.io is the central hub for Docker. It hosts public Docker images and provides services to help you build and manage your Docker environment. To learn more; -Go to [Using Docker Hub](/userguide/dockerhub). +Go to [Using Docker.io](/userguide/dockerio). ## Dockerizing Applications: A "Hello World!" @@ -72,21 +72,21 @@ learning how to manage data, volumes and mounts inside our containers. Go to [Managing Data in Containers](/userguide/dockervolumes). -## Working with Docker Hub +## Working with Docker.io Now we've learned a bit more about how to use Docker we're going to see -how to combine Docker with the services available on Docker Hub including -Trusted Builds and private repositories. +how to combine Docker with the services available on Docker.io including +Automated Builds and private repositories. -Go to [Working with Docker Hub](/userguide/dockerrepos). +Go to [Working with Docker.io](/userguide/dockerrepos). ## Getting help -* [Docker homepage](http://www.docker.com/) -* [Docker Hub](https://hub.docker.com/) -* [Docker blog](http://blog.docker.com/) -* [Docker documentation](http://docs.docker.com/) -* [Docker Getting Started Guide](http://www.docker.com/gettingstarted/) +* [Docker homepage](http://www.docker.io/) +* [Docker.io](http://index.docker.io) +* [Docker blog](http://blog.docker.io/) +* [Docker documentation](http://docs.docker.io/) +* [Docker Getting Started Guide](http://www.docker.io/gettingstarted/) * [Docker code on GitHub](https://github.com/dotcloud/docker) * [Docker mailing list](https://groups.google.com/forum/#!forum/docker-user) diff --git a/docs/sources/userguide/usingdocker.md b/docs/sources/userguide/usingdocker.md index 89401b19b..2eaf707d1 100644 --- a/docs/sources/userguide/usingdocker.md +++ b/docs/sources/userguide/usingdocker.md @@ -309,7 +309,7 @@ And now our container is stopped and deleted. # Next steps Until now we've only used images that we've downloaded from -[Docker Hub](https://hub.docker.com) now let's get introduced to +[Docker.io](https://index.docker.io) now let's get introduced to building and sharing our own images. Go to [Working with Docker Images](/userguide/dockerimages). From 09b19752e028f91644e3e2801f0fd616c4adc683 Mon Sep 17 00:00:00 2001 From: Fred Lifton Date: Wed, 4 Jun 2014 13:17:21 -0700 Subject: [PATCH 392/400] Initial copy edits to home page. Docker-DCO-1.1-Signed-off-by: Fred Lifton (github: fredlf) Docker-DCO-1.1-Signed-off-by: Fred Lifton (github: SvenDowideit) --- docs/sources/index.md | 77 ++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 41 deletions(-) diff --git a/docs/sources/index.md b/docs/sources/index.md index f7295d9c1..93021c2d8 100644 --- a/docs/sources/index.md +++ b/docs/sources/index.md @@ -7,78 +7,73 @@ page_keywords: docker, introduction, documentation, about, technology, understan **Develop, Ship and Run Any Application, Anywhere** [**Docker**](https://www.docker.io) is a platform for developers and -sysadmins to develop, ship, and run applications. Docker consists of: +sysadmins to develop, ship, and run applications. Docker lets you quickly assemble applications from components and eliminates the friction that can come when shipping code. Docker lets you get your code tested and deployed into production as fast as possible. + +Docker consists of: * The Docker Engine - our lightweight and powerful open source container - virtualization technology combined with a work flow to help you build - and containerize your applications. -* [Docker.io](https://index.docker.io) - our SAAS service that helps you - share and manage your applications stacks. - -Docker enables applications to be quickly assembled from components and -eliminates the friction when shipping code. We want to help you get code -from your desktop, tested and deployed into production as fast as -possible. + virtualization technology combined with a work flow for building + and containerizing your applications. +* [Docker.io](https://index.docker.io) - our SAAS service for + sharing and managing your application stacks. ## Why Docker? - **Faster delivery of your applications** - * We want to help your environment work better. Docker containers, - and the work flow that comes with them, helps your developers, - sysadmins, QA folks, and release engineers work together to get code - into production and doing something useful. We've created a standard - container format that allows developers to care about their applications - inside containers and sysadmins and operators to care about running the - container. This creates a separation of duties that makes managing and - deploying code much easier and much more streamlined. + * We want your environment to work better. Docker containers, + and the work flow that comes with them, help your developers, + sysadmins, QA folks, and release engineers work together to get your code + into production and make it useful. We've created a standard + container format that lets developers care about their applications + inside containers while sysadmins and operators can work on running the + container in your deployment. This separation of duties streamlines and simplifies the management and + deployment of code. * We make it easy to build new containers, enable rapid iteration of - your applications and increase the visibility of changes. This + your applications, and increase the visibility of changes. This helps everyone in your organization understand how an application works and how it is built. * Docker containers are lightweight and fast! Containers have - sub-second launch times! With containers you can reduce the cycle - time in development, testing and deployment. + sub-second launch times, reducing the cycle + time of development, testing, and deployment. - **Deploy and scale more easily** * Docker containers run (almost!) everywhere. You can deploy your containers on desktops, physical servers, virtual machines, into - data centers and to public and private clouds. - * As Docker runs on so many platforms it makes it easy to move your + data centers, and up to public and private clouds. + * Since Docker runs on so many platforms, it's easy to move your appications around. You can easily move an application from a testing environment into the cloud and back whenever you need. - * The lightweight containers Docker creates also making scaling and - down really fast and easy. If you need more containers you can - quickly launch them and then shut them down when you don't need them - anymore. + * Docker's lightweight containers Docker also make scaling up and + down fast and easy. You can quickly launch more containers when needed and then shut them down easily when they're no longer needed. - **Get higher density and run more workloads** - * Docker containers don't need a hypervisor so you can pack more of + * Docker containers don't need a hypervisor, so you can pack more of them onto your hosts. This means you get more value out of every - server and can potentially reduce the money you spend on equipment and - licenses! + server and can potentially reduce what you spend on equipment and + licenses. - **Faster deployment makes for easier management** - * As Docker speeds up your work flow it makes it easier to make lots - of little changes instead of huge, big bang updates. Smaller - changes mean smaller risks and mean more uptime! + * As Docker speeds up your work flow, it gets easier to make lots + of small changes instead of huge, big bang updates. Smaller + changes mean reduced risk and more uptime. ## About this guide -First we'll show you [what makes Docker tick in our Understanding Docker -section](introduction/understanding-docker.md): +First, the [Understanding Docker +section](introduction/understanding-docker.md) will help you: - - You will find see how Docker works at a high level; - - The architecture of Docker; + - See how Docker works at a high level + - Understand the architecture of Docker - Discover Docker's features; - - See how Docker compares to virtual machines; - - And see some common use cases. + - See how Docker compares to virtual machines + - See some common use cases. > [Click here to go to the Understanding > Docker section](introduction/understanding-docker.md). ### Installation Guides -Then we'll learn how to install Docker on a variety of platforms in our +Next, we'll show you how to install Docker on a variety of platforms in the [installation](/installation/#installation) section. > [Click here to go to the Installation @@ -86,7 +81,7 @@ Then we'll learn how to install Docker on a variety of platforms in our ### Docker User Guide -Once you've gotten Docker installed we recommend you step through our [Docker User Guide](/userguide/), which will give you an in depth introduction to Docker. +Once you've gotten Docker installed we recommend you work through the [Docker User Guide](/userguide/), to learn about Docker in more detail and answer questions about usage and implementation. > [Click here to go to the Docker User Guide](/userguide/). From abcb724ed90a1e68234bd9c1c0d9263557b67026 Mon Sep 17 00:00:00 2001 From: SvenDowideit Date: Fri, 6 Jun 2014 07:50:04 +1000 Subject: [PATCH 393/400] reflow to 80 columns Docker-DCO-1.1-Signed-off-by: SvenDowideit (github: SvenDowideit) --- docs/sources/index.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/sources/index.md b/docs/sources/index.md index 93021c2d8..5a734895d 100644 --- a/docs/sources/index.md +++ b/docs/sources/index.md @@ -6,8 +6,11 @@ page_keywords: docker, introduction, documentation, about, technology, understan **Develop, Ship and Run Any Application, Anywhere** -[**Docker**](https://www.docker.io) is a platform for developers and -sysadmins to develop, ship, and run applications. Docker lets you quickly assemble applications from components and eliminates the friction that can come when shipping code. Docker lets you get your code tested and deployed into production as fast as possible. +[**Docker**](https://www.docker.io) is a platform for developers and sysadmins +to develop, ship, and run applications. Docker lets you quickly assemble +applications from components and eliminates the friction that can come when +shipping code. Docker lets you get your code tested and deployed into production +as fast as possible. Docker consists of: @@ -26,8 +29,8 @@ Docker consists of: into production and make it useful. We've created a standard container format that lets developers care about their applications inside containers while sysadmins and operators can work on running the - container in your deployment. This separation of duties streamlines and simplifies the management and - deployment of code. + container in your deployment. This separation of duties streamlines and + simplifies the management and deployment of code. * We make it easy to build new containers, enable rapid iteration of your applications, and increase the visibility of changes. This helps everyone in your organization understand how an application works @@ -44,7 +47,8 @@ Docker consists of: appications around. You can easily move an application from a testing environment into the cloud and back whenever you need. * Docker's lightweight containers Docker also make scaling up and - down fast and easy. You can quickly launch more containers when needed and then shut them down easily when they're no longer needed. + down fast and easy. You can quickly launch more containers when + needed and then shut them down easily when they're no longer needed. - **Get higher density and run more workloads** * Docker containers don't need a hypervisor, so you can pack more of @@ -81,7 +85,9 @@ Next, we'll show you how to install Docker on a variety of platforms in the ### Docker User Guide -Once you've gotten Docker installed we recommend you work through the [Docker User Guide](/userguide/), to learn about Docker in more detail and answer questions about usage and implementation. +Once you've gotten Docker installed we recommend you work through the +[Docker User Guide](/userguide/), to learn about Docker in more detail and +answer questions about usage and implementation. > [Click here to go to the Docker User Guide](/userguide/). From b1ac791d8426cd7b6fb5a19f5e918b26c83d83f6 Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Thu, 5 Jun 2014 15:13:43 -0700 Subject: [PATCH 394/400] Ensures files get closed properly. Closes #6213 Docker-DCO-1.1-Signed-off-by: Tibor Vass (github: tiborvass) --- daemon/graphdriver/devmapper/README.md | 2 +- daemon/graphdriver/devmapper/deviceset.go | 23 ++--------------------- 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/daemon/graphdriver/devmapper/README.md b/daemon/graphdriver/devmapper/README.md index ccff3f021..c8ab1d1ee 100644 --- a/daemon/graphdriver/devmapper/README.md +++ b/daemon/graphdriver/devmapper/README.md @@ -4,7 +4,7 @@ The device mapper graphdriver uses the device mapper thin provisioning module (dm-thinp) to implement CoW snapshots. For each devicemapper -graph locaion (typically `/var/lib/docker/devicemapper`, $graph below) +graph location (typically `/var/lib/docker/devicemapper`, $graph below) a thin pool is created based on two block devices, one for data and one for metadata. By default these block devices are created automatically by using loopback mounts of automatically creates sparse diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index a930bf2b9..48323f661 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -307,7 +307,6 @@ func (devices *DeviceSet) createFilesystem(info *DevInfo) error { err = fmt.Errorf("Unsupported filesystem type %s", devices.filesystem) } if err != nil { - utils.Debugf("\n--->Err: %s\n", err) return err } @@ -317,12 +316,10 @@ func (devices *DeviceSet) createFilesystem(info *DevInfo) error { func (devices *DeviceSet) initMetaData() error { _, _, _, params, err := getStatus(devices.getPoolName()) if err != nil { - utils.Debugf("\n--->Err: %s\n", err) return err } if _, err := fmt.Sscanf(params, "%d", &devices.TransactionId); err != nil { - utils.Debugf("\n--->Err: %s\n", err) return err } devices.NewTransactionId = devices.TransactionId @@ -331,7 +328,6 @@ func (devices *DeviceSet) initMetaData() error { jsonData, err := ioutil.ReadFile(devices.oldMetadataFile()) if err != nil && !os.IsNotExist(err) { - utils.Debugf("\n--->Err: %s\n", err) return err } @@ -339,7 +335,6 @@ func (devices *DeviceSet) initMetaData() error { m := MetaData{Devices: make(map[string]*DevInfo)} if err := json.Unmarshal(jsonData, &m); err != nil { - utils.Debugf("\n--->Err: %s\n", err) return err } @@ -389,7 +384,6 @@ func (devices *DeviceSet) setupBaseImage() error { if oldInfo != nil && !oldInfo.Initialized { utils.Debugf("Removing uninitialized base image") if err := devices.deleteDevice(oldInfo); err != nil { - utils.Debugf("\n--->Err: %s\n", err) return err } } @@ -400,7 +394,6 @@ func (devices *DeviceSet) setupBaseImage() error { // Create initial device if err := createDevice(devices.getPoolDevName(), &id); err != nil { - utils.Debugf("\n--->Err: %s\n", err) return err } @@ -411,26 +404,22 @@ func (devices *DeviceSet) setupBaseImage() error { info, err := devices.registerDevice(id, "", devices.baseFsSize) if err != nil { _ = deleteDevice(devices.getPoolDevName(), id) - utils.Debugf("\n--->Err: %s\n", err) return err } utils.Debugf("Creating filesystem on base device-manager snapshot") if err = devices.activateDeviceIfNeeded(info); err != nil { - utils.Debugf("\n--->Err: %s\n", err) return err } if err := devices.createFilesystem(info); err != nil { - utils.Debugf("\n--->Err: %s\n", err) return err } info.Initialized = true if err = devices.saveMetadata(info); err != nil { info.Initialized = false - utils.Debugf("\n--->Err: %s\n", err) return err } @@ -610,16 +599,15 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { dataFile, err = attachLoopDevice(data) if err != nil { - utils.Debugf("\n--->Err: %s\n", err) return err } - defer dataFile.Close() } else { dataFile, err = os.OpenFile(devices.dataDevice, os.O_RDWR, 0600) if err != nil { return err } } + defer dataFile.Close() if devices.metadataDevice == "" { // Make sure the sparse images exist in /devicemapper/metadata @@ -642,19 +630,17 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { metadataFile, err = attachLoopDevice(metadata) if err != nil { - utils.Debugf("\n--->Err: %s\n", err) return err } - defer metadataFile.Close() } else { metadataFile, err = os.OpenFile(devices.metadataDevice, os.O_RDWR, 0600) if err != nil { return err } } + defer metadataFile.Close() if err := createPool(devices.getPoolName(), dataFile, metadataFile); err != nil { - utils.Debugf("\n--->Err: %s\n", err) return err } } @@ -663,7 +649,6 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { // load the transaction id and migrate old metadata if !createdLoopback { if err = devices.initMetaData(); err != nil { - utils.Debugf("\n--->Err: %s\n", err) return err } } @@ -775,7 +760,6 @@ func (devices *DeviceSet) deactivatePool() error { devname := devices.getPoolDevName() devinfo, err := getInfo(devname) if err != nil { - utils.Debugf("\n--->Err: %s\n", err) return err } if devinfo.Exists != 0 { @@ -797,12 +781,10 @@ func (devices *DeviceSet) deactivateDevice(info *DevInfo) error { devinfo, err := getInfo(info.Name()) if err != nil { - utils.Debugf("\n--->Err: %s\n", err) return err } if devinfo.Exists != 0 { if err := devices.removeDeviceAndWait(info.Name()); err != nil { - utils.Debugf("\n--->Err: %s\n", err) return err } } @@ -1032,7 +1014,6 @@ func (devices *DeviceSet) UnmountDevice(hash string) error { utils.Debugf("[devmapper] Unmount(%s)", info.mountPath) if err := syscall.Unmount(info.mountPath, 0); err != nil { - utils.Debugf("\n--->Err: %s\n", err) return err } utils.Debugf("[devmapper] Unmount done") From 3609b051b88565c0fe0615fd47ddb48eed549d27 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 5 Jun 2014 12:50:53 -0700 Subject: [PATCH 395/400] Move remount as private to the graph drivers If this is at the root directory for the daemon you could unmount somones filesystem when you stop docker and this is actually only needed for the palces that the graph drivers mount the container's root filesystems. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/daemon.go | 24 ------------------------ daemon/graphdriver/aufs/aufs.go | 8 +++++++- daemon/graphdriver/btrfs/btrfs.go | 14 ++++++++++++-- daemon/graphdriver/devmapper/driver.go | 15 ++++++++++++++- daemon/graphdriver/driver.go | 19 ++++++++++++++++++- 5 files changed, 51 insertions(+), 29 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index 2c2b04694..445d5c5b6 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -27,7 +27,6 @@ import ( "github.com/dotcloud/docker/image" "github.com/dotcloud/docker/pkg/graphdb" "github.com/dotcloud/docker/pkg/label" - "github.com/dotcloud/docker/pkg/mount" "github.com/dotcloud/docker/pkg/namesgenerator" "github.com/dotcloud/docker/pkg/networkfs/resolvconf" "github.com/dotcloud/docker/pkg/selinux" @@ -102,21 +101,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { return eng.Register("container_inspect", daemon.ContainerInspect) } -// Mountpoints should be private to the container -func remountPrivate(mountPoint string) error { - mounted, err := mount.Mounted(mountPoint) - if err != nil { - return err - } - - if !mounted { - if err := mount.Mount(mountPoint, mountPoint, "none", "bind,rw"); err != nil { - return err - } - } - return mount.ForceMount("", mountPoint, "none", "private") -} - // List returns an array of all containers registered in the daemon. func (daemon *Daemon) List() []*Container { return daemon.containers.List() @@ -786,10 +770,6 @@ func NewDaemonFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (*D } utils.Debugf("Using graph driver %s", driver) - if err := remountPrivate(config.Root); err != nil { - return nil, err - } - daemonRepo := path.Join(config.Root, "containers") if err := os.MkdirAll(daemonRepo, 0700); err != nil && !os.IsExist(err) { @@ -938,10 +918,6 @@ func (daemon *Daemon) Close() error { utils.Errorf("daemon.containerGraph.Close(): %s", err.Error()) errorsStrings = append(errorsStrings, err.Error()) } - if err := mount.Unmount(daemon.config.Root); err != nil { - utils.Errorf("daemon.Umount(%s): %s", daemon.config.Root, err.Error()) - errorsStrings = append(errorsStrings, err.Error()) - } if len(errorsStrings) > 0 { return fmt.Errorf("%s", strings.Join(errorsStrings, ", ")) } diff --git a/daemon/graphdriver/aufs/aufs.go b/daemon/graphdriver/aufs/aufs.go index 43c312827..97e9b9748 100644 --- a/daemon/graphdriver/aufs/aufs.go +++ b/daemon/graphdriver/aufs/aufs.go @@ -97,6 +97,10 @@ func Init(root string, options []string) (graphdriver.Driver, error) { return nil, err } + if err := graphdriver.MakePrivate(root); err != nil { + return nil, err + } + for _, p := range paths { if err := os.MkdirAll(path.Join(root, p), 0755); err != nil { return nil, err @@ -371,12 +375,14 @@ func (a *Driver) Cleanup() error { if err != nil { return err } + for _, id := range ids { if err := a.unmount(id); err != nil { utils.Errorf("Unmounting %s: %s", utils.TruncateID(id), err) } } - return nil + + return mountpk.Unmount(a.root) } func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err error) { diff --git a/daemon/graphdriver/btrfs/btrfs.go b/daemon/graphdriver/btrfs/btrfs.go index ba3ecba76..f561244c5 100644 --- a/daemon/graphdriver/btrfs/btrfs.go +++ b/daemon/graphdriver/btrfs/btrfs.go @@ -11,11 +11,13 @@ import "C" import ( "fmt" - "github.com/dotcloud/docker/daemon/graphdriver" "os" "path" "syscall" "unsafe" + + "github.com/dotcloud/docker/daemon/graphdriver" + "github.com/dotcloud/docker/pkg/mount" ) func init() { @@ -34,6 +36,14 @@ func Init(home string, options []string) (graphdriver.Driver, error) { return nil, graphdriver.ErrPrerequisites } + if err := os.MkdirAll(home, 0700); err != nil { + return nil, err + } + + if err := graphdriver.MakePrivate(home); err != nil { + return nil, err + } + return &Driver{ home: home, }, nil @@ -52,7 +62,7 @@ func (d *Driver) Status() [][2]string { } func (d *Driver) Cleanup() error { - return nil + return mount.Unmount(d.home) } func free(p *C.char) { diff --git a/daemon/graphdriver/devmapper/driver.go b/daemon/graphdriver/devmapper/driver.go index 5bfd8ee65..cf82ad62e 100644 --- a/daemon/graphdriver/devmapper/driver.go +++ b/daemon/graphdriver/devmapper/driver.go @@ -9,6 +9,7 @@ import ( "path" "github.com/dotcloud/docker/daemon/graphdriver" + "github.com/dotcloud/docker/pkg/mount" "github.com/dotcloud/docker/utils" ) @@ -31,10 +32,16 @@ func Init(home string, options []string) (graphdriver.Driver, error) { if err != nil { return nil, err } + + if err := graphdriver.MakePrivate(home); err != nil { + return nil, err + } + d := &Driver{ DeviceSet: deviceSet, home: home, } + return d, nil } @@ -58,7 +65,13 @@ func (d *Driver) Status() [][2]string { } func (d *Driver) Cleanup() error { - return d.DeviceSet.Shutdown() + err := d.DeviceSet.Shutdown() + + if err2 := mount.Unmount(d.home); err == nil { + err = err2 + } + + return err } func (d *Driver) Create(id, parent string) error { diff --git a/daemon/graphdriver/driver.go b/daemon/graphdriver/driver.go index 93d4ed253..453648970 100644 --- a/daemon/graphdriver/driver.go +++ b/daemon/graphdriver/driver.go @@ -3,9 +3,11 @@ package graphdriver import ( "errors" "fmt" - "github.com/dotcloud/docker/archive" "os" "path" + + "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/pkg/mount" ) type FsMagic uint64 @@ -107,3 +109,18 @@ func New(root string, options []string) (driver Driver, err error) { } return nil, fmt.Errorf("No supported storage backend found") } + +func MakePrivate(mountPoint string) error { + mounted, err := mount.Mounted(mountPoint) + if err != nil { + return err + } + + if !mounted { + if err := mount.Mount(mountPoint, mountPoint, "none", "bind,rw"); err != nil { + return err + } + } + + return mount.ForceMount("", mountPoint, "none", "private") +} From 762c52850177d0eb9148e5f3a0df90c79043246b Mon Sep 17 00:00:00 2001 From: Fred Lifton Date: Thu, 5 Jun 2014 16:55:32 -0700 Subject: [PATCH 396/400] More minor copy edits, typo fix. Docker-DCO-1.1-Signed-off-by: Fred Lifton (github: fredfl) --- docs/sources/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/index.md b/docs/sources/index.md index 5a734895d..dc18fcdad 100644 --- a/docs/sources/index.md +++ b/docs/sources/index.md @@ -40,11 +40,11 @@ Docker consists of: time of development, testing, and deployment. - **Deploy and scale more easily** - * Docker containers run (almost!) everywhere. You can deploy your + * Docker containers run (almost) everywhere. You can deploy containers on desktops, physical servers, virtual machines, into data centers, and up to public and private clouds. * Since Docker runs on so many platforms, it's easy to move your - appications around. You can easily move an application from a + applications around. You can easily move an application from a testing environment into the cloud and back whenever you need. * Docker's lightweight containers Docker also make scaling up and down fast and easy. You can quickly launch more containers when From 3986bc659be9ff83d521f82e3353b1f73b896cbc Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 5 Jun 2014 17:03:32 -0700 Subject: [PATCH 397/400] Don't compile nsinit Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- hack/make.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/hack/make.sh b/hack/make.sh index e7dc09cfb..cc5582a8d 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -170,6 +170,7 @@ find_dirs() { -o -wholename './.git' \ -o -wholename './bundles' \ -o -wholename './docs' \ + -o -wholename './pkg/libcontainer/nsinit' \ \) \ -prune \ \) -name "$1" -print0 | xargs -0n1 dirname | sort -u From 34bd2d622910444ae0d30bd0ac32005e224074c1 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 6 Jun 2014 00:31:58 +0000 Subject: [PATCH 398/400] start containers after all of them are registered Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- daemon/daemon.go | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index 445d5c5b6..b990b0df6 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -147,11 +147,11 @@ func (daemon *Daemon) load(id string) (*Container, error) { // Register makes a container object usable by the daemon as // This is a wrapper for register func (daemon *Daemon) Register(container *Container) error { - return daemon.register(container, true) + return daemon.register(container, true, nil) } // register makes a container object usable by the daemon as -func (daemon *Daemon) register(container *Container, updateSuffixarray bool) error { +func (daemon *Daemon) register(container *Container, updateSuffixarray bool, containersToStart *[]*Container) error { if container.daemon != nil || daemon.Exists(container.ID) { return fmt.Errorf("Container is already loaded") } @@ -220,13 +220,13 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err if !info.IsRunning() { utils.Debugf("Container %s was supposed to be running but is not.", container.ID) if daemon.config.AutoRestart { - utils.Debugf("Restarting") + utils.Debugf("Marking as restarting") if err := container.Unmount(); err != nil { utils.Debugf("restart unmount error %s", err) } - if err := container.Start(); err != nil { - return err + if containersToStart != nil { + *containersToStart = append(*containersToStart, container) } } else { utils.Debugf("Marking as stopped") @@ -311,7 +311,12 @@ func (daemon *Daemon) Destroy(container *Container) error { } func (daemon *Daemon) restore() error { - debug := (os.Getenv("DEBUG") != "" || os.Getenv("TEST") != "") + var ( + debug = (os.Getenv("DEBUG") != "" || os.Getenv("TEST") != "") + containers = make(map[string]*Container) + currentDriver = daemon.driver.String() + containersToStart = []*Container{} + ) if !debug { fmt.Printf("Loading containers: ") @@ -320,8 +325,6 @@ func (daemon *Daemon) restore() error { if err != nil { return err } - containers := make(map[string]*Container) - currentDriver := daemon.driver.String() for _, v := range dir { id := v.Name() @@ -350,7 +353,7 @@ func (daemon *Daemon) restore() error { } e := entities[p] if container, ok := containers[e.ID()]; ok { - if err := daemon.register(container, false); err != nil { + if err := daemon.register(container, false, &containersToStart); err != nil { utils.Debugf("Failed to register container %s: %s", container.ID, err) } delete(containers, e.ID()) @@ -365,12 +368,20 @@ func (daemon *Daemon) restore() error { if err != nil { utils.Debugf("Setting default id - %s", err) } - if err := daemon.register(container, false); err != nil { + if err := daemon.register(container, false, &containersToStart); err != nil { utils.Debugf("Failed to register container %s: %s", container.ID, err) } } daemon.idIndex.UpdateSuffixarray() + + for _, container := range containersToStart { + utils.Debugf("Starting container %d", container.ID) + if err := container.Start(); err != nil { + utils.Debugf("Failed to start container %s: %s", container.ID, err) + } + } + if !debug { fmt.Printf(": done.\n") } From 30ba7546cb5a1ff7e4915c5a25dd8d72b3bf735b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 6 Jun 2014 00:32:14 +0000 Subject: [PATCH 399/400] add wait4 after kill Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- daemon/execdriver/native/driver.go | 1 + 1 file changed, 1 insertion(+) diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index 41121a70f..d3805b493 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -187,6 +187,7 @@ func (d *driver) Terminate(p *execdriver.Command) error { } if started == currentStartTime { err = syscall.Kill(p.Process.Pid, 9) + syscall.Wait4(p.Process.Pid, nil, 0, nil) } d.removeContainerRoot(p.ID) return err From 14680bf724161785d164fd99de36e3eb03a823ef Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Thu, 5 Jun 2014 17:59:19 -0700 Subject: [PATCH 400/400] Bump version to v0.12.0 Docker-DCO-1.1-Signed-off-by: Tibor Vass (github: tiborvass) --- CHANGELOG.md | 12 ++++++++++++ VERSION | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14329ab96..07f11d90f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 0.12.0 (2014-06-05) + +#### Notable features since 0.11.0 +* New `COPY` Dockerfile instruction to allow copying a local file from the context into the container without ever extracting if the file is a tar file +* Inherit file permissions from the host on `ADD` +* New `pause` and `unpause` commands to allow pausing and unpausing of containers using cgroup freezer +* The `images` command has a `-f`/`--filter` option to filter the list of images +* Add `--force-rm` to clean up after a failed build +* Standardize JSON keys in Remote API to CamelCase +* Pull from a docker run now assumes `latest` tag if not specified +* Enhance security on Linux capabilities and device nodes + ## 0.11.1 (2014-05-07) #### Registry diff --git a/VERSION b/VERSION index 72ab694d3..ac454c6a1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.11.1-dev +0.12.0