From d6ddff56bf95af0b324f3bbfb10537b46a5eee5c 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) Upstream-commit: 8116b86e050b23a6b897d90a0d2f8d409e7ec04c Component: engine --- components/engine/daemon/graphdriver/devmapper/deviceset.go | 2 +- components/engine/daemon/graphdriver/devmapper/devmapper.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/daemon/graphdriver/devmapper/deviceset.go b/components/engine/daemon/graphdriver/devmapper/deviceset.go index 640bebd32b..7940c4a5c4 100644 --- a/components/engine/daemon/graphdriver/devmapper/deviceset.go +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/devmapper.go b/components/engine/daemon/graphdriver/devmapper/devmapper.go index 7317118dcf..128fce15e2 100644 --- a/components/engine/daemon/graphdriver/devmapper/devmapper.go +++ b/components/engine/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 4d208f5ffdb5df39a192558b25dce2a5eb6c7e1a 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) Upstream-commit: 586a511cb5e47127a8fd173159fcd43e1b823fe7 Component: engine --- .../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/components/engine/daemon/graphdriver/devmapper/deviceset.go b/components/engine/daemon/graphdriver/devmapper/deviceset.go index 7940c4a5c4..01d1a5d312 100644 --- a/components/engine/daemon/graphdriver/devmapper/deviceset.go +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/devmapper.go b/components/engine/daemon/graphdriver/devmapper/devmapper.go index 128fce15e2..f064f9d615 100644 --- a/components/engine/daemon/graphdriver/devmapper/devmapper.go +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/devmapper_log.go b/components/engine/daemon/graphdriver/devmapper/devmapper_log.go index 18dde7cca5..7f78bbf0d4 100644 --- a/components/engine/daemon/graphdriver/devmapper/devmapper_log.go +++ b/components/engine/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 ce3bed99616d934bfc416fb7a3629b8feb21cea0 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) Upstream-commit: f26203cf544db07ae64dffaa495e4217976c0786 Component: engine --- .../daemon/graphdriver/devmapper/deviceset.go | 27 +++--- .../daemon/graphdriver/devmapper/devmapper.go | 94 ++++++++++++------- .../graphdriver/devmapper/devmapper_log.go | 4 + 3 files changed, 73 insertions(+), 52 deletions(-) diff --git a/components/engine/daemon/graphdriver/devmapper/deviceset.go b/components/engine/daemon/graphdriver/devmapper/deviceset.go index 01d1a5d312..d3a097961c 100644 --- a/components/engine/daemon/graphdriver/devmapper/deviceset.go +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/devmapper.go b/components/engine/daemon/graphdriver/devmapper/devmapper.go index f064f9d615..86af653b4d 100644 --- a/components/engine/daemon/graphdriver/devmapper/devmapper.go +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/devmapper_log.go b/components/engine/daemon/graphdriver/devmapper/devmapper_log.go index 7f78bbf0d4..cdeaed2525 100644 --- a/components/engine/daemon/graphdriver/devmapper/devmapper_log.go +++ b/components/engine/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 5874977460e9e1d80ef9937e295282e5a94cd42d 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) Upstream-commit: 6d631968fa095734da5e3483c5d7c43fd5b87146 Component: engine --- .../daemon/graphdriver/devmapper/deviceset.go | 186 ++++++++++-------- .../graphdriver/devmapper/driver_test.go | 4 - 2 files changed, 107 insertions(+), 83 deletions(-) diff --git a/components/engine/daemon/graphdriver/devmapper/deviceset.go b/components/engine/daemon/graphdriver/devmapper/deviceset.go index d3a097961c..3faf3d5bf0 100644 --- a/components/engine/daemon/graphdriver/devmapper/deviceset.go +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/driver_test.go b/components/engine/daemon/graphdriver/devmapper/driver_test.go index 77e8a6013a..af366ef293 100644 --- a/components/engine/daemon/graphdriver/devmapper/driver_test.go +++ b/components/engine/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 73af1a1e6dd91f2bce8c8b0fcd6debd7f2a3fb43 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) Upstream-commit: 6893689336e3cc325d1e6bcc6d61955418186936 Component: engine --- components/engine/server/buildfile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/server/buildfile.go b/components/engine/server/buildfile.go index 8466f4290e..019c47977b 100644 --- a/components/engine/server/buildfile.go +++ b/components/engine/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 0bb49ae03003b5d8b65ef98b97218f7e364a7702 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) Upstream-commit: 21b42dfcfd15ee6757bcba57acb511d5ce60a3ba Component: engine --- components/engine/archive/diff.go | 10 ---------- components/engine/archive/time_linux.go | 16 ++++++++++++++++ components/engine/archive/time_unsupported.go | 16 ++++++++++++++++ 3 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 components/engine/archive/time_linux.go create mode 100644 components/engine/archive/time_unsupported.go diff --git a/components/engine/archive/diff.go b/components/engine/archive/diff.go index 87e8ac7dc4..49d8cb4984 100644 --- a/components/engine/archive/diff.go +++ b/components/engine/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/components/engine/archive/time_linux.go b/components/engine/archive/time_linux.go new file mode 100644 index 0000000000..3448569b1e --- /dev/null +++ b/components/engine/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/components/engine/archive/time_unsupported.go b/components/engine/archive/time_unsupported.go new file mode 100644 index 0000000000..e85aac0540 --- /dev/null +++ b/components/engine/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 bbec633621fac74c2acc9b60dcc319e7a0826bd4 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) Upstream-commit: 659305085f551e39143636b1cfb8351a63cbab6f Component: engine --- components/engine/hack/dind | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/hack/dind b/components/engine/hack/dind index 94147f5324..e6ec59b38a 100755 --- a/components/engine/hack/dind +++ b/components/engine/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 0bb0e72f1c62d351ba023655cff0566bf3d11a9b 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) Upstream-commit: 48dbee082403a57b4dbc344e12b6724016d64b72 Component: engine --- components/engine/hack/MAINTAINERS.md | 26 +++++++++++++++---------- components/engine/hack/getmaintainer.sh | 10 +++++++--- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/components/engine/hack/MAINTAINERS.md b/components/engine/hack/MAINTAINERS.md index be3117c864..9dbdf99d9a 100644 --- a/components/engine/hack/MAINTAINERS.md +++ b/components/engine/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/components/engine/hack/getmaintainer.sh b/components/engine/hack/getmaintainer.sh index 2c24bacc89..ca532d42ec 100755 --- a/components/engine/hack/getmaintainer.sh +++ b/components/engine/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 d799d709fbb0b049e3fd11f75d0744b3d0ec2e7d 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) Upstream-commit: b06ad88ca0a94c63f0c45d00e3478e5e7fd808fa Component: engine --- components/engine/CONTRIBUTING.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/components/engine/CONTRIBUTING.md b/components/engine/CONTRIBUTING.md index 0e8b98122f..69a578f462 100644 --- a/components/engine/CONTRIBUTING.md +++ b/components/engine/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 d53ebc7c71d30dc4a8012c7ce27dffdb27922ce9 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) Upstream-commit: 91b5fe85029cfab2b7f8c55859e0fae999e9f968 Component: engine --- components/engine/pkg/cgroups/MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/engine/pkg/cgroups/MAINTAINERS b/components/engine/pkg/cgroups/MAINTAINERS index 1e998f8ac1..30cb8bba27 100644 --- a/components/engine/pkg/cgroups/MAINTAINERS +++ b/components/engine/pkg/cgroups/MAINTAINERS @@ -1 +1,3 @@ Michael Crosby (@crosbymichael) +Rohit Jnagal (@rjnagal) +Victor Marmol (@vmarmol) From d6298e76a2f73e3788d6045c27d726aaf83de8e1 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) Upstream-commit: dcae8c157b4e754917a1ea5901ba02dc226a6d4b Component: engine --- components/engine/docs/theme/mkdocs/css/base.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/engine/docs/theme/mkdocs/css/base.css b/components/engine/docs/theme/mkdocs/css/base.css index 999a0dedbe..956e17a263 100644 --- a/components/engine/docs/theme/mkdocs/css/base.css +++ b/components/engine/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 a16e59966d6cea166f6ec4845949c0d27c8e90bc 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) Upstream-commit: 02f95a089cd4f9ff7d8e5ba9c7780607dac78d14 Component: engine --- components/engine/docs/theme/mkdocs/base.html | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/components/engine/docs/theme/mkdocs/base.html b/components/engine/docs/theme/mkdocs/base.html index ca418b3cd7..66bb2d3d68 100644 --- a/components/engine/docs/theme/mkdocs/base.html +++ b/components/engine/docs/theme/mkdocs/base.html @@ -67,5 +67,19 @@ + From 575e3e191703246c040464bec41ab9730c7514bf 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) Upstream-commit: 6f327278446b958b32b02a3baefda26e6c8d219f Component: engine --- components/engine/registry/MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/registry/MAINTAINERS b/components/engine/registry/MAINTAINERS index bf3984f5f9..af791fb40c 100644 --- a/components/engine/registry/MAINTAINERS +++ b/components/engine/registry/MAINTAINERS @@ -1,3 +1,4 @@ Sam Alba (@samalba) Joffrey Fuhrer (@shin-) Ken Cochrane (@kencochrane) +Vincent Batts (@vbatts) From b4dfa516c6804f35da032a20bd92e8420c030b38 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. Upstream-commit: 7549199cc5e1de910f5dbd539831095739524ea6 Component: engine --- components/engine/docs/sources/use/port_redirection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/docs/sources/use/port_redirection.md b/components/engine/docs/sources/use/port_redirection.md index 9f2ce98eae..19de4141ba 100644 --- a/components/engine/docs/sources/use/port_redirection.md +++ b/components/engine/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 a947a1c096e1fe2ed833c1a4cbbdeeda52ac1c62 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) Upstream-commit: 36d913078b7ea0138dbc5d561703d068e7e31934 Component: engine --- components/engine/contrib/init/sysvinit-debian/docker | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/engine/contrib/init/sysvinit-debian/docker b/components/engine/contrib/init/sysvinit-debian/docker index 67f0d2807f..9b50fad448 100755 --- a/components/engine/contrib/init/sysvinit-debian/docker +++ b/components/engine/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 1f5f83d095bcc34f1a4849e3230701e68e33aba0 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) Upstream-commit: 91b7d8ebd31dba64b551de85e70786c568cb402a Component: engine --- components/engine/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 +++++++++++++++++++ components/engine/server/buildfile.go | 38 ++++++-- 15 files changed, 185 insertions(+), 6 deletions(-) create mode 100644 components/engine/integration-cli/build_tests/TestAdd/DirContentToExistDir/Dockerfile create mode 100644 components/engine/integration-cli/build_tests/TestAdd/DirContentToExistDir/test_dir/test_file create mode 100644 components/engine/integration-cli/build_tests/TestAdd/DirContentToRoot/Dockerfile create mode 100644 components/engine/integration-cli/build_tests/TestAdd/DirContentToRoot/test_dir/test_file create mode 100644 components/engine/integration-cli/build_tests/TestAdd/SingleFileToExistDir/Dockerfile create mode 100644 components/engine/integration-cli/build_tests/TestAdd/SingleFileToExistDir/test_file create mode 100644 components/engine/integration-cli/build_tests/TestAdd/SingleFileToNonExistDir/Dockerfile create mode 100644 components/engine/integration-cli/build_tests/TestAdd/SingleFileToNonExistDir/test_file create mode 100644 components/engine/integration-cli/build_tests/TestAdd/SingleFileToRoot/Dockerfile create mode 100644 components/engine/integration-cli/build_tests/TestAdd/SingleFileToRoot/test_file create mode 100644 components/engine/integration-cli/build_tests/TestAdd/WholeDirToRoot/Dockerfile create mode 100644 components/engine/integration-cli/build_tests/TestAdd/WholeDirToRoot/test_dir/test_file diff --git a/components/engine/archive/archive.go b/components/engine/archive/archive.go index 2fac18e99f..e21b10ca0c 100644 --- a/components/engine/archive/archive.go +++ b/components/engine/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/components/engine/integration-cli/build_tests/TestAdd/DirContentToExistDir/Dockerfile b/components/engine/integration-cli/build_tests/TestAdd/DirContentToExistDir/Dockerfile new file mode 100644 index 0000000000..6ab0e98f49 --- /dev/null +++ b/components/engine/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/components/engine/integration-cli/build_tests/TestAdd/DirContentToExistDir/test_dir/test_file b/components/engine/integration-cli/build_tests/TestAdd/DirContentToExistDir/test_dir/test_file new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/engine/integration-cli/build_tests/TestAdd/DirContentToRoot/Dockerfile b/components/engine/integration-cli/build_tests/TestAdd/DirContentToRoot/Dockerfile new file mode 100644 index 0000000000..03a9c052fd --- /dev/null +++ b/components/engine/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/components/engine/integration-cli/build_tests/TestAdd/DirContentToRoot/test_dir/test_file b/components/engine/integration-cli/build_tests/TestAdd/DirContentToRoot/test_dir/test_file new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/engine/integration-cli/build_tests/TestAdd/SingleFileToExistDir/Dockerfile b/components/engine/integration-cli/build_tests/TestAdd/SingleFileToExistDir/Dockerfile new file mode 100644 index 0000000000..fefbd09f0c --- /dev/null +++ b/components/engine/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/components/engine/integration-cli/build_tests/TestAdd/SingleFileToExistDir/test_file b/components/engine/integration-cli/build_tests/TestAdd/SingleFileToExistDir/test_file new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/engine/integration-cli/build_tests/TestAdd/SingleFileToNonExistDir/Dockerfile b/components/engine/integration-cli/build_tests/TestAdd/SingleFileToNonExistDir/Dockerfile new file mode 100644 index 0000000000..661990b7f4 --- /dev/null +++ b/components/engine/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/components/engine/integration-cli/build_tests/TestAdd/SingleFileToNonExistDir/test_file b/components/engine/integration-cli/build_tests/TestAdd/SingleFileToNonExistDir/test_file new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/engine/integration-cli/build_tests/TestAdd/SingleFileToRoot/Dockerfile b/components/engine/integration-cli/build_tests/TestAdd/SingleFileToRoot/Dockerfile new file mode 100644 index 0000000000..d6375debe1 --- /dev/null +++ b/components/engine/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/components/engine/integration-cli/build_tests/TestAdd/SingleFileToRoot/test_file b/components/engine/integration-cli/build_tests/TestAdd/SingleFileToRoot/test_file new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/engine/integration-cli/build_tests/TestAdd/WholeDirToRoot/Dockerfile b/components/engine/integration-cli/build_tests/TestAdd/WholeDirToRoot/Dockerfile new file mode 100644 index 0000000000..3db6d3fd95 --- /dev/null +++ b/components/engine/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/components/engine/integration-cli/build_tests/TestAdd/WholeDirToRoot/test_dir/test_file b/components/engine/integration-cli/build_tests/TestAdd/WholeDirToRoot/test_dir/test_file new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index 7cd42dc69c..cd3e06d60c 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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/components/engine/server/buildfile.go b/components/engine/server/buildfile.go index 24b0b58f25..253993528b 100644 --- a/components/engine/server/buildfile.go +++ b/components/engine/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 de6ae5d92c79d0edea3a2dad37a583ef3a3d82a0 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) Upstream-commit: 0ed9bba1ee48ff925ade710658fecd2270f8a223 Component: engine --- components/engine/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/VERSION b/components/engine/VERSION index af88ba8248..72ab694d37 100644 --- a/components/engine/VERSION +++ b/components/engine/VERSION @@ -1 +1 @@ -0.11.1 +0.11.1-dev From e6d82972c80403baa34d6b2d7f84f37469b892b2 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) Upstream-commit: fdccfaf72a614872aeeb79e9cee7f42abcaacf6d Component: engine --- components/engine/engine/env.go | 133 ----------------------- components/engine/engine/table.go | 141 +++++++++++++++++++++++++ components/engine/engine/table_test.go | 84 +++++++++++++++ 3 files changed, 225 insertions(+), 133 deletions(-) create mode 100644 components/engine/engine/table.go diff --git a/components/engine/engine/env.go b/components/engine/engine/env.go index f96795f48c..94d8c3d8a7 100644 --- a/components/engine/engine/env.go +++ b/components/engine/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/components/engine/engine/table.go b/components/engine/engine/table.go new file mode 100644 index 0000000000..292c4ed677 --- /dev/null +++ b/components/engine/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/components/engine/engine/table_test.go b/components/engine/engine/table_test.go index 3e8e4ff1b3..9a32ac9cdb 100644 --- a/components/engine/engine/table_test.go +++ b/components/engine/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 71620ac1c06e8336fa7386dbf6d7a1276b069f6d 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) Upstream-commit: d60301edb88a4e182a10cd2becb3795b2dd13fab Component: engine --- .../engine/pkg/libcontainer/security/restrict/restrict.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/engine/pkg/libcontainer/security/restrict/restrict.go b/components/engine/pkg/libcontainer/security/restrict/restrict.go index e1296b1d7f..361d0764a0 100644 --- a/components/engine/pkg/libcontainer/security/restrict/restrict.go +++ b/components/engine/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 960115467c93b95cb6b1fe66b93f199283f1a6cc 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 Upstream-commit: 5d39175c22b01cb31ebabb0c52de913a5541cd8b Component: engine --- components/engine/docs/sources/reference/run.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/docs/sources/reference/run.md b/components/engine/docs/sources/reference/run.md index 09c2b642a1..0f72679ced 100644 --- a/components/engine/docs/sources/reference/run.md +++ b/components/engine/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 ff4347f669c9cf7555919a4d005a9f56f97f0455 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. Upstream-commit: ea049a2ebc072819920c42140221ce4ab7bab22e Component: engine --- .../docs/sources/introduction/working-with-docker.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/components/engine/docs/sources/introduction/working-with-docker.md b/components/engine/docs/sources/introduction/working-with-docker.md index 8d946e6846..1b5b43fec4 100644 --- a/components/engine/docs/sources/introduction/working-with-docker.md +++ b/components/engine/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 49a93b84edc7aa2b144a79ec7c43c519d4aee7ff 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) Upstream-commit: bff0c4f3dc560109ed2d5dc6cae12453c9bc2747 Component: engine --- components/engine/daemon/graphdriver/devmapper/deviceset.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/components/engine/daemon/graphdriver/devmapper/deviceset.go b/components/engine/daemon/graphdriver/devmapper/deviceset.go index f734f55202..79563c108e 100644 --- a/components/engine/daemon/graphdriver/devmapper/deviceset.go +++ b/components/engine/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 0d11cb3ece7f44e39bd9dc9946601d2b492e53d0 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) Upstream-commit: 170e4d2e19258c4e45b975177618640cb554c32b Component: engine --- components/engine/engine/streams.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/engine/streams.go b/components/engine/engine/streams.go index 48f031de8f..6cc10d346d 100644 --- a/components/engine/engine/streams.go +++ b/components/engine/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 3b9e92a12c41646addb456c314dd075edc69e9a0 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) Upstream-commit: ca33d2589f69381719b456352fdaaaa3fe54d5ac Component: engine --- components/engine/docs/sources/installation/mac.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/docs/sources/installation/mac.md b/components/engine/docs/sources/installation/mac.md index c30e0b6440..8165362561 100644 --- a/components/engine/docs/sources/installation/mac.md +++ b/components/engine/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 90fcda8544ba477f512f9b1d5f40880751e3d23f 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) Upstream-commit: 4c2b9d732446adb472667c2a7017ba3e8cc58c0d Component: engine --- components/engine/api/client/commands.go | 4 ++-- components/engine/integration/runtime_test.go | 4 ++-- components/engine/server/server.go | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/components/engine/api/client/commands.go b/components/engine/api/client/commands.go index 89f9b0a4c4..46fec3f28a 100644 --- a/components/engine/api/client/commands.go +++ b/components/engine/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/components/engine/integration/runtime_test.go b/components/engine/integration/runtime_test.go index c84ea5bed2..07207c9b53 100644 --- a/components/engine/integration/runtime_test.go +++ b/components/engine/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/components/engine/server/server.go b/components/engine/server/server.go index 3763f87dd5..30e5ef1f6f 100644 --- a/components/engine/server/server.go +++ b/components/engine/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 719c55d2d08732d6509665c1da96885a3258e91a 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 Upstream-commit: fd5672349490f9cd18933a2508e1890c9e026af5 Component: engine --- components/engine/docs/mkdocs.yml | 2 +- .../engine/docs/sources/examples/mongodb.md | 191 ++++++++++++------ .../docs/sources/examples/mongodb/Dockerfile | 24 +++ 3 files changed, 158 insertions(+), 59 deletions(-) create mode 100644 components/engine/docs/sources/examples/mongodb/Dockerfile diff --git a/components/engine/docs/mkdocs.yml b/components/engine/docs/mkdocs.yml index c16436e892..32d0852791 100755 --- a/components/engine/docs/mkdocs.yml +++ b/components/engine/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/components/engine/docs/sources/examples/mongodb.md b/components/engine/docs/sources/examples/mongodb.md index 4b5f95d023..e397cb0e58 100644 --- a/components/engine/docs/sources/examples/mongodb.md +++ b/components/engine/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/components/engine/docs/sources/examples/mongodb/Dockerfile b/components/engine/docs/sources/examples/mongodb/Dockerfile new file mode 100644 index 0000000000..e7acc0fd85 --- /dev/null +++ b/components/engine/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 169b9a06a275376aa70bfba01ad566cecae07777 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) Upstream-commit: 47c79870ea529099cca635f53da870e0cea5652a Component: engine --- .../graphdriver/devmapper/driver_test.go | 52 ++++++++++++++++--- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/components/engine/daemon/graphdriver/devmapper/driver_test.go b/components/engine/daemon/graphdriver/devmapper/driver_test.go index e53dfaa753..54e75e2a09 100644 --- a/components/engine/daemon/graphdriver/devmapper/driver_test.go +++ b/components/engine/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 ed453a16df1b0d718695fe3ac64ea842fc644f91 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) Upstream-commit: 84f19a09ac1bb6221aeafd858306b097203aa974 Component: engine --- components/engine/daemon/graphdriver/vfs/driver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/daemon/graphdriver/vfs/driver.go b/components/engine/daemon/graphdriver/vfs/driver.go index 765b21cded..7473aa659d 100644 --- a/components/engine/daemon/graphdriver/vfs/driver.go +++ b/components/engine/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 b543168adca6887182ce8b28b33038a594f6d8cd 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) Upstream-commit: 27744062aa96f5f16d615ed829bc0d06b7df381d Component: engine --- .../daemon/graphdriver/btrfs/btrfs_test.go | 28 +++ .../graphdriver/devmapper/driver_test.go | 23 ++ .../daemon/graphdriver/graphtest/graphtest.go | 225 ++++++++++++++++++ .../engine/daemon/graphdriver/vfs/vfs_test.go | 28 +++ 4 files changed, 304 insertions(+) create mode 100644 components/engine/daemon/graphdriver/btrfs/btrfs_test.go create mode 100644 components/engine/daemon/graphdriver/graphtest/graphtest.go create mode 100644 components/engine/daemon/graphdriver/vfs/vfs_test.go diff --git a/components/engine/daemon/graphdriver/btrfs/btrfs_test.go b/components/engine/daemon/graphdriver/btrfs/btrfs_test.go new file mode 100644 index 0000000000..3069a98557 --- /dev/null +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/driver_test.go b/components/engine/daemon/graphdriver/devmapper/driver_test.go index 54e75e2a09..0602f123a0 100644 --- a/components/engine/daemon/graphdriver/devmapper/driver_test.go +++ b/components/engine/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/components/engine/daemon/graphdriver/graphtest/graphtest.go b/components/engine/daemon/graphdriver/graphtest/graphtest.go new file mode 100644 index 0000000000..c83c840bc3 --- /dev/null +++ b/components/engine/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/components/engine/daemon/graphdriver/vfs/vfs_test.go b/components/engine/daemon/graphdriver/vfs/vfs_test.go new file mode 100644 index 0000000000..e79f93c91d --- /dev/null +++ b/components/engine/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 12887ed424c126ada5975ae5284077fdf5719691 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) Upstream-commit: 4bdb8c03fc9ac4c7c49fd9838d7eccdfd66e1c5b Component: engine --- .../engine/daemon/graphdriver/aufs/aufs.go | 2 +- .../daemon/graphdriver/aufs/aufs_test.go | 2 +- .../engine/daemon/graphdriver/btrfs/btrfs.go | 2 +- .../engine/daemon/graphdriver/driver.go | 22 +++++++++++++------ 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/components/engine/daemon/graphdriver/aufs/aufs.go b/components/engine/daemon/graphdriver/aufs/aufs.go index 12b7a77fb3..2b7aa1b68a 100644 --- a/components/engine/daemon/graphdriver/aufs/aufs.go +++ b/components/engine/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/components/engine/daemon/graphdriver/aufs/aufs_test.go b/components/engine/daemon/graphdriver/aufs/aufs_test.go index 1ffa264aa1..dab5aecc41 100644 --- a/components/engine/daemon/graphdriver/aufs/aufs_test.go +++ b/components/engine/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/components/engine/daemon/graphdriver/btrfs/btrfs.go b/components/engine/daemon/graphdriver/btrfs/btrfs.go index 4d195537eb..614dc1ff06 100644 --- a/components/engine/daemon/graphdriver/btrfs/btrfs.go +++ b/components/engine/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/components/engine/daemon/graphdriver/driver.go b/components/engine/daemon/graphdriver/driver.go index 80bf8a0143..96f8d3ab3e 100644 --- a/components/engine/daemon/graphdriver/driver.go +++ b/components/engine/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 6dabe466b13c5b7057b31f33b9aef7d2fbea9051 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) Upstream-commit: 55cd7dd7f90d19332464ac946727297de1969483 Component: engine --- components/engine/daemon/graphdriver/graphtest/graphtest.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/engine/daemon/graphdriver/graphtest/graphtest.go b/components/engine/daemon/graphdriver/graphtest/graphtest.go index c83c840bc3..f8ccb2ef33 100644 --- a/components/engine/daemon/graphdriver/graphtest/graphtest.go +++ b/components/engine/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 c589c9a2f350d7c7ea88f8843b5d097451785d6b 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) Upstream-commit: b5a37127aadbefea937346846116cb93966798dd Component: engine --- components/engine/docs/sources/reference/builder.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/engine/docs/sources/reference/builder.md b/components/engine/docs/sources/reference/builder.md index 98e9e0f544..4c726aafa2 100644 --- a/components/engine/docs/sources/reference/builder.md +++ b/components/engine/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 184234bcc1d9bbb57fe0bfae5f0462c3e2aa0c1a 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) Upstream-commit: 5dfe0945c34402760475bbf51001d0ae0fb69ccb Component: engine --- components/engine/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/CONTRIBUTING.md b/components/engine/CONTRIBUTING.md index d77afbc443..6f07fcbcc7 100644 --- a/components/engine/CONTRIBUTING.md +++ b/components/engine/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 cc0e75c9115312a100fdab61b01303a049b61040 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) Upstream-commit: e304e3a6624e740159e99e83d6d13f0a3cdfeb49 Component: engine --- components/engine/api/server/server.go | 48 ++++---- components/engine/engine/engine.go | 3 +- components/engine/engine/job.go | 7 +- components/engine/engine/job_test.go | 17 +-- components/engine/engine/streams.go | 81 ++++--------- components/engine/engine/streams_test.go | 110 +++--------------- components/engine/integration/runtime_test.go | 39 ++++--- components/engine/integration/server_test.go | 26 ++--- components/engine/integration/utils_test.go | 8 +- 9 files changed, 112 insertions(+), 227 deletions(-) diff --git a/components/engine/api/server/server.go b/components/engine/api/server/server.go index 3c93a3478d..e2a7f651a8 100644 --- a/components/engine/api/server/server.go +++ b/components/engine/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/components/engine/engine/engine.go b/components/engine/engine/engine.go index 58b43eca04..5c3228d5d3 100644 --- a/components/engine/engine/engine.go +++ b/components/engine/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/components/engine/engine/job.go b/components/engine/engine/job.go index b56155ac1c..ca4d2ebcf3 100644 --- a/components/engine/engine/job.go +++ b/components/engine/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/components/engine/engine/job_test.go b/components/engine/engine/job_test.go index 1f927cbafc..67e723988e 100644 --- a/components/engine/engine/job_test.go +++ b/components/engine/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/components/engine/engine/streams.go b/components/engine/engine/streams.go index 48f031de8f..6d5511b2dc 100644 --- a/components/engine/engine/streams.go +++ b/components/engine/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/components/engine/engine/streams_test.go b/components/engine/engine/streams_test.go index 30d31d2952..83dd05c6f4 100644 --- a/components/engine/engine/streams_test.go +++ b/components/engine/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/components/engine/integration/runtime_test.go b/components/engine/integration/runtime_test.go index c84ea5bed2..9b79134f9f 100644 --- a/components/engine/integration/runtime_test.go +++ b/components/engine/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/components/engine/integration/server_test.go b/components/engine/integration/server_test.go index 226247556d..4da3e6e368 100644 --- a/components/engine/integration/server_test.go +++ b/components/engine/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/components/engine/integration/utils_test.go b/components/engine/integration/utils_test.go index 6901662ce6..d8101dfb1d 100644 --- a/components/engine/integration/utils_test.go +++ b/components/engine/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 fb89461e499b63b7d115a9d18205421169d5dc25 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) Upstream-commit: 2899195540857f2ad7c50ea509847c3a598c5e81 Component: engine --- components/engine/runconfig/parse.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/engine/runconfig/parse.go b/components/engine/runconfig/parse.go index f27adc2cae..5a588c0344 100644 --- a/components/engine/runconfig/parse.go +++ b/components/engine/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 3712225f957fe413feef724f4ea1fd2ab74f2dfb 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) Upstream-commit: 670564d07096c190011bf7adf74afe4d61ef9720 Component: engine --- components/engine/server/server.go | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/components/engine/server/server.go b/components/engine/server/server.go index 30e5ef1f6f..3c3b0b4c42 100644 --- a/components/engine/server/server.go +++ b/components/engine/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 c3352a3b6cfe839215475ee7dbd1cedf56ff07da 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) Upstream-commit: 0aeff69e5900a15e3872494ac0009600f6c1c372 Component: engine --- components/engine/engine/remote.go | 6 +-- components/engine/pkg/beam/beam.go | 41 ++++++++++++++++--- .../engine/pkg/beam/examples/beamsh/beamsh.go | 4 +- .../pkg/beam/examples/beamsh/builtins.go | 2 +- components/engine/pkg/beam/router.go | 2 +- 5 files changed, 43 insertions(+), 12 deletions(-) diff --git a/components/engine/engine/remote.go b/components/engine/engine/remote.go index 60aad243c5..e0c3c18e0d 100644 --- a/components/engine/engine/remote.go +++ b/components/engine/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/components/engine/pkg/beam/beam.go b/components/engine/pkg/beam/beam.go index b1e4667a3f..88d3ea7c79 100644 --- a/components/engine/pkg/beam/beam.go +++ b/components/engine/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/components/engine/pkg/beam/examples/beamsh/beamsh.go b/components/engine/pkg/beam/examples/beamsh/beamsh.go index 3f258de332..808f038c68 100644 --- a/components/engine/pkg/beam/examples/beamsh/beamsh.go +++ b/components/engine/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/components/engine/pkg/beam/examples/beamsh/builtins.go b/components/engine/pkg/beam/examples/beamsh/builtins.go index cc94d2b5fb..3242237cc1 100644 --- a/components/engine/pkg/beam/examples/beamsh/builtins.go +++ b/components/engine/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/components/engine/pkg/beam/router.go b/components/engine/pkg/beam/router.go index fc41a8991b..15910e95b1 100644 --- a/components/engine/pkg/beam/router.go +++ b/components/engine/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 4cfcc681e6f703e02a22837ded13d0a374dca867 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) Upstream-commit: dfdc03b061d5bd5a7557f077b500304d4da26d2e Component: engine --- components/engine/engine/remote.go | 2 + components/engine/engine/remote_test.go | 73 ++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/components/engine/engine/remote.go b/components/engine/engine/remote.go index e0c3c18e0d..fbb9951065 100644 --- a/components/engine/engine/remote.go +++ b/components/engine/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/components/engine/engine/remote_test.go b/components/engine/engine/remote_test.go index 54092ec934..a56120941f 100644 --- a/components/engine/engine/remote_test.go +++ b/components/engine/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 1b604a326b986de2fd5bc5ecfd1c9f4d785bb577 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) Upstream-commit: bf25951837440286fb185f97f7c9cfda11962cab Component: engine --- components/engine/engine/remote_test.go | 64 ++++++++++++++++--------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/components/engine/engine/remote_test.go b/components/engine/engine/remote_test.go index a56120941f..921a4376ce 100644 --- a/components/engine/engine/remote_test.go +++ b/components/engine/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 9cb18397df900d3820c8d662fc5220fe94272a45 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 Upstream-commit: d61190169de27bc58f1fe4a8f49b37bd2294d489 Component: engine --- components/engine/engine/job.go | 3 +++ components/engine/engine/remote.go | 21 ++++++++++++------- components/engine/engine/remote_test.go | 28 +++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/components/engine/engine/job.go b/components/engine/engine/job.go index b56155ac1c..7e655e1b12 100644 --- a/components/engine/engine/job.go +++ b/components/engine/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/components/engine/engine/remote.go b/components/engine/engine/remote.go index fbb9951065..1da521a3c7 100644 --- a/components/engine/engine/remote.go +++ b/components/engine/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/components/engine/engine/remote_test.go b/components/engine/engine/remote_test.go index 921a4376ce..a23830af01 100644 --- a/components/engine/engine/remote_test.go +++ b/components/engine/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 b701571f37e21a7c5f3e1ecf7eaad5f422a1c14d 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) Upstream-commit: 2af030ab57d1d84ac9a1d22552dc9d83b16951c4 Component: engine --- components/engine/pkg/beam/data/message.go | 10 ++++++++++ components/engine/pkg/beam/data/message_test.go | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/components/engine/pkg/beam/data/message.go b/components/engine/pkg/beam/data/message.go index 193fb7b241..0ebe90295a 100644 --- a/components/engine/pkg/beam/data/message.go +++ b/components/engine/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/components/engine/pkg/beam/data/message_test.go b/components/engine/pkg/beam/data/message_test.go index 7685769069..7224f33d11 100644 --- a/components/engine/pkg/beam/data/message_test.go +++ b/components/engine/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 371479ab9fb02e303ac3c929e1d9b08049b0345a 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) Upstream-commit: c7978c98097b92b659e2ea1763cd134b0695407e Component: engine --- components/engine/engine/env.go | 24 ++++++++++++++++++++++++ components/engine/engine/env_test.go | 20 ++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/components/engine/engine/env.go b/components/engine/engine/env.go index 94d8c3d8a7..f63f29e10f 100644 --- a/components/engine/engine/env.go +++ b/components/engine/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/components/engine/engine/env_test.go b/components/engine/engine/env_test.go index 0c66cea04e..39669d6780 100644 --- a/components/engine/engine/env_test.go +++ b/components/engine/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 69200e5e0370257fef7d324271f1a60840f16a51 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) Upstream-commit: a1754c7e46b321c2a079fc5186984a847aa50218 Component: engine --- components/engine/engine/remote.go | 13 ++++++-- components/engine/engine/remote_test.go | 41 +++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/components/engine/engine/remote.go b/components/engine/engine/remote.go index 1da521a3c7..974ca02137 100644 --- a/components/engine/engine/remote.go +++ b/components/engine/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/components/engine/engine/remote_test.go b/components/engine/engine/remote_test.go index a23830af01..e59ac78cc0 100644 --- a/components/engine/engine/remote_test.go +++ b/components/engine/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 9910e2b0616ecef3249608a32fcd08c4be22fde0 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) Upstream-commit: b4ccd7cbfb5f2c7c4b6c963c4c12e41500e7ad55 Component: engine --- components/engine/daemon/daemon.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go index 00b6d9eee2..7901f8ec6f 100644 --- a/components/engine/daemon/daemon.go +++ b/components/engine/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 2dde49a6073ff03fb10285be48a7be611a959dd7 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) Upstream-commit: 46a47f0dcbce1a4240bc970388dd09d029f82f61 Component: engine --- components/engine/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/Dockerfile b/components/engine/Dockerfile index be2233ff87..8a8818d757 100644 --- a/components/engine/Dockerfile +++ b/components/engine/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 910a692b372f4aa6987e45f5c6ec548ef5663b01 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) Upstream-commit: ff4ef504708bfaa51d4d361455689a21a031cc35 Component: engine --- components/engine/graph/service.go | 128 +++++++++++++++++++++++++++++ components/engine/server/server.go | 7 +- 2 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 components/engine/graph/service.go diff --git a/components/engine/graph/service.go b/components/engine/graph/service.go new file mode 100644 index 0000000000..211babd0bd --- /dev/null +++ b/components/engine/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/components/engine/server/server.go b/components/engine/server/server.go index 30e5ef1f6f..ac2bb0cada 100644 --- a/components/engine/server/server.go +++ b/components/engine/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 b2601c1b6388c30e9f6289746b95d525371e6b9f 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) Upstream-commit: 752c57ae567813f354aca66ff51d8d64100ae01b Component: engine --- components/engine/daemon/execdriver/lxc/driver.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/components/engine/daemon/execdriver/lxc/driver.go b/components/engine/daemon/execdriver/lxc/driver.go index d787d8d873..0b98176213 100644 --- a/components/engine/daemon/execdriver/lxc/driver.go +++ b/components/engine/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 4b636334e3d549a1f8ea2b0f8562d46bb82eabb4 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) Upstream-commit: 3f8ffb461ab66535b3c3a96a564c06db12a27281 Component: engine --- components/engine/utils/signal_freebsd.go | 42 ----------------------- 1 file changed, 42 deletions(-) delete mode 100644 components/engine/utils/signal_freebsd.go diff --git a/components/engine/utils/signal_freebsd.go b/components/engine/utils/signal_freebsd.go deleted file mode 100644 index 65a700e894..0000000000 --- a/components/engine/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 a5c6015bad4553ba1636c59151e6fa266de4523f 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) Upstream-commit: a4a023580bc89fb34e5f80ca472fc70267546eaf Component: engine --- components/engine/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/engine/README.md b/components/engine/README.md index fae1bb916b..c965efafe8 100644 --- a/components/engine/README.md +++ b/components/engine/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 c178af94585850a2063346f3ade018724bcf4687 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) Upstream-commit: 62bfef59f7ae6f9128bfc3e7ef2e6ed5e4441d2e Component: engine --- components/engine/pkg/beam/beam.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/pkg/beam/beam.go b/components/engine/pkg/beam/beam.go index 88d3ea7c79..2e8895a153 100644 --- a/components/engine/pkg/beam/beam.go +++ b/components/engine/pkg/beam/beam.go @@ -30,7 +30,7 @@ type ReceiveSender interface { } const ( - R int = 1 << (32 - 1 - iota) + R = iota W ) From 7a283742d22624eece0d05bb387916a2ba2c286d 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) Upstream-commit: 7cc27b2075d6293ab2f54a7255fcb1b1e062fd91 Component: engine --- .../integration-cli/docker_cli_links_test.go | 26 +++++++++++++++++++ .../engine/integration-cli/docker_utils.go | 10 +++++++ 2 files changed, 36 insertions(+) diff --git a/components/engine/integration-cli/docker_cli_links_test.go b/components/engine/integration-cli/docker_cli_links_test.go index 55c41e0bbc..ed30288b7d 100644 --- a/components/engine/integration-cli/docker_cli_links_test.go +++ b/components/engine/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/components/engine/integration-cli/docker_utils.go b/components/engine/integration-cli/docker_utils.go index 6da86c9753..17a331f2dd 100644 --- a/components/engine/integration-cli/docker_utils.go +++ b/components/engine/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 57c8245b81ee466a8c86995d09d6b61f2acd7b63 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) Upstream-commit: 0d59cc080a8dc115bdb727d376e3965e58109718 Component: engine --- components/engine/docs/Dockerfile | 2 +- components/engine/docs/release.sh | 7 +++++-- components/engine/docs/s3_website.json | 12 ++++++------ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/components/engine/docs/Dockerfile b/components/engine/docs/Dockerfile index a907072ddf..694729d89b 100644 --- a/components/engine/docs/Dockerfile +++ b/components/engine/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/components/engine/docs/release.sh b/components/engine/docs/release.sh index 323887f594..acb40a46d7 100755 --- a/components/engine/docs/release.sh +++ b/components/engine/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/components/engine/docs/s3_website.json b/components/engine/docs/s3_website.json index fb14628ce6..2d158cf9de 100644 --- a/components/engine/docs/s3_website.json +++ b/components/engine/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 a0faa3873c4f3376fbe284d94a73e612ba1655c7 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) Upstream-commit: 1dec8fd03c4f9c99491d3019f56772cccaf100fd Component: engine --- components/engine/docs/release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/docs/release.sh b/components/engine/docs/release.sh index 323887f594..1ca648e578 100755 --- a/components/engine/docs/release.sh +++ b/components/engine/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 76ae6d21587d8c298e0420bc0ca2e9fd901100f2 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) Upstream-commit: c1108f5d605c30a42bb494920b50d486c6e404a4 Component: engine --- components/engine/contrib/desktop-integration/data/Dockerfile | 2 +- .../engine/contrib/desktop-integration/iceweasel/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/contrib/desktop-integration/data/Dockerfile b/components/engine/contrib/desktop-integration/data/Dockerfile index 76846af912..236912f904 100644 --- a/components/engine/contrib/desktop-integration/data/Dockerfile +++ b/components/engine/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/components/engine/contrib/desktop-integration/iceweasel/Dockerfile b/components/engine/contrib/desktop-integration/iceweasel/Dockerfile index f9f58c9ca5..80d6a55e4a 100644 --- a/components/engine/contrib/desktop-integration/iceweasel/Dockerfile +++ b/components/engine/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 9844c828066678acb1d7b233fc78a2472a1ae3c3 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) Upstream-commit: 70ef53f25e177e42046170ef59bb29ebd77a3016 Component: engine --- .../engine/pkg/libcontainer/mount/init.go | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/components/engine/pkg/libcontainer/mount/init.go b/components/engine/pkg/libcontainer/mount/init.go index cfe61d1532..b0a3ef1061 100644 --- a/components/engine/pkg/libcontainer/mount/init.go +++ b/components/engine/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 8c86f99ef384f5d8478a6823fec59175352ebc19 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 Upstream-commit: feb42d3f97f3a4de9317f17297c6277fcb0cc2af Component: engine --- components/engine/docs/sources/reference/run.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/engine/docs/sources/reference/run.md b/components/engine/docs/sources/reference/run.md index 0f72679ced..a0be7c34db 100644 --- a/components/engine/docs/sources/reference/run.md +++ b/components/engine/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 f59da8d68c9cc0a60d8ecf8bfc4d2da7eb0a7f10 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 Upstream-commit: 3e636446c3a9de24b5b990988e0bd4890277df61 Component: engine --- .../docs/sources/installation/google.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/components/engine/docs/sources/installation/google.md b/components/engine/docs/sources/installation/google.md index 4c22808dcb..29ffe0d73f 100644 --- a/components/engine/docs/sources/installation/google.md +++ b/components/engine/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 5bdee3011a4892933fe4440aa6be1b0ab72114a4 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 Upstream-commit: 79f234e049c72e0cac97bbd93815f33a919eaf1b Component: engine --- components/engine/docs/sources/articles/runmetrics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/docs/sources/articles/runmetrics.md b/components/engine/docs/sources/articles/runmetrics.md index 50d46047c0..15f53fb0d0 100644 --- a/components/engine/docs/sources/articles/runmetrics.md +++ b/components/engine/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 616f1c2db19e3795a8a42acbe2fa08c9e6808756 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) Upstream-commit: fee1bbd79ef8767ed149c1b1af4a39ad35e07772 Component: engine --- components/engine/pkg/libcontainer/mount/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/pkg/libcontainer/mount/init.go b/components/engine/pkg/libcontainer/mount/init.go index cfe61d1532..2d1f6516f0 100644 --- a/components/engine/pkg/libcontainer/mount/init.go +++ b/components/engine/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 fba87d75f12d5882af1be8839f90758d0ff7b28e 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) Upstream-commit: cc678a7078b417a330e8d4e3604b74f9e5d4cda4 Component: engine --- components/engine/pkg/libcontainer/mount/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/pkg/libcontainer/mount/init.go b/components/engine/pkg/libcontainer/mount/init.go index b0a3ef1061..d01e9d5c95 100644 --- a/components/engine/pkg/libcontainer/mount/init.go +++ b/components/engine/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 23c5da98b4dee0fd98571ee4032e030b20574b5d 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) Upstream-commit: 905795ece624675abe2ec2622b0bbafdb9d7f44c Component: engine --- components/engine/daemon/execdriver/lxc/lxc_template.go | 1 + components/engine/graph/graph.go | 1 + components/engine/pkg/libcontainer/mount/init.go | 1 + 3 files changed, 3 insertions(+) diff --git a/components/engine/daemon/execdriver/lxc/lxc_template.go b/components/engine/daemon/execdriver/lxc/lxc_template.go index 7fdc5ce92b..d40b316768 100644 --- a/components/engine/daemon/execdriver/lxc/lxc_template.go +++ b/components/engine/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/components/engine/graph/graph.go b/components/engine/graph/graph.go index b889139121..5de9cbe7a1 100644 --- a/components/engine/graph/graph.go +++ b/components/engine/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/components/engine/pkg/libcontainer/mount/init.go b/components/engine/pkg/libcontainer/mount/init.go index 4d11cc819a..12f833a966 100644 --- a/components/engine/pkg/libcontainer/mount/init.go +++ b/components/engine/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 91980047579c6e72dcf731465280a1d2762aa664 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) Upstream-commit: 1d15c0b491afc6b32206d62ee3f50448b89b8d49 Component: engine --- components/engine/server/MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/server/MAINTAINERS b/components/engine/server/MAINTAINERS index aee10c8421..3564d3db47 100644 --- a/components/engine/server/MAINTAINERS +++ b/components/engine/server/MAINTAINERS @@ -1 +1,2 @@ Solomon Hykes (@shykes) +Victor Vieux (@vieux) \ No newline at end of file From 7e5ef8d0015f69bc940190e517e1512f994aab71 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) Upstream-commit: 7894a70f8b2dcb329178978066d825dc41ec6239 Component: engine --- components/engine/builtins/builtins.go | 30 ++++++++++++++++++++++++-- components/engine/server/server.go | 19 ---------------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/components/engine/builtins/builtins.go b/components/engine/builtins/builtins.go index 40d421f154..572e16252a 100644 --- a/components/engine/builtins/builtins.go +++ b/components/engine/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/components/engine/server/server.go b/components/engine/server/server.go index ac2bb0cada..7c25d0306f 100644 --- a/components/engine/server/server.go +++ b/components/engine/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 cbbfe1274af14fb777244c1bc372d48cf26748a2 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) Upstream-commit: d54dec4d8b6f36fa9211e7a3379f7e949c40b0ce Component: engine --- components/engine/api/common.go | 7 ++++--- components/engine/daemon/network_settings.go | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/components/engine/api/common.go b/components/engine/api/common.go index af4ced4f6e..f4e31a970b 100644 --- a/components/engine/api/common.go +++ b/components/engine/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/components/engine/daemon/network_settings.go b/components/engine/daemon/network_settings.go index 762270362b..a5c750acfe 100644 --- a/components/engine/daemon/network_settings.go +++ b/components/engine/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 d39f282a2b11cb7d89671860a1e64b18b35e5e06 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) Upstream-commit: d33b4655c4339dcbbf9f78948598e216ac3c06b4 Component: engine --- components/engine/api/client/commands.go | 15 ++-- .../execdriver/native/configuration/parse.go | 6 +- components/engine/daemon/state.go | 7 +- components/engine/pkg/units/MAINTAINERS | 2 + components/engine/pkg/units/duration.go | 31 ++++++++ components/engine/pkg/units/size.go | 56 ++++++++++++++ components/engine/pkg/units/size_test.go | 54 ++++++++++++++ components/engine/runconfig/parse.go | 3 +- components/engine/utils/jsonmessage.go | 8 +- components/engine/utils/utils.go | 74 ------------------- components/engine/utils/utils_test.go | 49 ------------ 11 files changed, 165 insertions(+), 140 deletions(-) create mode 100644 components/engine/pkg/units/MAINTAINERS create mode 100644 components/engine/pkg/units/duration.go create mode 100644 components/engine/pkg/units/size.go create mode 100644 components/engine/pkg/units/size_test.go diff --git a/components/engine/api/client/commands.go b/components/engine/api/client/commands.go index 46fec3f28a..f60b6cebd7 100644 --- a/components/engine/api/client/commands.go +++ b/components/engine/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/components/engine/daemon/execdriver/native/configuration/parse.go b/components/engine/daemon/execdriver/native/configuration/parse.go index 22fe4b0e66..3767f5a866 100644 --- a/components/engine/daemon/execdriver/native/configuration/parse.go +++ b/components/engine/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/components/engine/daemon/state.go b/components/engine/daemon/state.go index 562929c87a..c0ed9516e3 100644 --- a/components/engine/daemon/state.go +++ b/components/engine/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/components/engine/pkg/units/MAINTAINERS b/components/engine/pkg/units/MAINTAINERS new file mode 100644 index 0000000000..68a97d2fc2 --- /dev/null +++ b/components/engine/pkg/units/MAINTAINERS @@ -0,0 +1,2 @@ +Michael Crosby (@crosbymichael) +Victor Vieux (@vieux) diff --git a/components/engine/pkg/units/duration.go b/components/engine/pkg/units/duration.go new file mode 100644 index 0000000000..cd33121496 --- /dev/null +++ b/components/engine/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/components/engine/pkg/units/size.go b/components/engine/pkg/units/size.go new file mode 100644 index 0000000000..99c8800965 --- /dev/null +++ b/components/engine/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/components/engine/pkg/units/size_test.go b/components/engine/pkg/units/size_test.go new file mode 100644 index 0000000000..958a4ca13d --- /dev/null +++ b/components/engine/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/components/engine/runconfig/parse.go b/components/engine/runconfig/parse.go index 5a588c0344..d8e4127f0a 100644 --- a/components/engine/runconfig/parse.go +++ b/components/engine/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/components/engine/utils/jsonmessage.go b/components/engine/utils/jsonmessage.go index 6be421be94..d6546e3ee6 100644 --- a/components/engine/utils/jsonmessage.go +++ b/components/engine/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/components/engine/utils/utils.go b/components/engine/utils/utils.go index 4ef44b5617..86c9b74a87 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/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/components/engine/utils/utils_test.go b/components/engine/utils/utils_test.go index ccd212202c..83164c68dd 100644 --- a/components/engine/utils/utils_test.go +++ b/components/engine/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 4502be27177336b410e8be00b3c423dbb1a2869d 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) Upstream-commit: b9c2d57d890c0f6a798baf74f7b56793fdf1fd61 Component: engine --- components/engine/docs/sources/use/workingwithrepository.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/engine/docs/sources/use/workingwithrepository.md b/components/engine/docs/sources/use/workingwithrepository.md index 07f130a909..d92d057d58 100644 --- a/components/engine/docs/sources/use/workingwithrepository.md +++ b/components/engine/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 e67f6c038853f25adda6263ad1bb30d2a631d6fc 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) Upstream-commit: 100a92146e563d39d5a401c11d48e7b4ef7fad6e Component: engine --- components/engine/daemon/daemon.go | 29 ++++++++++++++++++++++++++ components/engine/server/server.go | 33 +----------------------------- 2 files changed, 30 insertions(+), 32 deletions(-) diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go index 7901f8ec6f..38a107bba3 100644 --- a/components/engine/daemon/daemon.go +++ b/components/engine/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/components/engine/server/server.go b/components/engine/server/server.go index 7c25d0306f..f8e7539e7e 100644 --- a/components/engine/server/server.go +++ b/components/engine/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 cb8e10ccdc38e147ac4b27d82e1f7d87729432cc 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) Upstream-commit: f3736265fdddef9e9a9ab588906eeb7abe8a0cf6 Component: engine --- components/engine/api/server/server.go | 7 ++++++- components/engine/docker/docker.go | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/components/engine/api/server/server.go b/components/engine/api/server/server.go index 3c93a3478d..ed1d434064 100644 --- a/components/engine/api/server/server.go +++ b/components/engine/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/components/engine/docker/docker.go b/components/engine/docker/docker.go index 60f34a1f14..1683d7a0e8 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/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 aba2e9f1f50abc07b3b21c3976d0082f79d2c82b 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) Upstream-commit: 937f8f2d813f58480e4bb2c0060353be31b04e10 Component: engine --- components/engine/api/server/server.go | 8 +++----- components/engine/builtins/builtins.go | 5 ++++- components/engine/docker/docker.go | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/components/engine/api/server/server.go b/components/engine/api/server/server.go index ed1d434064..8fe518f7c6 100644 --- a/components/engine/api/server/server.go +++ b/components/engine/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/components/engine/builtins/builtins.go b/components/engine/builtins/builtins.go index 572e16252a..3e0041c9d7 100644 --- a/components/engine/builtins/builtins.go +++ b/components/engine/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/components/engine/docker/docker.go b/components/engine/docker/docker.go index 1683d7a0e8..ab485d0b63 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/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 267e5e63e1c5decc0912bd5d711f4f642d4578ea 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) Upstream-commit: fe445a2447e701cc91ef77d7bcb0978a5d373940 Component: engine --- components/engine/docker/docker.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index 60f34a1f14..275a96c73a 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/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 bd987eeae139003459cc7d7c6b435e473eacaef4 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 Upstream-commit: 653328c6cef8bab89343587b134ba7676ee39867 Component: engine --- components/engine/contrib/man/md/docker.1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/contrib/man/md/docker.1.md b/components/engine/contrib/man/md/docker.1.md index d1ddf192b5..0071a71c92 100644 --- a/components/engine/contrib/man/md/docker.1.md +++ b/components/engine/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 23ad20055508781e4301d3e65399e95005223116 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) Upstream-commit: b7c7b851dce28bb679e0289168da382d7cdad74b Component: engine --- components/engine/pkg/libcontainer/mount/init.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/engine/pkg/libcontainer/mount/init.go b/components/engine/pkg/libcontainer/mount/init.go index 12f833a966..16fb758e57 100644 --- a/components/engine/pkg/libcontainer/mount/init.go +++ b/components/engine/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 7c5b416146649c085b296a2f700cf1c8d669c103 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) Upstream-commit: dcf81f95fdfe3ac8e97602d2ef2fef03288c15b1 Component: engine --- .../engine/{utils => pkg/symlink}/fs.go | 33 +------------------ .../engine/{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 components/engine/utils/utils.go | 31 +++++++++++++++++ 8 files changed, 33 insertions(+), 33 deletions(-) rename components/engine/{utils => pkg/symlink}/fs.go (64%) rename components/engine/{utils => pkg/symlink}/fs_test.go (99%) rename components/engine/{utils => pkg/symlink}/testdata/fs/a/d (100%) rename components/engine/{utils => pkg/symlink}/testdata/fs/a/e (100%) rename components/engine/{utils => pkg/symlink}/testdata/fs/a/f (100%) rename components/engine/{utils => pkg/symlink}/testdata/fs/b/h (100%) rename components/engine/{utils => pkg/symlink}/testdata/fs/g (100%) diff --git a/components/engine/utils/fs.go b/components/engine/pkg/symlink/fs.go similarity index 64% rename from components/engine/utils/fs.go rename to components/engine/pkg/symlink/fs.go index e07ced75d7..e91d33db4b 100644 --- a/components/engine/utils/fs.go +++ b/components/engine/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/components/engine/utils/fs_test.go b/components/engine/pkg/symlink/fs_test.go similarity index 99% rename from components/engine/utils/fs_test.go rename to components/engine/pkg/symlink/fs_test.go index 9affc00e91..1f12aa3a60 100644 --- a/components/engine/utils/fs_test.go +++ b/components/engine/pkg/symlink/fs_test.go @@ -1,4 +1,4 @@ -package utils +package symlink import ( "io/ioutil" diff --git a/components/engine/utils/testdata/fs/a/d b/components/engine/pkg/symlink/testdata/fs/a/d similarity index 100% rename from components/engine/utils/testdata/fs/a/d rename to components/engine/pkg/symlink/testdata/fs/a/d diff --git a/components/engine/utils/testdata/fs/a/e b/components/engine/pkg/symlink/testdata/fs/a/e similarity index 100% rename from components/engine/utils/testdata/fs/a/e rename to components/engine/pkg/symlink/testdata/fs/a/e diff --git a/components/engine/utils/testdata/fs/a/f b/components/engine/pkg/symlink/testdata/fs/a/f similarity index 100% rename from components/engine/utils/testdata/fs/a/f rename to components/engine/pkg/symlink/testdata/fs/a/f diff --git a/components/engine/utils/testdata/fs/b/h b/components/engine/pkg/symlink/testdata/fs/b/h similarity index 100% rename from components/engine/utils/testdata/fs/b/h rename to components/engine/pkg/symlink/testdata/fs/b/h diff --git a/components/engine/utils/testdata/fs/g b/components/engine/pkg/symlink/testdata/fs/g similarity index 100% rename from components/engine/utils/testdata/fs/g rename to components/engine/pkg/symlink/testdata/fs/g diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go index 4ef44b5617..7ffcc06b93 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/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 740073112a1b03a11f72b7ee933e1ee14b648f5f 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) Upstream-commit: ca040b1a377c467a9504ffa256ae77d9e3d29f0c Component: engine --- components/engine/daemon/volumes.go | 4 ++-- components/engine/pkg/libcontainer/mount/init.go | 4 ++-- components/engine/server/buildfile.go | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/components/engine/daemon/volumes.go b/components/engine/daemon/volumes.go index a15e3084b2..ac01c6a982 100644 --- a/components/engine/daemon/volumes.go +++ b/components/engine/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/components/engine/pkg/libcontainer/mount/init.go b/components/engine/pkg/libcontainer/mount/init.go index 16fb758e57..e5f2b43cab 100644 --- a/components/engine/pkg/libcontainer/mount/init.go +++ b/components/engine/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/components/engine/server/buildfile.go b/components/engine/server/buildfile.go index eeafbb5b4c..faaac0d3d4 100644 --- a/components/engine/server/buildfile.go +++ b/components/engine/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 057bbc9d6afc81e7b09c7d8eb756d21efc11530a 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) Upstream-commit: ea7647099fcabd73077a403d461e9a0778dda12f Component: engine --- components/engine/pkg/symlink/MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 components/engine/pkg/symlink/MAINTAINERS diff --git a/components/engine/pkg/symlink/MAINTAINERS b/components/engine/pkg/symlink/MAINTAINERS new file mode 100644 index 0000000000..68a97d2fc2 --- /dev/null +++ b/components/engine/pkg/symlink/MAINTAINERS @@ -0,0 +1,2 @@ +Michael Crosby (@crosbymichael) +Victor Vieux (@vieux) From df579e2ee63efa64ddc88184211b048f9f2288e8 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) Upstream-commit: 461f801f832b1ecbe527999e05dc0bede6ca6c71 Component: engine --- components/engine/utils/tarsum_test.go | 59 ++++++++++++++++++ .../json | 1 + .../layer.tar | Bin 0 -> 9216 bytes 3 files changed, 60 insertions(+) create mode 100644 components/engine/utils/tarsum_test.go create mode 100644 components/engine/utils/testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/json create mode 100644 components/engine/utils/testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar diff --git a/components/engine/utils/tarsum_test.go b/components/engine/utils/tarsum_test.go new file mode 100644 index 0000000000..82aea57c69 --- /dev/null +++ b/components/engine/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/components/engine/utils/testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/json b/components/engine/utils/testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/json new file mode 100644 index 0000000000..0f0ba4974d --- /dev/null +++ b/components/engine/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/components/engine/utils/testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar b/components/engine/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) Upstream-commit: ae85dd54582e94d36b146ab1688844ed58cc8df3 Component: engine --- components/engine/pkg/libcontainer/console/console.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/components/engine/pkg/libcontainer/console/console.go b/components/engine/pkg/libcontainer/console/console.go index 5f06aea225..62edbfde8a 100644 --- a/components/engine/pkg/libcontainer/console/console.go +++ b/components/engine/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 749eeee77f2ae3859f7a190d91573491e36a3877 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) Upstream-commit: efa369a6ee89722ace2e85aad91c69ddf37985c5 Component: engine --- components/engine/utils/tarsum_test.go | 4 ++++ .../json | 1 + .../layer.tar | Bin 0 -> 1536 bytes 3 files changed, 5 insertions(+) create mode 100644 components/engine/utils/testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/json create mode 100644 components/engine/utils/testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/layer.tar diff --git a/components/engine/utils/tarsum_test.go b/components/engine/utils/tarsum_test.go index 82aea57c69..77cacfb730 100644 --- a/components/engine/utils/tarsum_test.go +++ b/components/engine/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/components/engine/utils/testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/json b/components/engine/utils/testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/json new file mode 100644 index 0000000000..12c18a076f --- /dev/null +++ b/components/engine/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/components/engine/utils/testdata/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/layer.tar b/components/engine/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) Upstream-commit: cfd1227e91d54cca533a73cb7d12c124c7c934af Component: engine --- components/engine/utils/tarsum_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/utils/tarsum_test.go b/components/engine/utils/tarsum_test.go index 77cacfb730..1b2f0ba735 100644 --- a/components/engine/utils/tarsum_test.go +++ b/components/engine/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 eb9c7bf12e5e502aafbdd2fec853c529acd02f6f 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) Upstream-commit: af891a67516149c4db490018430114c137cba9db Component: engine --- components/engine/docs/sources/use/working_with_volumes.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/engine/docs/sources/use/working_with_volumes.md b/components/engine/docs/sources/use/working_with_volumes.md index 7d6136b85a..065b867c68 100644 --- a/components/engine/docs/sources/use/working_with_volumes.md +++ b/components/engine/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 40fe9434ea0afb3be97519008b8157be26c556e5 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) Upstream-commit: 195f3a3f42ac8894ade9e3dbf8f02945781b6ace Component: engine --- components/engine/docs/README.md | 100 +++++++++++++++---------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/components/engine/docs/README.md b/components/engine/docs/README.md index 47b390bda4..71ce548003 100755 --- a/components/engine/docs/README.md +++ b/components/engine/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 396c5aa745d422a6b63479bebb5f6849751d47fc 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) Upstream-commit: 3de15bda7e1d3ab193094e6e07a5b2e42ea828bd Component: engine --- components/engine/pkg/cgroups/fs/apply_raw.go | 12 ++- components/engine/pkg/cgroups/fs/cpuset.go | 86 +++++++++++++++++-- 2 files changed, 89 insertions(+), 9 deletions(-) diff --git a/components/engine/pkg/cgroups/fs/apply_raw.go b/components/engine/pkg/cgroups/fs/apply_raw.go index 5f9fc826b3..ee26bffac5 100644 --- a/components/engine/pkg/cgroups/fs/apply_raw.go +++ b/components/engine/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/components/engine/pkg/cgroups/fs/cpuset.go b/components/engine/pkg/cgroups/fs/cpuset.go index 8a13c56cea..f9f65ba422 100644 --- a/components/engine/pkg/cgroups/fs/cpuset.go +++ b/components/engine/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 9a8663f86a84d74a7d36118ed9f0af2ab3dfde3c 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) Upstream-commit: bfc3a4192ae5723e401470688cdae59b95bd61f1 Component: engine --- components/engine/AUTHORS | 1 + components/engine/daemon/container.go | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/components/engine/AUTHORS b/components/engine/AUTHORS index adfcfaa851..b8c58ab09a 100644 --- a/components/engine/AUTHORS +++ b/components/engine/AUTHORS @@ -6,6 +6,7 @@ Aanand Prasad Aaron Feng Abel Muiño +Aleksa Sarai Alexander Larsson Alexey Shamrin Alex Gaynor diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go index 7b6b65494e..7250b442a6 100644 --- a/components/engine/daemon/container.go +++ b/components/engine/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 37a49fd37145141553777280584388788733ebc6 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) Upstream-commit: 79ca77f3e80d983cf72aa131c1b59c77c60270b0 Component: engine --- .../integration-cli/docker_cli_cp_test.go | 208 ++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 components/engine/integration-cli/docker_cli_cp_test.go diff --git a/components/engine/integration-cli/docker_cli_cp_test.go b/components/engine/integration-cli/docker_cli_cp_test.go new file mode 100644 index 0000000000..b5a70a45ed --- /dev/null +++ b/components/engine/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 65987d4f804b352bad8ea80ae34d6872b951e417 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) Upstream-commit: 0fb507dc2328c5c364a2cd1701a155efb1767a1a Component: engine --- components/engine/daemon/container.go | 36 ++++++++++++++++----------- components/engine/daemon/volumes.go | 4 +-- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go index 7250b442a6..4f10cc4e93 100644 --- a/components/engine/daemon/container.go +++ b/components/engine/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/components/engine/daemon/volumes.go b/components/engine/daemon/volumes.go index ac01c6a982..eac743b2d9 100644 --- a/components/engine/daemon/volumes.go +++ b/components/engine/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 4926e35bb137001585ffa876937ef9c08d304ec2 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) Upstream-commit: adbe3096e8c8572925dbae5f19ac2ce2dc84fb1c Component: engine --- components/engine/daemon/container.go | 1 + components/engine/daemon/execdriver/driver.go | 7 ++++--- .../engine/daemon/execdriver/lxc/lxc_template.go | 3 +++ components/engine/daemon/execdriver/native/create.go | 1 + .../engine/docs/sources/reference/commandline/cli.md | 1 + .../engine/integration-cli/docker_cli_run_test.go | 11 +++++++++++ components/engine/runconfig/config.go | 8 +++++--- components/engine/runconfig/parse.go | 2 ++ 8 files changed, 28 insertions(+), 6 deletions(-) diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go index 7b6b65494e..b8743f4d08 100644 --- a/components/engine/daemon/container.go +++ b/components/engine/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/components/engine/daemon/execdriver/driver.go b/components/engine/daemon/execdriver/driver.go index 4837a398ea..a3e43e60ac 100644 --- a/components/engine/daemon/execdriver/driver.go +++ b/components/engine/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/components/engine/daemon/execdriver/lxc/lxc_template.go b/components/engine/daemon/execdriver/lxc/lxc_template.go index d40b316768..f2364cb77f 100644 --- a/components/engine/daemon/execdriver/lxc/lxc_template.go +++ b/components/engine/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/components/engine/daemon/execdriver/native/create.go b/components/engine/daemon/execdriver/native/create.go index a7b3d9a107..5adae30db5 100644 --- a/components/engine/daemon/execdriver/native/create.go +++ b/components/engine/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/components/engine/docs/sources/reference/commandline/cli.md b/components/engine/docs/sources/reference/commandline/cli.md index 8e0507cbf8..cfa0c284cf 100644 --- a/components/engine/docs/sources/reference/commandline/cli.md +++ b/components/engine/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/components/engine/integration-cli/docker_cli_run_test.go b/components/engine/integration-cli/docker_cli_run_test.go index b9737feeea..a439c5f387 100644 --- a/components/engine/integration-cli/docker_cli_run_test.go +++ b/components/engine/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/components/engine/runconfig/config.go b/components/engine/runconfig/config.go index 33a7882b6f..8a069c64c7 100644 --- a/components/engine/runconfig/config.go +++ b/components/engine/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/components/engine/runconfig/parse.go b/components/engine/runconfig/parse.go index 5a588c0344..8c4b9dcc14 100644 --- a/components/engine/runconfig/parse.go +++ b/components/engine/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 0291829e72e9a228138fc950e96f25a53cb3591c 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) Upstream-commit: 5128feb690e8fd0244d1fecef5f3f5f77598bbfa Component: engine --- .../engine/pkg/collections/orderedintset.go | 81 +++++++++---------- .../pkg/collections/orderedintset_test.go | 71 ++++++++++++++++ 2 files changed, 111 insertions(+), 41 deletions(-) create mode 100644 components/engine/pkg/collections/orderedintset_test.go diff --git a/components/engine/pkg/collections/orderedintset.go b/components/engine/pkg/collections/orderedintset.go index 23abab04d3..7442f2e93f 100644 --- a/components/engine/pkg/collections/orderedintset.go +++ b/components/engine/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/components/engine/pkg/collections/orderedintset_test.go b/components/engine/pkg/collections/orderedintset_test.go new file mode 100644 index 0000000000..0ac4ca5455 --- /dev/null +++ b/components/engine/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 3b48f8f4bc985e245251a0eccafebcb521ab5d7a 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) Upstream-commit: 80fca061e7661549a05b2696488db3fea008e2dd Component: engine --- .../networkdriver/ipallocator/allocator.go | 42 +++--- .../ipallocator/allocator_test.go | 125 +++++++++++++++--- 2 files changed, 124 insertions(+), 43 deletions(-) diff --git a/components/engine/daemon/networkdriver/ipallocator/allocator.go b/components/engine/daemon/networkdriver/ipallocator/allocator.go index 914df34942..4bcce65174 100644 --- a/components/engine/daemon/networkdriver/ipallocator/allocator.go +++ b/components/engine/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/components/engine/daemon/networkdriver/ipallocator/allocator_test.go b/components/engine/daemon/networkdriver/ipallocator/allocator_test.go index 5e9fcfc983..2b63d9a03c 100644 --- a/components/engine/daemon/networkdriver/ipallocator/allocator_test.go +++ b/components/engine/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 64ee7b470db32a5704eb70224b6dd1d6f481b14f 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) Upstream-commit: 830c2d7fa3f19a2fba50464273ae19cd2113e689 Component: engine --- .../pkg/libcontainer/nsinit/nsinit/main.go | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/components/engine/pkg/libcontainer/nsinit/nsinit/main.go b/components/engine/pkg/libcontainer/nsinit/nsinit/main.go index b5325d40b3..b076b5c55c 100644 --- a/components/engine/pkg/libcontainer/nsinit/nsinit/main.go +++ b/components/engine/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 2809f19cbb6364383ad832670ff6044d4cb11ee0 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) Upstream-commit: e963179c790ec49b28fae0d7ebc0d9d2b3ac0b72 Component: engine --- components/engine/daemon/daemon.go | 1 + components/engine/daemon/history.go | 3 +++ 2 files changed, 4 insertions(+) diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go index 38a107bba3..33ddb87ae4 100644 --- a/components/engine/daemon/daemon.go +++ b/components/engine/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/components/engine/daemon/history.go b/components/engine/daemon/history.go index 57a00a2090..0b125ad2b3 100644 --- a/components/engine/daemon/history.go +++ b/components/engine/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 bc13a475f858d274af99d0741b6d66fede16f078 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) Upstream-commit: 219b7ae8b526bb5e6d0e27176308db71438a002f Component: engine --- components/engine/utils/utils.go | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go index 7ffcc06b93..7788b20d54 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/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 36853439a848e987888b2f52f4f334662559727e 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) Upstream-commit: 5d5c89398c39e2f38459aae42189c9ca1125c1d3 Component: engine --- components/engine/daemon/daemon.go | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go index 38a107bba3..143683001d 100644 --- a/components/engine/daemon/daemon.go +++ b/components/engine/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 d94c5fdcb0743e5d9f5dd918c481ddabeb26773f 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) Upstream-commit: d153740d9c7d672b9433f173b9a098a5d3c14c53 Component: engine --- components/engine/utils/tarsum_test.go | 171 ++++++++++++++++++++++++- 1 file changed, 166 insertions(+), 5 deletions(-) diff --git a/components/engine/utils/tarsum_test.go b/components/engine/utils/tarsum_test.go index 1b2f0ba735..52ddd64590 100644 --- a/components/engine/utils/tarsum_test.go +++ b/components/engine/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 3cdf280b4528c15fc2ff6e0bfa0575d37bb4794e 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) Upstream-commit: a50a46048b8853ddfdac6f000ece86f95229dfea Component: engine --- components/engine/docs/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/engine/docs/README.md b/components/engine/docs/README.md index 71ce548003..fa3c501087 100755 --- a/components/engine/docs/README.md +++ b/components/engine/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 89b28579db8f4f0ed5e9c40a2318b2559f0d6620 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 Upstream-commit: 137f4b326ad1669cd562fa59d809f39d21b653fe Component: engine --- components/engine/docs/sources/examples/running_ssh_service.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/docs/sources/examples/running_ssh_service.md b/components/engine/docs/sources/examples/running_ssh_service.md index 864d10c726..fc7cd933b2 100644 --- a/components/engine/docs/sources/examples/running_ssh_service.md +++ b/components/engine/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 68f263f16021919d09ac82e18557d962c1e22a4d 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 Upstream-commit: 2d622df835f8c80b213a68bbceef21fb003a9a6a Component: engine --- components/engine/docs/sources/examples/running_ssh_service.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/docs/sources/examples/running_ssh_service.md b/components/engine/docs/sources/examples/running_ssh_service.md index 864d10c726..b2cb39280c 100644 --- a/components/engine/docs/sources/examples/running_ssh_service.md +++ b/components/engine/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 785f4eb4163d34d8d4db071960c644703ac5cc4a 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) Upstream-commit: 8699f53e6a033a7b5b55a2b6356da7c6e220d01f Component: engine --- components/engine/AUTHORS | 1 + components/engine/server/server.go | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/components/engine/AUTHORS b/components/engine/AUTHORS index b8c58ab09a..014748e187 100644 --- a/components/engine/AUTHORS +++ b/components/engine/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/components/engine/server/server.go b/components/engine/server/server.go index d82c5fe549..cab11bcf50 100644 --- a/components/engine/server/server.go +++ b/components/engine/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 799cb05f172dab4cc0f86459fd347db80b939281 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) Upstream-commit: f664e9a7d9a584220c28486f8e40cb1a1023e7f6 Component: engine --- components/engine/CONTRIBUTING.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/engine/CONTRIBUTING.md b/components/engine/CONTRIBUTING.md index d77afbc443..db75354c3b 100644 --- a/components/engine/CONTRIBUTING.md +++ b/components/engine/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 66a02eb50be43ba74e095fbd48a86b05cee1b290 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) Upstream-commit: 2bc34036b9106318f8564ee36b696ad070b02573 Component: engine --- .../engine/pkg/libcontainer/mount/init.go | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/components/engine/pkg/libcontainer/mount/init.go b/components/engine/pkg/libcontainer/mount/init.go index e5f2b43cab..6f3080cf08 100644 --- a/components/engine/pkg/libcontainer/mount/init.go +++ b/components/engine/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 4f88a0b2f23d44a7d13bb6326d4eb3fee2f266f9 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) Upstream-commit: 359f8aca29bf0e7fca757fa73e1d5565ed2d16f7 Component: engine --- components/engine/archive/changes.go | 31 +++++++++++++++++++--------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/components/engine/archive/changes.go b/components/engine/archive/changes.go index 723e4a7425..88cea0f709 100644 --- a/components/engine/archive/changes.go +++ b/components/engine/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 910f9d50e2ef27ed505753da47ab7255dc46a413 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) Upstream-commit: 3b7a19def609c8fbadc6559e7f47f8a5a7769a5b Component: engine --- components/engine/daemon/execdriver/lxc/driver.go | 2 +- components/engine/daemon/execdriver/native/driver.go | 2 +- .../daemon/execdriver/native/template/default_template.go | 2 +- .../engine/pkg/{ => libcontainer}/cgroups/MAINTAINERS | 0 components/engine/pkg/{ => libcontainer}/cgroups/cgroups.go | 0 .../engine/pkg/{ => libcontainer}/cgroups/cgroups_test.go | 0 .../engine/pkg/{ => libcontainer}/cgroups/fs/apply_raw.go | 2 +- .../engine/pkg/{ => libcontainer}/cgroups/fs/blkio.go | 2 +- .../engine/pkg/{ => libcontainer}/cgroups/fs/blkio_test.go | 0 components/engine/pkg/{ => libcontainer}/cgroups/fs/cpu.go | 0 .../engine/pkg/{ => libcontainer}/cgroups/fs/cpu_test.go | 0 .../engine/pkg/{ => libcontainer}/cgroups/fs/cpuacct.go | 2 +- .../engine/pkg/{ => libcontainer}/cgroups/fs/cpuset.go | 0 .../engine/pkg/{ => libcontainer}/cgroups/fs/devices.go | 0 .../engine/pkg/{ => libcontainer}/cgroups/fs/freezer.go | 2 +- .../engine/pkg/{ => libcontainer}/cgroups/fs/memory.go | 0 .../engine/pkg/{ => libcontainer}/cgroups/fs/memory_test.go | 0 .../engine/pkg/{ => libcontainer}/cgroups/fs/perf_event.go | 2 +- .../engine/pkg/{ => libcontainer}/cgroups/fs/test_util.go | 0 .../engine/pkg/{ => libcontainer}/cgroups/fs/utils.go | 0 .../engine/pkg/{ => libcontainer}/cgroups/fs/utils_test.go | 0 .../{ => libcontainer}/cgroups/systemd/apply_nosystemd.go | 3 ++- .../pkg/{ => libcontainer}/cgroups/systemd/apply_systemd.go | 2 +- components/engine/pkg/{ => libcontainer}/cgroups/utils.go | 0 components/engine/pkg/libcontainer/container.go | 2 +- components/engine/pkg/libcontainer/nsinit/exec.go | 6 +++--- components/engine/pkg/libcontainer/nsinit/unsupported.go | 2 +- components/engine/pkg/sysinfo/sysinfo.go | 3 ++- 28 files changed, 18 insertions(+), 16 deletions(-) rename components/engine/pkg/{ => libcontainer}/cgroups/MAINTAINERS (100%) rename components/engine/pkg/{ => libcontainer}/cgroups/cgroups.go (100%) rename components/engine/pkg/{ => libcontainer}/cgroups/cgroups_test.go (100%) rename components/engine/pkg/{ => libcontainer}/cgroups/fs/apply_raw.go (98%) rename components/engine/pkg/{ => libcontainer}/cgroups/fs/blkio.go (97%) rename components/engine/pkg/{ => libcontainer}/cgroups/fs/blkio_test.go (100%) rename components/engine/pkg/{ => libcontainer}/cgroups/fs/cpu.go (100%) rename components/engine/pkg/{ => libcontainer}/cgroups/fs/cpu_test.go (100%) rename components/engine/pkg/{ => libcontainer}/cgroups/fs/cpuacct.go (98%) rename components/engine/pkg/{ => libcontainer}/cgroups/fs/cpuset.go (100%) rename components/engine/pkg/{ => libcontainer}/cgroups/fs/devices.go (100%) rename components/engine/pkg/{ => libcontainer}/cgroups/fs/freezer.go (95%) rename components/engine/pkg/{ => libcontainer}/cgroups/fs/memory.go (100%) rename components/engine/pkg/{ => libcontainer}/cgroups/fs/memory_test.go (100%) rename components/engine/pkg/{ => libcontainer}/cgroups/fs/perf_event.go (89%) rename components/engine/pkg/{ => libcontainer}/cgroups/fs/test_util.go (100%) rename components/engine/pkg/{ => libcontainer}/cgroups/fs/utils.go (100%) rename components/engine/pkg/{ => libcontainer}/cgroups/fs/utils_test.go (100%) rename components/engine/pkg/{ => libcontainer}/cgroups/systemd/apply_nosystemd.go (78%) rename components/engine/pkg/{ => libcontainer}/cgroups/systemd/apply_systemd.go (99%) rename components/engine/pkg/{ => libcontainer}/cgroups/utils.go (100%) diff --git a/components/engine/daemon/execdriver/lxc/driver.go b/components/engine/daemon/execdriver/lxc/driver.go index 0b98176213..6b2b2cc46b 100644 --- a/components/engine/daemon/execdriver/lxc/driver.go +++ b/components/engine/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/components/engine/daemon/execdriver/native/driver.go b/components/engine/daemon/execdriver/native/driver.go index 2e57729d4b..59d193ed00 100644 --- a/components/engine/daemon/execdriver/native/driver.go +++ b/components/engine/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/components/engine/daemon/execdriver/native/template/default_template.go b/components/engine/daemon/execdriver/native/template/default_template.go index 249c5d5fe8..03b76e0f58 100644 --- a/components/engine/daemon/execdriver/native/template/default_template.go +++ b/components/engine/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/components/engine/pkg/cgroups/MAINTAINERS b/components/engine/pkg/libcontainer/cgroups/MAINTAINERS similarity index 100% rename from components/engine/pkg/cgroups/MAINTAINERS rename to components/engine/pkg/libcontainer/cgroups/MAINTAINERS diff --git a/components/engine/pkg/cgroups/cgroups.go b/components/engine/pkg/libcontainer/cgroups/cgroups.go similarity index 100% rename from components/engine/pkg/cgroups/cgroups.go rename to components/engine/pkg/libcontainer/cgroups/cgroups.go diff --git a/components/engine/pkg/cgroups/cgroups_test.go b/components/engine/pkg/libcontainer/cgroups/cgroups_test.go similarity index 100% rename from components/engine/pkg/cgroups/cgroups_test.go rename to components/engine/pkg/libcontainer/cgroups/cgroups_test.go diff --git a/components/engine/pkg/cgroups/fs/apply_raw.go b/components/engine/pkg/libcontainer/cgroups/fs/apply_raw.go similarity index 98% rename from components/engine/pkg/cgroups/fs/apply_raw.go rename to components/engine/pkg/libcontainer/cgroups/fs/apply_raw.go index ee26bffac5..65aabcc52b 100644 --- a/components/engine/pkg/cgroups/fs/apply_raw.go +++ b/components/engine/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/components/engine/pkg/cgroups/fs/blkio.go b/components/engine/pkg/libcontainer/cgroups/fs/blkio.go similarity index 97% rename from components/engine/pkg/cgroups/fs/blkio.go rename to components/engine/pkg/libcontainer/cgroups/fs/blkio.go index 79e14fa2dc..213d250d2b 100644 --- a/components/engine/pkg/cgroups/fs/blkio.go +++ b/components/engine/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/components/engine/pkg/cgroups/fs/blkio_test.go b/components/engine/pkg/libcontainer/cgroups/fs/blkio_test.go similarity index 100% rename from components/engine/pkg/cgroups/fs/blkio_test.go rename to components/engine/pkg/libcontainer/cgroups/fs/blkio_test.go diff --git a/components/engine/pkg/cgroups/fs/cpu.go b/components/engine/pkg/libcontainer/cgroups/fs/cpu.go similarity index 100% rename from components/engine/pkg/cgroups/fs/cpu.go rename to components/engine/pkg/libcontainer/cgroups/fs/cpu.go diff --git a/components/engine/pkg/cgroups/fs/cpu_test.go b/components/engine/pkg/libcontainer/cgroups/fs/cpu_test.go similarity index 100% rename from components/engine/pkg/cgroups/fs/cpu_test.go rename to components/engine/pkg/libcontainer/cgroups/fs/cpu_test.go diff --git a/components/engine/pkg/cgroups/fs/cpuacct.go b/components/engine/pkg/libcontainer/cgroups/fs/cpuacct.go similarity index 98% rename from components/engine/pkg/cgroups/fs/cpuacct.go rename to components/engine/pkg/libcontainer/cgroups/fs/cpuacct.go index 892b5ab6b1..3382440d2d 100644 --- a/components/engine/pkg/cgroups/fs/cpuacct.go +++ b/components/engine/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/components/engine/pkg/cgroups/fs/cpuset.go b/components/engine/pkg/libcontainer/cgroups/fs/cpuset.go similarity index 100% rename from components/engine/pkg/cgroups/fs/cpuset.go rename to components/engine/pkg/libcontainer/cgroups/fs/cpuset.go diff --git a/components/engine/pkg/cgroups/fs/devices.go b/components/engine/pkg/libcontainer/cgroups/fs/devices.go similarity index 100% rename from components/engine/pkg/cgroups/fs/devices.go rename to components/engine/pkg/libcontainer/cgroups/fs/devices.go diff --git a/components/engine/pkg/cgroups/fs/freezer.go b/components/engine/pkg/libcontainer/cgroups/fs/freezer.go similarity index 95% rename from components/engine/pkg/cgroups/fs/freezer.go rename to components/engine/pkg/libcontainer/cgroups/fs/freezer.go index 70cfcdde72..f3b1985a51 100644 --- a/components/engine/pkg/cgroups/fs/freezer.go +++ b/components/engine/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/components/engine/pkg/cgroups/fs/memory.go b/components/engine/pkg/libcontainer/cgroups/fs/memory.go similarity index 100% rename from components/engine/pkg/cgroups/fs/memory.go rename to components/engine/pkg/libcontainer/cgroups/fs/memory.go diff --git a/components/engine/pkg/cgroups/fs/memory_test.go b/components/engine/pkg/libcontainer/cgroups/fs/memory_test.go similarity index 100% rename from components/engine/pkg/cgroups/fs/memory_test.go rename to components/engine/pkg/libcontainer/cgroups/fs/memory_test.go diff --git a/components/engine/pkg/cgroups/fs/perf_event.go b/components/engine/pkg/libcontainer/cgroups/fs/perf_event.go similarity index 89% rename from components/engine/pkg/cgroups/fs/perf_event.go rename to components/engine/pkg/libcontainer/cgroups/fs/perf_event.go index 789b3e59ad..063cec4b04 100644 --- a/components/engine/pkg/cgroups/fs/perf_event.go +++ b/components/engine/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/components/engine/pkg/cgroups/fs/test_util.go b/components/engine/pkg/libcontainer/cgroups/fs/test_util.go similarity index 100% rename from components/engine/pkg/cgroups/fs/test_util.go rename to components/engine/pkg/libcontainer/cgroups/fs/test_util.go diff --git a/components/engine/pkg/cgroups/fs/utils.go b/components/engine/pkg/libcontainer/cgroups/fs/utils.go similarity index 100% rename from components/engine/pkg/cgroups/fs/utils.go rename to components/engine/pkg/libcontainer/cgroups/fs/utils.go diff --git a/components/engine/pkg/cgroups/fs/utils_test.go b/components/engine/pkg/libcontainer/cgroups/fs/utils_test.go similarity index 100% rename from components/engine/pkg/cgroups/fs/utils_test.go rename to components/engine/pkg/libcontainer/cgroups/fs/utils_test.go diff --git a/components/engine/pkg/cgroups/systemd/apply_nosystemd.go b/components/engine/pkg/libcontainer/cgroups/systemd/apply_nosystemd.go similarity index 78% rename from components/engine/pkg/cgroups/systemd/apply_nosystemd.go rename to components/engine/pkg/libcontainer/cgroups/systemd/apply_nosystemd.go index 4faa749745..302fab773b 100644 --- a/components/engine/pkg/cgroups/systemd/apply_nosystemd.go +++ b/components/engine/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/components/engine/pkg/cgroups/systemd/apply_systemd.go b/components/engine/pkg/libcontainer/cgroups/systemd/apply_systemd.go similarity index 99% rename from components/engine/pkg/cgroups/systemd/apply_systemd.go rename to components/engine/pkg/libcontainer/cgroups/systemd/apply_systemd.go index c4b0937b63..52940f6fae 100644 --- a/components/engine/pkg/cgroups/systemd/apply_systemd.go +++ b/components/engine/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/components/engine/pkg/cgroups/utils.go b/components/engine/pkg/libcontainer/cgroups/utils.go similarity index 100% rename from components/engine/pkg/cgroups/utils.go rename to components/engine/pkg/libcontainer/cgroups/utils.go diff --git a/components/engine/pkg/libcontainer/container.go b/components/engine/pkg/libcontainer/container.go index 5acdff3d29..7328f5544e 100644 --- a/components/engine/pkg/libcontainer/container.go +++ b/components/engine/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/components/engine/pkg/libcontainer/nsinit/exec.go b/components/engine/pkg/libcontainer/nsinit/exec.go index 5d0d772a0f..03e0f4b9aa 100644 --- a/components/engine/pkg/libcontainer/nsinit/exec.go +++ b/components/engine/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/components/engine/pkg/libcontainer/nsinit/unsupported.go b/components/engine/pkg/libcontainer/nsinit/unsupported.go index 929b3dba5b..51509f79a2 100644 --- a/components/engine/pkg/libcontainer/nsinit/unsupported.go +++ b/components/engine/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/components/engine/pkg/sysinfo/sysinfo.go b/components/engine/pkg/sysinfo/sysinfo.go index 27af37bb89..bdc192b913 100644 --- a/components/engine/pkg/sysinfo/sysinfo.go +++ b/components/engine/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 1aafe01162ce6f679ddc9df615c1e1d5d6d6d9a5 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) Upstream-commit: b22d10e3c541c46cebe7da44fd2f521c4bc653f4 Component: engine --- components/engine/pkg/libcontainer/cgroups/MAINTAINERS | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 components/engine/pkg/libcontainer/cgroups/MAINTAINERS diff --git a/components/engine/pkg/libcontainer/cgroups/MAINTAINERS b/components/engine/pkg/libcontainer/cgroups/MAINTAINERS deleted file mode 100644 index 30cb8bba27..0000000000 --- a/components/engine/pkg/libcontainer/cgroups/MAINTAINERS +++ /dev/null @@ -1,3 +0,0 @@ -Michael Crosby (@crosbymichael) -Rohit Jnagal (@rjnagal) -Victor Marmol (@vmarmol) From 82867e529b1e3fe1cded2d399784f3c2f010b88e 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) Upstream-commit: 2269472f3ad09de59ba15185b9558b91f1ecb0b5 Component: engine --- .../sources/use/ambassador_pattern_linking.md | 75 +++++++-------- components/engine/docs/sources/use/basics.md | 57 +++++------ components/engine/docs/sources/use/chef.md | 6 +- .../docs/sources/use/host_integration.md | 18 ++-- .../engine/docs/sources/use/networking.md | 49 +++++----- .../docs/sources/use/port_redirection.md | 55 ++++++----- components/engine/docs/sources/use/puppet.md | 11 ++- .../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/components/engine/docs/sources/use/ambassador_pattern_linking.md b/components/engine/docs/sources/use/ambassador_pattern_linking.md index 2bdd434f6e..01962f19a9 100644 --- a/components/engine/docs/sources/use/ambassador_pattern_linking.md +++ b/components/engine/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/components/engine/docs/sources/use/basics.md b/components/engine/docs/sources/use/basics.md index ee3eeabd9d..e4422034d4 100644 --- a/components/engine/docs/sources/use/basics.md +++ b/components/engine/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/components/engine/docs/sources/use/chef.md b/components/engine/docs/sources/use/chef.md index 897c2b429a..5568e99afa 100644 --- a/components/engine/docs/sources/use/chef.md +++ b/components/engine/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/components/engine/docs/sources/use/host_integration.md b/components/engine/docs/sources/use/host_integration.md index 370c00e20a..fa442620e5 100644 --- a/components/engine/docs/sources/use/host_integration.md +++ b/components/engine/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/components/engine/docs/sources/use/networking.md b/components/engine/docs/sources/use/networking.md index 00d0684256..db60fe843d 100644 --- a/components/engine/docs/sources/use/networking.md +++ b/components/engine/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/components/engine/docs/sources/use/port_redirection.md b/components/engine/docs/sources/use/port_redirection.md index 19de4141ba..315ef2650d 100644 --- a/components/engine/docs/sources/use/port_redirection.md +++ b/components/engine/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/components/engine/docs/sources/use/puppet.md b/components/engine/docs/sources/use/puppet.md index a0d20ab446..81ae05ba56 100644 --- a/components/engine/docs/sources/use/puppet.md +++ b/components/engine/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/components/engine/docs/sources/use/working_with_links_names.md b/components/engine/docs/sources/use/working_with_links_names.md index 6951e3c26f..ea3a25ab75 100644 --- a/components/engine/docs/sources/use/working_with_links_names.md +++ b/components/engine/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/components/engine/docs/sources/use/working_with_volumes.md b/components/engine/docs/sources/use/working_with_volumes.md index 065b867c68..f6d1db04fa 100644 --- a/components/engine/docs/sources/use/working_with_volumes.md +++ b/components/engine/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/components/engine/docs/sources/use/workingwithrepository.md b/components/engine/docs/sources/use/workingwithrepository.md index d92d057d58..88c3f6c44e 100644 --- a/components/engine/docs/sources/use/workingwithrepository.md +++ b/components/engine/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 d3d33862048c4d769e23c682c8421eaf1d6acea0 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) Upstream-commit: ed703fc8be652eee65319a774ffbe95a07a03242 Component: engine --- .../engine/docs/sources/reference/commandline/cli.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/components/engine/docs/sources/reference/commandline/cli.md b/components/engine/docs/sources/reference/commandline/cli.md index 8e0507cbf8..c928b03eba 100644 --- a/components/engine/docs/sources/reference/commandline/cli.md +++ b/components/engine/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 ebf36b41a1f09fd8dcf0db449deccc080b924b4f 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) Upstream-commit: 002aa8fc207d803349777cde61426603976ca8ee Component: engine --- components/engine/pkg/system/calls_linux.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/components/engine/pkg/system/calls_linux.go b/components/engine/pkg/system/calls_linux.go index cc4727aaa2..faead0114e 100644 --- a/components/engine/pkg/system/calls_linux.go +++ b/components/engine/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 f03889033203cb5debbd05fa5b3c65ef6bdcacb7 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) Upstream-commit: 239d1ef301eb8d9b2b1e8469489b25ff3708f47f Component: engine --- components/engine/docs/sources/reference/builder.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/components/engine/docs/sources/reference/builder.md b/components/engine/docs/sources/reference/builder.md index 4c726aafa2..c3ba939550 100644 --- a/components/engine/docs/sources/reference/builder.md +++ b/components/engine/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 4cab943cdb20cc6a63c67406933675551776b758 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 Upstream-commit: 10a320818e875547c83fecea32cd73231248acf8 Component: engine --- components/engine/docs/sources/reference/run.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/docs/sources/reference/run.md b/components/engine/docs/sources/reference/run.md index a0be7c34db..7587c70ba6 100644 --- a/components/engine/docs/sources/reference/run.md +++ b/components/engine/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 e8ae384f461167fcbb62bdb32163eb83f4f9bc47 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) Upstream-commit: 77c2b76e44f77eee2ca89ff551ac64f8c6b8bd23 Component: engine --- components/engine/archive/archive.go | 38 ++++++++++++---------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/components/engine/archive/archive.go b/components/engine/archive/archive.go index e21b10ca0c..078aa6d354 100644 --- a/components/engine/archive/archive.go +++ b/components/engine/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 3bd2cdddb890fa4aabb7d8abc042d630d082724b 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) Upstream-commit: 8ec01f9dc1d18c532f60f1bfb6c0ab5eea378b3c Component: engine --- components/engine/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/Makefile b/components/engine/Makefile index a4c8658e08..a8e4dc5ca1 100644 --- a/components/engine/Makefile +++ b/components/engine/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 f5e7688aff2ef03a83683c0fbe5be0d3fcb20aef 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) Upstream-commit: 094926206bc98718f2eac95849e57439155d7c6c Component: engine --- components/engine/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 components/engine/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/sparse-formats.tar create mode 100644 components/engine/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big-long.tar diff --git a/components/engine/hack/vendor.sh b/components/engine/hack/vendor.sh index 79322cd9af..0bec5838f7 100755 --- a/components/engine/hack/vendor.sh +++ b/components/engine/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/components/engine/vendor/src/code.google.com/p/go/src/pkg/archive/tar/common.go b/components/engine/vendor/src/code.google.com/p/go/src/pkg/archive/tar/common.go index e8b973c1fa..e363aa793e 100644 --- a/components/engine/vendor/src/code.google.com/p/go/src/pkg/archive/tar/common.go +++ b/components/engine/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/components/engine/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader.go b/components/engine/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader.go index 7cb6e649c7..920a9b08f9 100644 --- a/components/engine/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader.go +++ b/components/engine/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/components/engine/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader_test.go b/components/engine/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader_test.go index f84dbebe98..9601ffe459 100644 --- a/components/engine/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader_test.go +++ b/components/engine/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/components/engine/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/sparse-formats.tar b/components/engine/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/components/engine/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big-long.tar b/components/engine/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/components/engine/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer_test.go b/components/engine/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer_test.go index 2b9ea658db..512fab1a6f 100644 --- a/components/engine/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer_test.go +++ b/components/engine/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 30441260af3d39ddef02dbbfc17b5fab0b4d7bc1 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) Upstream-commit: aedcb76adc2b2468565b311b67f4079a128b37cd Component: engine --- components/engine/archive/archive_test.go | 29 ++++++++++++++++++ components/engine/archive/testdata/broken.tar | Bin 0 -> 13824 bytes 2 files changed, 29 insertions(+) create mode 100644 components/engine/archive/testdata/broken.tar diff --git a/components/engine/archive/archive_test.go b/components/engine/archive/archive_test.go index 412660139c..e959a2b073 100644 --- a/components/engine/archive/archive_test.go +++ b/components/engine/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/components/engine/archive/testdata/broken.tar b/components/engine/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 28ce705ffc49945f24b267e724d5065f90864678 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) Upstream-commit: 8b77a5b7aedb1168707f486ed540edf3e5de8819 Component: engine --- components/engine/pkg/symlink/fs_test.go | 13 +++++++++++++ components/engine/pkg/symlink/testdata/fs/i | 1 + 2 files changed, 14 insertions(+) create mode 120000 components/engine/pkg/symlink/testdata/fs/i diff --git a/components/engine/pkg/symlink/fs_test.go b/components/engine/pkg/symlink/fs_test.go index 1f12aa3a60..d85fd6da74 100644 --- a/components/engine/pkg/symlink/fs_test.go +++ b/components/engine/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/components/engine/pkg/symlink/testdata/fs/i b/components/engine/pkg/symlink/testdata/fs/i new file mode 120000 index 0000000000..2e65efe2a1 --- /dev/null +++ b/components/engine/pkg/symlink/testdata/fs/i @@ -0,0 +1 @@ +a \ No newline at end of file From 1092af80f9fd9c01b5b0f311f33595cba7680b1e 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) Upstream-commit: b51c366bfc963687b8cc14df614a2fc10bad6306 Component: engine --- components/engine/pkg/symlink/fs.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/components/engine/pkg/symlink/fs.go b/components/engine/pkg/symlink/fs.go index e91d33db4b..4dcfdf360f 100644 --- a/components/engine/pkg/symlink/fs.go +++ b/components/engine/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 dba2ddd06851937b8bb06c72b5f2993020c8179b 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) Upstream-commit: ad35d522dbfac124225e27f58bf07c61a34d78b5 Component: engine --- components/engine/AUTHORS | 1 + components/engine/pkg/symlink/fs.go | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/engine/AUTHORS b/components/engine/AUTHORS index 014748e187..8f75ab85b3 100644 --- a/components/engine/AUTHORS +++ b/components/engine/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/components/engine/pkg/symlink/fs.go b/components/engine/pkg/symlink/fs.go index 4dcfdf360f..257491f91b 100644 --- a/components/engine/pkg/symlink/fs.go +++ b/components/engine/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 e1b6815b39e40e1fb50c35cb07bbbfff8e720809 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) Upstream-commit: 81ffd6530aced5237c01bd13e80686da68de6637 Component: engine --- components/engine/api/server/server.go | 2 +- components/engine/server/server.go | 12 ++++++------ components/engine/server/server_unit_test.go | 7 ++++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/components/engine/api/server/server.go b/components/engine/api/server/server.go index e2a7f651a8..9bf6da3fe2 100644 --- a/components/engine/api/server/server.go +++ b/components/engine/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/components/engine/server/server.go b/components/engine/server/server.go index cab11bcf50..49fb00ce54 100644 --- a/components/engine/server/server.go +++ b/components/engine/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/components/engine/server/server_unit_test.go b/components/engine/server/server_unit_test.go index b471c5c581..47e4be8280 100644 --- a/components/engine/server/server_unit_test.go +++ b/components/engine/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 98235fb032e2d2e43a4f9f3e79f7a0784436c9ed 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) Upstream-commit: d35330bb7aa8473b92f6f45718aa6c38863cccc7 Component: engine --- .../docs/sources/reference/commandline/cli.md | 341 +++++++++--------- 1 file changed, 174 insertions(+), 167 deletions(-) diff --git a/components/engine/docs/sources/reference/commandline/cli.md b/components/engine/docs/sources/reference/commandline/cli.md index fcc2862b3d..ec2ebf9a16 100644 --- a/components/engine/docs/sources/reference/commandline/cli.md +++ b/components/engine/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 48bd5989f0a8470db33b1745e3d9a45b4a75daaf 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) Upstream-commit: 9d6875d19d3926faf6287487234ad0b2f1310e9d Component: engine --- .../native/template/default_template.go | 5 +++ .../security/capabilities/capabilities.go | 38 ++++++++++--------- components/engine/pkg/libcontainer/types.go | 5 +++ 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/components/engine/daemon/execdriver/native/template/default_template.go b/components/engine/daemon/execdriver/native/template/default_template.go index 249c5d5fe8..9f2dd17a33 100644 --- a/components/engine/daemon/execdriver/native/template/default_template.go +++ b/components/engine/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/components/engine/pkg/libcontainer/security/capabilities/capabilities.go b/components/engine/pkg/libcontainer/security/capabilities/capabilities.go index ad13e672c7..107417ad7d 100644 --- a/components/engine/pkg/libcontainer/security/capabilities/capabilities.go +++ b/components/engine/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/components/engine/pkg/libcontainer/types.go b/components/engine/pkg/libcontainer/types.go index 8f056c817d..4c8f60c477 100644 --- a/components/engine/pkg/libcontainer/types.go +++ b/components/engine/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 811e26ec974fe48c1cdb2bf59a94a6ec27ac0841 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) Upstream-commit: ddb99054bc9c04cce4ca761f9f0d822d67d614c4 Component: engine --- .../engine/docs/sources/reference/commandline/cli.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/engine/docs/sources/reference/commandline/cli.md b/components/engine/docs/sources/reference/commandline/cli.md index fcc2862b3d..513560b11f 100644 --- a/components/engine/docs/sources/reference/commandline/cli.md +++ b/components/engine/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 191900baf700ccdee979a3a40c22619eff7652bd 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 Upstream-commit: 1b4915c1a8ba4724edd4e7864e85ff4fb795d04f Component: engine --- components/engine/docs/sources/installation/ubuntulinux.md | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/docs/sources/installation/ubuntulinux.md b/components/engine/docs/sources/installation/ubuntulinux.md index d40e17b646..add4fb180f 100644 --- a/components/engine/docs/sources/installation/ubuntulinux.md +++ b/components/engine/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 130a91328f09a4d7612250bf4ab5ee175c2912c2 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) Upstream-commit: bd6fd25dfb4cf7aae0e5e382e3bf10c9b2caa5fb Component: engine --- .../graphdriver/devmapper/devmapper_test.go | 286 +----- .../graphdriver/devmapper/driver_test.go | 939 ------------------ 2 files changed, 18 insertions(+), 1207 deletions(-) delete mode 100644 components/engine/daemon/graphdriver/devmapper/driver_test.go diff --git a/components/engine/daemon/graphdriver/devmapper/devmapper_test.go b/components/engine/daemon/graphdriver/devmapper/devmapper_test.go index 3ffa163ceb..7c97d6bb04 100644 --- a/components/engine/daemon/graphdriver/devmapper/devmapper_test.go +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/driver_test.go b/components/engine/daemon/graphdriver/devmapper/driver_test.go deleted file mode 100644 index 0602f123a0..0000000000 --- a/components/engine/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 2ef34809388527726ac12a49c8e94b7b2d4f3567 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) Upstream-commit: 408f050d648da0b64d353146a1be59827d76bf70 Component: engine --- components/engine/daemon/execdriver/execdrivers/execdrivers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/daemon/execdriver/execdrivers/execdrivers.go b/components/engine/daemon/execdriver/execdrivers/execdrivers.go index 18db1f8026..2e18454a09 100644 --- a/components/engine/daemon/execdriver/execdrivers/execdrivers.go +++ b/components/engine/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 da3598172a36e7b2d0c2978b05dd0910f0ccf0fa 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) Upstream-commit: 00e1adfeada87100e5e88707309bcdcd674082d6 Component: engine --- .../engine/pkg/libcontainer/nsinit/init.go | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/components/engine/pkg/libcontainer/nsinit/init.go b/components/engine/pkg/libcontainer/nsinit/init.go index 3bbcfcc654..85ca08c798 100644 --- a/components/engine/pkg/libcontainer/nsinit/init.go +++ b/components/engine/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 0b78fad0c65120fda32f706a0bcaff12e0aa0fc1 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) Upstream-commit: 6a1d76bc7bc589b53530c03720022f0095b65d55 Component: engine --- components/engine/pkg/libcontainer/nsinit/exec.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/pkg/libcontainer/nsinit/exec.go b/components/engine/pkg/libcontainer/nsinit/exec.go index 03e0f4b9aa..fbc7512047 100644 --- a/components/engine/pkg/libcontainer/nsinit/exec.go +++ b/components/engine/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 e3a28edbe268d9bf4248ad69abdae226384301bd 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) Upstream-commit: 39d244a593aad63be58d8b2e452715e25135f255 Component: engine --- .../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 components/engine/daemon/graphdriver/devmapper/sys.go diff --git a/components/engine/daemon/graphdriver/devmapper/attach_loopback.go b/components/engine/daemon/graphdriver/devmapper/attach_loopback.go index 23339076e8..d2ab8c3a4b 100644 --- a/components/engine/daemon/graphdriver/devmapper/attach_loopback.go +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/deviceset.go b/components/engine/daemon/graphdriver/devmapper/deviceset.go index 79563c108e..744f1e11b9 100644 --- a/components/engine/daemon/graphdriver/devmapper/deviceset.go +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/devmapper.go b/components/engine/daemon/graphdriver/devmapper/devmapper.go index 86af653b4d..315f0c0db5 100644 --- a/components/engine/daemon/graphdriver/devmapper/devmapper.go +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/driver.go b/components/engine/daemon/graphdriver/devmapper/driver.go index 9f240d96e0..609971cda1 100644 --- a/components/engine/daemon/graphdriver/devmapper/driver.go +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/ioctl.go b/components/engine/daemon/graphdriver/devmapper/ioctl.go index 30bafff943..8f403da2b0 100644 --- a/components/engine/daemon/graphdriver/devmapper/ioctl.go +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/mount.go b/components/engine/daemon/graphdriver/devmapper/mount.go index 4f19109bf8..6de9e46c8c 100644 --- a/components/engine/daemon/graphdriver/devmapper/mount.go +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/sys.go b/components/engine/daemon/graphdriver/devmapper/sys.go deleted file mode 100644 index 5a9ab4d74b..0000000000 --- a/components/engine/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 3e603cbd495d6f91f3571372200042cdc84a828b 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) Upstream-commit: b8e338144e90a6bb76110ab04cc216966640a3f4 Component: engine --- components/engine/api/client/commands.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/engine/api/client/commands.go b/components/engine/api/client/commands.go index f60b6cebd7..502c1be69e 100644 --- a/components/engine/api/client/commands.go +++ b/components/engine/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 cdfc80841ab679dc141e48d42d8cbfc3121993df 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) Upstream-commit: a598dba1346c9f2e878e8250a9d641a2206cb4e5 Component: engine --- components/engine/contrib/init/sysvinit-redhat/docker | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/engine/contrib/init/sysvinit-redhat/docker b/components/engine/contrib/init/sysvinit-redhat/docker index 2b75c6903f..7d056aacad 100755 --- a/components/engine/contrib/init/sysvinit-redhat/docker +++ b/components/engine/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 d8d2d008c4985e2f28b56108e92c5be0d318b3cf 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) Upstream-commit: 1ee423bd5dd7abf6f5f4662132b77fdebf14529a Component: engine --- components/engine/contrib/init/sysvinit-redhat/docker | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/components/engine/contrib/init/sysvinit-redhat/docker b/components/engine/contrib/init/sysvinit-redhat/docker index 7d056aacad..06699f6ab1 100755 --- a/components/engine/contrib/init/sysvinit-redhat/docker +++ b/components/engine/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 cdfe67b94e8584a11d60c6dd45ccd83f54c3f0cf 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) Upstream-commit: ba0c8292917560b45f840f187c2a8f452550705d Component: engine --- components/engine/contrib/init/upstart/docker.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/contrib/init/upstart/docker.conf b/components/engine/contrib/init/upstart/docker.conf index e27d77e145..db00e4f47f 100644 --- a/components/engine/contrib/init/upstart/docker.conf +++ b/components/engine/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 c6f81b2d5f3f8760fdebb0d06bd82b0c2cdf99aa 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) Upstream-commit: 8b31824ad6118ed505b43443816c1492e3d81c8c Component: engine --- components/engine/server/buildfile.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/server/buildfile.go b/components/engine/server/buildfile.go index faaac0d3d4..9387a87877 100644 --- a/components/engine/server/buildfile.go +++ b/components/engine/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 919aaa7d954a516d981643a1491113fed6b0942a 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) Upstream-commit: d787f2731e4242f244e88f047032ad9650f1f8d7 Component: engine --- components/engine/pkg/libcontainer/nsinit/init.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/engine/pkg/libcontainer/nsinit/init.go b/components/engine/pkg/libcontainer/nsinit/init.go index 3bbcfcc654..af60d739ca 100644 --- a/components/engine/pkg/libcontainer/nsinit/init.go +++ b/components/engine/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 09681d4edce47c8c7e3811315262cb8b2b71a659 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) Upstream-commit: 2e0d47e5c4c63323623c38cf0ca8d63efab8b493 Component: engine --- .../engine/contrib/man/md/docker-run.1.md | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/components/engine/contrib/man/md/docker-run.1.md b/components/engine/contrib/man/md/docker-run.1.md index 56364f9d5f..3cf56c24bc 100644 --- a/components/engine/contrib/man/md/docker-run.1.md +++ b/components/engine/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 3f955b3caca71b53249ed44dafce0f61fbe0242c 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) Upstream-commit: 311614952ea64315075d827c880debbdfb503640 Component: engine --- components/engine/archive/archive.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/components/engine/archive/archive.go b/components/engine/archive/archive.go index e21b10ca0c..a22da4ee57 100644 --- a/components/engine/archive/archive.go +++ b/components/engine/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 78ed1c0704416adc90fb9dc5704261b7bfde16c0 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) Upstream-commit: a42e451e10bc7383c82933eb4073e70aaf5aaa8f Component: engine --- components/engine/graph/graph.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/engine/graph/graph.go b/components/engine/graph/graph.go index 5de9cbe7a1..edca42f42b 100644 --- a/components/engine/graph/graph.go +++ b/components/engine/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 a03989227c380e7c4495eb84002f8e1eaa06e984 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) Upstream-commit: 366f084eb0864db6dcf5cc47e434b3c6f43500d7 Component: engine --- .../engine/integration/commands_test.go | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/components/engine/integration/commands_test.go b/components/engine/integration/commands_test.go index 5b967b68cc..7164cc5af7 100644 --- a/components/engine/integration/commands_test.go +++ b/components/engine/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 8003cf19d2089d8d3a4c850f9394ec5080df5245 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) Upstream-commit: 2c8b63cb754128a64083426e5c3e6016118e78df Component: engine --- .../integration-cli/docker_cli_commit_test.go | 21 +++++++++++ .../engine/integration/commands_test.go | 37 ++++--------------- components/engine/runconfig/merge.go | 9 ----- 3 files changed, 28 insertions(+), 39 deletions(-) diff --git a/components/engine/integration-cli/docker_cli_commit_test.go b/components/engine/integration-cli/docker_cli_commit_test.go index e99379231e..7635add913 100644 --- a/components/engine/integration-cli/docker_cli_commit_test.go +++ b/components/engine/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/components/engine/integration/commands_test.go b/components/engine/integration/commands_test.go index 7164cc5af7..b91ba603a7 100644 --- a/components/engine/integration/commands_test.go +++ b/components/engine/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/components/engine/runconfig/merge.go b/components/engine/runconfig/merge.go index 1240dbcacd..252a2fe434 100644 --- a/components/engine/runconfig/merge.go +++ b/components/engine/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 c1c5dca31cad917756d0f4da8f2fc3831305cd6a 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) Upstream-commit: 3118952e0a63b42b830fc846a56b891d14902976 Component: engine --- components/engine/archive/archive.go | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/components/engine/archive/archive.go b/components/engine/archive/archive.go index 5d29ce8193..c72849097b 100644 --- a/components/engine/archive/archive.go +++ b/components/engine/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 1976c1713ab1680217fbf6a1567719ac584a1c9c 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) Upstream-commit: 67ce59a806007cd28d96fd11208c4a113d1b92e0 Component: engine --- components/engine/archive/archive.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/engine/archive/archive.go b/components/engine/archive/archive.go index c72849097b..1eda6c2de9 100644 --- a/components/engine/archive/archive.go +++ b/components/engine/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 25b0a0131ed255a034ed1c7c554db6251f7cc05c 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) Upstream-commit: 4a3aefbb5232433098c9d27423a10031eb17ff93 Component: engine --- components/engine/archive/archive.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/archive/archive.go b/components/engine/archive/archive.go index 1eda6c2de9..160ea0737e 100644 --- a/components/engine/archive/archive.go +++ b/components/engine/archive/archive.go @@ -2,6 +2,7 @@ package archive import ( "bufio" + "bytes" "compress/bzip2" "compress/gzip" "errors" From 9195f7ed61fe056e65a541a29fe935350f2b6308 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) Upstream-commit: 46578a2359714e32d61af4efaeea86b7ff770359 Component: engine --- components/engine/integration-cli/docker_test_vars.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/components/engine/integration-cli/docker_test_vars.go b/components/engine/integration-cli/docker_test_vars.go index f8bd5c116b..78e0685a9d 100644 --- a/components/engine/integration-cli/docker_test_vars.go +++ b/components/engine/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 e59443d033192d016633193b78b5700177db1c83 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) Upstream-commit: 1dedcd0d376f57abae5cadd38116c1aca2821330 Component: engine --- components/engine/utils/utils.go | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go index 191c85206e..c3ae4e569d 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/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 237489fbeb8e263849d8b40a34faab48a84a9843 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) Upstream-commit: 33d5b38d62f48edcadbe239595e242300f3ecd27 Component: engine --- components/engine/api/client/commands.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/engine/api/client/commands.go b/components/engine/api/client/commands.go index 502c1be69e..f7eca7d358 100644 --- a/components/engine/api/client/commands.go +++ b/components/engine/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 d17f84162575c6345eeb40bb70e2f2e54ab294d8 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) Upstream-commit: f5b1afae74a62ba9a4c89f3372dd6e5e5cb89dbf Component: engine --- .../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 components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessibledirectory/Dockerfile create mode 100644 components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessibledirectory/directoryWeCantStat/bar create mode 100644 components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessiblefile/Dockerfile create mode 100644 components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessiblefile/fileWithoutReadAccess create mode 100644 components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/linksdirectory/Dockerfile create mode 120000 components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/linksdirectory/g diff --git a/components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessibledirectory/Dockerfile b/components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessibledirectory/Dockerfile new file mode 100644 index 0000000000..0964b8e87c --- /dev/null +++ b/components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessibledirectory/Dockerfile @@ -0,0 +1,2 @@ +FROM busybox +ADD . /foo/ diff --git a/components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessibledirectory/directoryWeCantStat/bar b/components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessibledirectory/directoryWeCantStat/bar new file mode 100644 index 0000000000..257cc5642c --- /dev/null +++ b/components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessibledirectory/directoryWeCantStat/bar @@ -0,0 +1 @@ +foo diff --git a/components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessiblefile/Dockerfile b/components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessiblefile/Dockerfile new file mode 100644 index 0000000000..0964b8e87c --- /dev/null +++ b/components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessiblefile/Dockerfile @@ -0,0 +1,2 @@ +FROM busybox +ADD . /foo/ diff --git a/components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessiblefile/fileWithoutReadAccess b/components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessiblefile/fileWithoutReadAccess new file mode 100644 index 0000000000..b25f9a2a19 --- /dev/null +++ b/components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/inaccessiblefile/fileWithoutReadAccess @@ -0,0 +1 @@ +should make `docker build` throw an error diff --git a/components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/linksdirectory/Dockerfile b/components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/linksdirectory/Dockerfile new file mode 100644 index 0000000000..0964b8e87c --- /dev/null +++ b/components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/linksdirectory/Dockerfile @@ -0,0 +1,2 @@ +FROM busybox +ADD . /foo/ diff --git a/components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/linksdirectory/g b/components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/linksdirectory/g new file mode 120000 index 0000000000..5fc3f33923 --- /dev/null +++ b/components/engine/integration-cli/build_tests/TestBuildWithInaccessibleFilesInContext/linksdirectory/g @@ -0,0 +1 @@ +../../../../../../../../../../../../../../../../../../../azA \ No newline at end of file diff --git a/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index cd3e06d60c..c4d6f12925 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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 c07bcbabc15c9012c6cbcdf8e0cd9636c9094147 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) Upstream-commit: 42734394b0ec238c88bc3ef09454df411b8f3776 Component: engine --- components/engine/utils/http.go | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/components/engine/utils/http.go b/components/engine/utils/http.go index 68e93d8eb9..e193633792 100644 --- a/components/engine/utils/http.go +++ b/components/engine/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 e3742d26419fc737deff3e1f973d633f769e8a6b 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) Upstream-commit: 92614928cecd48b241011e614fa856c4fdbac1f6 Component: engine --- .../execdriver/native/configuration/parse.go | 11 +++++-- .../native/configuration/parse_test.go | 25 +++++++++++----- .../engine/daemon/execdriver/native/create.go | 4 +-- .../native/template/default_template.go | 29 +++++-------------- .../engine/pkg/libcontainer/container.go | 26 ++++++++--------- .../engine/pkg/libcontainer/container.json | 21 ++------------ .../engine/pkg/libcontainer/container_test.go | 21 +++++++++----- .../security/capabilities/capabilities.go | 10 +++---- components/engine/pkg/libcontainer/types.go | 8 +++++ 9 files changed, 75 insertions(+), 80 deletions(-) diff --git a/components/engine/daemon/execdriver/native/configuration/parse.go b/components/engine/daemon/execdriver/native/configuration/parse.go index 3767f5a866..f18a60f797 100644 --- a/components/engine/daemon/execdriver/native/configuration/parse.go +++ b/components/engine/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/components/engine/daemon/execdriver/native/configuration/parse_test.go b/components/engine/daemon/execdriver/native/configuration/parse_test.go index 1b0316b688..5524adb857 100644 --- a/components/engine/daemon/execdriver/native/configuration/parse_test.go +++ b/components/engine/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/components/engine/daemon/execdriver/native/create.go b/components/engine/daemon/execdriver/native/create.go index 5adae30db5..76816e0b9c 100644 --- a/components/engine/daemon/execdriver/native/create.go +++ b/components/engine/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/components/engine/daemon/execdriver/native/template/default_template.go b/components/engine/daemon/execdriver/native/template/default_template.go index 32d901d87b..66cfa88a3a 100644 --- a/components/engine/daemon/execdriver/native/template/default_template.go +++ b/components/engine/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/components/engine/pkg/libcontainer/container.go b/components/engine/pkg/libcontainer/container.go index 7328f5544e..0ea8d37c20 100644 --- a/components/engine/pkg/libcontainer/container.go +++ b/components/engine/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/components/engine/pkg/libcontainer/container.json b/components/engine/pkg/libcontainer/container.json index 33d79600d4..07950fe58a 100644 --- a/components/engine/pkg/libcontainer/container.json +++ b/components/engine/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/components/engine/pkg/libcontainer/container_test.go b/components/engine/pkg/libcontainer/container_test.go index c02385af3f..b3f240740c 100644 --- a/components/engine/pkg/libcontainer/container_test.go +++ b/components/engine/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/components/engine/pkg/libcontainer/security/capabilities/capabilities.go b/components/engine/pkg/libcontainer/security/capabilities/capabilities.go index 107417ad7d..ba72070f50 100644 --- a/components/engine/pkg/libcontainer/security/capabilities/capabilities.go +++ b/components/engine/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/components/engine/pkg/libcontainer/types.go b/components/engine/pkg/libcontainer/types.go index 4c8f60c477..ed5cce4836 100644 --- a/components/engine/pkg/libcontainer/types.go +++ b/components/engine/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 cc28bc2288390992637e3d4028add7163d107b7e 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) Upstream-commit: a0070f0c17b4f65bdfc5368b93d0fa8636eda03d Component: engine --- components/engine/pkg/libcontainer/types.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/pkg/libcontainer/types.go b/components/engine/pkg/libcontainer/types.go index ed5cce4836..07694e9f5a 100644 --- a/components/engine/pkg/libcontainer/types.go +++ b/components/engine/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 da923b63c2515d136723e2d2d390d8a6b3d098a9 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) Upstream-commit: ab6027324d1b31d3f8fb880a014091435b68dd07 Component: engine --- components/engine/integration-cli/MAINTAINERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 components/engine/integration-cli/MAINTAINERS diff --git a/components/engine/integration-cli/MAINTAINERS b/components/engine/integration-cli/MAINTAINERS new file mode 100644 index 0000000000..53c8a11858 --- /dev/null +++ b/components/engine/integration-cli/MAINTAINERS @@ -0,0 +1 @@ +Cristian Staretu (github: unclejack) From 66c2e5e12130a88a89b82585be75c607313c8d48 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) Upstream-commit: 4bf03a0fac48a06298afa149d4339245736810b6 Component: engine --- components/engine/hack/vendor.sh | 2 +- .../syndtr/gocapability/capability/capability_linux.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/components/engine/hack/vendor.sh b/components/engine/hack/vendor.sh index 79322cd9af..28c9fd40f0 100755 --- a/components/engine/hack/vendor.sh +++ b/components/engine/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/components/engine/vendor/src/github.com/syndtr/gocapability/capability/capability_linux.go b/components/engine/vendor/src/github.com/syndtr/gocapability/capability/capability_linux.go index 3aaae5973a..c5f335f7fb 100644 --- a/components/engine/vendor/src/github.com/syndtr/gocapability/capability/capability_linux.go +++ b/components/engine/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 16c16ea1975e6f805d69af62484cece60b28c2e5 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) Upstream-commit: 58c11ee0a8be74562b0e6f88ee000afdf9ddf025 Component: engine --- .../TestBuildSixtySteps/Dockerfile | 120 +++++++++--------- .../build_tests/TestBuildSixtySteps/foo | 1 + 2 files changed, 61 insertions(+), 60 deletions(-) create mode 100644 components/engine/integration-cli/build_tests/TestBuildSixtySteps/foo diff --git a/components/engine/integration-cli/build_tests/TestBuildSixtySteps/Dockerfile b/components/engine/integration-cli/build_tests/TestBuildSixtySteps/Dockerfile index 89b66f4f1d..6a2bcab301 100644 --- a/components/engine/integration-cli/build_tests/TestBuildSixtySteps/Dockerfile +++ b/components/engine/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/components/engine/integration-cli/build_tests/TestBuildSixtySteps/foo b/components/engine/integration-cli/build_tests/TestBuildSixtySteps/foo new file mode 100644 index 0000000000..7898192261 --- /dev/null +++ b/components/engine/integration-cli/build_tests/TestBuildSixtySteps/foo @@ -0,0 +1 @@ +a From 6374fe2fa3695e657abeef9d84185bed634c5d65 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) Upstream-commit: 3e2b97ef26467228f6018e19f517ed535e107026 Component: engine --- components/engine/api/README.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 components/engine/api/README.md diff --git a/components/engine/api/README.md b/components/engine/api/README.md new file mode 100644 index 0000000000..3ef33f8c29 --- /dev/null +++ b/components/engine/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 bc32176ca2c79358b61238b6c2f261847d7be4cb 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) Upstream-commit: 28fc387cf0c933128d0adad6d633c8f4a719d8ee Component: engine --- components/engine/archive/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 components/engine/archive/README.md diff --git a/components/engine/archive/README.md b/components/engine/archive/README.md new file mode 100644 index 0000000000..4eb0c04181 --- /dev/null +++ b/components/engine/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 2e0793def1ad4ea9735f3ff7421212b22b0e2e0a 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) Upstream-commit: b175b4dd434f6b16c0966d9c62be3d63cc8238bd Component: engine --- components/engine/daemon/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 components/engine/daemon/README.md diff --git a/components/engine/daemon/README.md b/components/engine/daemon/README.md new file mode 100644 index 0000000000..64bfcb55ee --- /dev/null +++ b/components/engine/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 63140ac4417adbe41ceba9b0768cb1ac2b69c1b7 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) Upstream-commit: 8dfd4b677b60bc2b5de0214bb3cef1a04da12bed Component: engine --- components/engine/daemonconfig/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 components/engine/daemonconfig/README.md diff --git a/components/engine/daemonconfig/README.md b/components/engine/daemonconfig/README.md new file mode 100644 index 0000000000..488e7c7cac --- /dev/null +++ b/components/engine/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 2ad8a6ba47e66b228d1add8aa98e5ac286dc9245 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) Upstream-commit: f9728de7a3a5bca5db800edf17d8bc3b39358b77 Component: engine --- components/engine/sysinit/README.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 components/engine/sysinit/README.md diff --git a/components/engine/sysinit/README.md b/components/engine/sysinit/README.md new file mode 100644 index 0000000000..c28d0298b8 --- /dev/null +++ b/components/engine/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 d0caec517ca674730512ef1c9921d6839d477dc7 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) Upstream-commit: a52db7d8802f3cfd8a88e1bcf0a25f59458f7558 Component: engine --- components/engine/docs/sources/installation/ubuntulinux.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/docs/sources/installation/ubuntulinux.md b/components/engine/docs/sources/installation/ubuntulinux.md index d40e17b646..d0e37a270b 100644 --- a/components/engine/docs/sources/installation/ubuntulinux.md +++ b/components/engine/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 32ad2ac6980e14aace46df31789bf77715e55b34 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) Upstream-commit: 82712ed67ef9c189ccc8837ef8469272c91f6fcf Component: engine --- components/engine/api/client/commands.go | 2 +- components/engine/integration-cli/docker_cli_version_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/api/client/commands.go b/components/engine/api/client/commands.go index 502c1be69e..532a20cb35 100644 --- a/components/engine/api/client/commands.go +++ b/components/engine/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/components/engine/integration-cli/docker_cli_version_test.go b/components/engine/integration-cli/docker_cli_version_test.go index f18d5bede6..822c2b8172 100644 --- a/components/engine/integration-cli/docker_cli_version_test.go +++ b/components/engine/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 95b5064ed647d33abede9d7367525937c1d54ff4 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) Upstream-commit: 3cec63d56f9de6332aa3f33502695283d4feb054 Component: engine --- components/engine/api/client/commands.go | 8 -------- .../integration-cli/docker_cli_version_test.go | 1 - components/engine/utils/utils.go | 16 ---------------- 3 files changed, 25 deletions(-) diff --git a/components/engine/api/client/commands.go b/components/engine/api/client/commands.go index 532a20cb35..647516da6b 100644 --- a/components/engine/api/client/commands.go +++ b/components/engine/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/components/engine/integration-cli/docker_cli_version_test.go b/components/engine/integration-cli/docker_cli_version_test.go index 822c2b8172..7f1838e5d9 100644 --- a/components/engine/integration-cli/docker_cli_version_test.go +++ b/components/engine/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/components/engine/utils/utils.go b/components/engine/utils/utils.go index 191c85206e..6e3a3c1b60 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/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 3103867f6faca02f342854f3ad7279bbb8f748fb 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 Upstream-commit: 21e36ab36cd0beb192ce95469639f659b4877641 Component: engine --- components/engine/docs/sources/installation/mac.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/docs/sources/installation/mac.md b/components/engine/docs/sources/installation/mac.md index 8165362561..45e67e8d01 100644 --- a/components/engine/docs/sources/installation/mac.md +++ b/components/engine/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 823bf4bfecffaedbcdbf4715963a3a6438e746a9 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) Upstream-commit: 72d1e40c4a3b190319cfa5cb44b5e6f1694100fc Component: engine --- .../engine/docs/sources/reference/run.md | 2 + .../integration-cli/docker_cli_run_test.go | 45 +++++++++++++++++++ components/engine/pkg/user/user.go | 15 +++++++ 3 files changed, 62 insertions(+) diff --git a/components/engine/docs/sources/reference/run.md b/components/engine/docs/sources/reference/run.md index 7587c70ba6..aa3d941b13 100644 --- a/components/engine/docs/sources/reference/run.md +++ b/components/engine/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/components/engine/integration-cli/docker_cli_run_test.go b/components/engine/integration-cli/docker_cli_run_test.go index a439c5f387..154b72e27a 100644 --- a/components/engine/integration-cli/docker_cli_run_test.go +++ b/components/engine/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/components/engine/pkg/user/user.go b/components/engine/pkg/user/user.go index 1672f7e679..df47101221 100644 --- a/components/engine/pkg/user/user.go +++ b/components/engine/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 876c525e4c83f3685971c66c98eec25e3b834b2e 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) Upstream-commit: 7f9178c6d15417ed8b32976b7ca075422c8160da Component: engine --- .../engine/docs/sources/examples/couchdb_data_volumes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/docs/sources/examples/couchdb_data_volumes.md b/components/engine/docs/sources/examples/couchdb_data_volumes.md index 17490487aa..ec1a0d9476 100644 --- a/components/engine/docs/sources/examples/couchdb_data_volumes.md +++ b/components/engine/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 4c6e7e81ee78e3238abb74532b7ef6441b4f8878 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) Upstream-commit: 89c4748b8351eaf070ce722fdb7fe0c0dcec2819 Component: engine --- components/engine/.mailmap | 38 ++++++++--- components/engine/AUTHORS | 134 ++++++++++++++++++++++++++++++++----- 2 files changed, 148 insertions(+), 24 deletions(-) diff --git a/components/engine/.mailmap b/components/engine/.mailmap index a34fc4823c..683758650e 100644 --- a/components/engine/.mailmap +++ b/components/engine/.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/components/engine/AUTHORS b/components/engine/AUTHORS index 8f75ab85b3..10f01fb589 100644 --- a/components/engine/AUTHORS +++ b/components/engine/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 35afe10f75c2c326f04fae9bac6bdd0acd361473 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) Upstream-commit: 88afc8992f2ebd2fd95d87dfff720ff946183975 Component: engine --- components/engine/docs/mkdocs.yml | 1 + .../engine/docs/sources/installation.md | 3 +- .../docs/sources/installation/debian.md | 77 +++++++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 components/engine/docs/sources/installation/debian.md diff --git a/components/engine/docs/mkdocs.yml b/components/engine/docs/mkdocs.yml index 32d0852791..ca1a4d74ef 100755 --- a/components/engine/docs/mkdocs.yml +++ b/components/engine/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/components/engine/docs/sources/installation.md b/components/engine/docs/sources/installation.md index 66b28b2b3c..1c3c726594 100644 --- a/components/engine/docs/sources/installation.md +++ b/components/engine/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/components/engine/docs/sources/installation/debian.md b/components/engine/docs/sources/installation/debian.md new file mode 100644 index 0000000000..3deda47637 --- /dev/null +++ b/components/engine/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 dc955f4ca788b256f1abebc48b9c934ec11c1f9c 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) Upstream-commit: f8974b5cfc87f7775019e3df2d09ad68f2119772 Component: engine --- .../docs/sources/installation/ubuntulinux.md | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/components/engine/docs/sources/installation/ubuntulinux.md b/components/engine/docs/sources/installation/ubuntulinux.md index d40e17b646..541b4da6fa 100644 --- a/components/engine/docs/sources/installation/ubuntulinux.md +++ b/components/engine/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 399e34e6ec2ff45ceb60a2d471237c43a27641e2 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. Upstream-commit: ca9c35cdf772ddb121447b3877dbcf8caa4c5cdb Component: engine --- .../engine/docs/sources/use/networking.md | 752 +++++++++++++++--- 1 file changed, 658 insertions(+), 94 deletions(-) diff --git a/components/engine/docs/sources/use/networking.md b/components/engine/docs/sources/use/networking.md index db60fe843d..5b76233eff 100644 --- a/components/engine/docs/sources/use/networking.md +++ b/components/engine/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 91b861a49c1ef5b3a3746adac8ce59f21d92bd6e 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) Upstream-commit: 7cd1e482309e944522d3506f8e15eeb3ab7fb796 Component: engine --- components/engine/hack/make/test-integration | 4 ++-- components/engine/hack/make/test-integration-cli | 3 ++- components/engine/hack/make/test-unit | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/components/engine/hack/make/test-integration b/components/engine/hack/make/test-integration index 4c2bccaead..baad1349a2 100644 --- a/components/engine/hack/make/test-integration +++ b/components/engine/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/components/engine/hack/make/test-integration-cli b/components/engine/hack/make/test-integration-cli index f2128a26ac..837bd8737a 100644 --- a/components/engine/hack/make/test-integration-cli +++ b/components/engine/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/components/engine/hack/make/test-unit b/components/engine/hack/make/test-unit index 066865859c..552810f349 100644 --- a/components/engine/hack/make/test-unit +++ b/components/engine/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 85d37676bad992a43da8ff681a75f44ee9be8d6d 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) Upstream-commit: 702442b58696b1a77d9274c92000f6e0c0c983bb Component: engine --- components/engine/docs/mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/docs/mkdocs.yml b/components/engine/docs/mkdocs.yml index ca1a4d74ef..96cf3c6e00 100755 --- a/components/engine/docs/mkdocs.yml +++ b/components/engine/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 f3abdf9b7d65d42875859e5b0a6c41546073bb16 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) Upstream-commit: e1c7abe8905d4cc034f1ed49e9d102846e412424 Component: engine --- components/engine/pkg/libcontainer/types.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/components/engine/pkg/libcontainer/types.go b/components/engine/pkg/libcontainer/types.go index 07694e9f5a..834201036f 100644 --- a/components/engine/pkg/libcontainer/types.go +++ b/components/engine/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 4feffb64a0ec3de52655ca7cd1fea06611306821 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) Upstream-commit: 0abad3ae2290a2b051b8fdaceab17a1ee41ecfb9 Component: engine --- .../execdriver/native/template/default_template.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/components/engine/daemon/execdriver/native/template/default_template.go b/components/engine/daemon/execdriver/native/template/default_template.go index 66cfa88a3a..5c41603428 100644 --- a/components/engine/daemon/execdriver/native/template/default_template.go +++ b/components/engine/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 c117ccdc5a943b098698b35e8b453ba9e2ed18c6 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) Upstream-commit: 68493e2f7f9cb8303302e1098e3293b521ace243 Component: engine --- .../engine/pkg/libcontainer/nsinit/init.go | 2 +- .../security/restrict/restrict.go | 21 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/components/engine/pkg/libcontainer/nsinit/init.go b/components/engine/pkg/libcontainer/nsinit/init.go index af60d739ca..25b5fc4334 100644 --- a/components/engine/pkg/libcontainer/nsinit/init.go +++ b/components/engine/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/components/engine/pkg/libcontainer/security/restrict/restrict.go b/components/engine/pkg/libcontainer/security/restrict/restrict.go index 361d0764a0..a22a1aa73e 100644 --- a/components/engine/pkg/libcontainer/security/restrict/restrict.go +++ b/components/engine/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 22e627f91899a9c8c0cec2df502aee2a3059b1e9 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) Upstream-commit: e39299ca1d50df90b085e6a2e3e3f4b6897e5a59 Component: engine --- .../engine/docs/sources/reference/api/docker_remote_api_v1.10.md | 1 + .../engine/docs/sources/reference/api/docker_remote_api_v1.11.md | 1 + 2 files changed, 2 insertions(+) diff --git a/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.md index 721244b49e..f743cb0b22 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md index 53e07b380c..8f4709ee69 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md +++ b/components/engine/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 20387c224e90938c3d4b91395eb767003e86a4b7 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) Upstream-commit: 620c8c72535670e8358cebc65a2fb424be58a93c Component: engine --- .../build_tests/TestAdd/SingleFileToRoot/Dockerfile | 1 + .../build_tests/TestAdd/WholeDirToRoot/Dockerfile | 2 ++ components/engine/server/buildfile.go | 13 ++++++++----- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/components/engine/integration-cli/build_tests/TestAdd/SingleFileToRoot/Dockerfile b/components/engine/integration-cli/build_tests/TestAdd/SingleFileToRoot/Dockerfile index d6375debe1..e96201d858 100644 --- a/components/engine/integration-cli/build_tests/TestAdd/SingleFileToRoot/Dockerfile +++ b/components/engine/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/components/engine/integration-cli/build_tests/TestAdd/WholeDirToRoot/Dockerfile b/components/engine/integration-cli/build_tests/TestAdd/WholeDirToRoot/Dockerfile index 3db6d3fd95..2f10979487 100644 --- a/components/engine/integration-cli/build_tests/TestAdd/WholeDirToRoot/Dockerfile +++ b/components/engine/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/components/engine/server/buildfile.go b/components/engine/server/buildfile.go index 9953904941..efe1869509 100644 --- a/components/engine/server/buildfile.go +++ b/components/engine/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 978fda27ac7d11907868266aba7d5933f9c8560a 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) Upstream-commit: 667e2bd4ea5fbc8698c34565f955cb92cff92890 Component: engine --- components/engine/api/client/commands.go | 5 + components/engine/api/common.go | 2 +- components/engine/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 + .../engine/integration/buildfile_test.go | 6 +- components/engine/server/buildfile.go | 7 +- components/engine/server/server.go | 3 +- 9 files changed, 1400 insertions(+), 10 deletions(-) create mode 100644 components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md diff --git a/components/engine/api/client/commands.go b/components/engine/api/client/commands.go index 6562709f3e..7f4288ec2b 100644 --- a/components/engine/api/client/commands.go +++ b/components/engine/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/components/engine/api/common.go b/components/engine/api/common.go index f4e31a970b..a20c5d7d1c 100644 --- a/components/engine/api/common.go +++ b/components/engine/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/components/engine/api/server/server.go b/components/engine/api/server/server.go index f08f395400..0ac57471af 100644 --- a/components/engine/api/server/server.go +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api.md b/components/engine/docs/sources/reference/api/docker_remote_api.md index 47f4724b1a..84b56866ba 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md new file mode 100644 index 0000000000..d2b7f8f908 --- /dev/null +++ b/components/engine/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/components/engine/docs/sources/reference/commandline/cli.md b/components/engine/docs/sources/reference/commandline/cli.md index 40abfe45a2..fb4bbd303a 100644 --- a/components/engine/docs/sources/reference/commandline/cli.md +++ b/components/engine/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/components/engine/integration/buildfile_test.go b/components/engine/integration/buildfile_test.go index b87fa116eb..820a3c7c89 100644 --- a/components/engine/integration/buildfile_test.go +++ b/components/engine/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/components/engine/server/buildfile.go b/components/engine/server/buildfile.go index 9953904941..7e4e157a43 100644 --- a/components/engine/server/buildfile.go +++ b/components/engine/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/components/engine/server/server.go b/components/engine/server/server.go index 49fb00ce54..80d881d628 100644 --- a/components/engine/server/server.go +++ b/components/engine/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 8a11c8ce1e4e3db2e46a4556414700a4268bc54d 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) Upstream-commit: 77f54252606e6a094b5f722680d43f625bb0d0a8 Component: engine --- .../engine/integration-cli/docker_utils.go | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/components/engine/integration-cli/docker_utils.go b/components/engine/integration-cli/docker_utils.go index 17a331f2dd..660d509e76 100644 --- a/components/engine/integration-cli/docker_utils.go +++ b/components/engine/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 2a0a0d8d030f95ee660ec4cd4ca27279462b8fad 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) Upstream-commit: 69dcf767fd6b4c54c219c20f87b2fdbb173b7f1c Component: engine --- .../build_tests/TestBuildForceRm/Dockerfile | 3 +++ .../integration-cli/docker_cli_build_test.go | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 components/engine/integration-cli/build_tests/TestBuildForceRm/Dockerfile diff --git a/components/engine/integration-cli/build_tests/TestBuildForceRm/Dockerfile b/components/engine/integration-cli/build_tests/TestBuildForceRm/Dockerfile new file mode 100644 index 0000000000..8468edd4ce --- /dev/null +++ b/components/engine/integration-cli/build_tests/TestBuildForceRm/Dockerfile @@ -0,0 +1,3 @@ +FROM busybox +RUN true +RUN thiswillfail diff --git a/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index c4d6f12925..ff6db58b47 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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 4c017922d1a5ef0784cac60257572bc620d06cd8 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) Upstream-commit: b60d6471721bc914dca179a4372303d41913cc4c Component: engine --- components/engine/api/client/commands.go | 2 ++ components/engine/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/components/engine/api/client/commands.go b/components/engine/api/client/commands.go index 7f4288ec2b..430613fde8 100644 --- a/components/engine/api/client/commands.go +++ b/components/engine/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/components/engine/api/server/server.go b/components/engine/api/server/server.go index 0ac57471af..1c620b3c96 100644 --- a/components/engine/api/server/server.go +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md index d2b7f8f908..188d4fe0a2 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md +++ b/components/engine/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 2e2afce79e8c4c940148b4cb5824304b3c73bda1 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) Upstream-commit: a691fcb2774deb07fe602d654bd7ba0908fdc09f Component: engine --- .../build_tests/TestBuildRm/Dockerfile | 4 + .../build_tests/TestBuildRm/foo | 1 + .../integration-cli/docker_cli_build_test.go | 85 +++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 components/engine/integration-cli/build_tests/TestBuildRm/Dockerfile create mode 100644 components/engine/integration-cli/build_tests/TestBuildRm/foo diff --git a/components/engine/integration-cli/build_tests/TestBuildRm/Dockerfile b/components/engine/integration-cli/build_tests/TestBuildRm/Dockerfile new file mode 100644 index 0000000000..190eacf117 --- /dev/null +++ b/components/engine/integration-cli/build_tests/TestBuildRm/Dockerfile @@ -0,0 +1,4 @@ +FROM busybox +ADD foo / +ADD foo / + diff --git a/components/engine/integration-cli/build_tests/TestBuildRm/foo b/components/engine/integration-cli/build_tests/TestBuildRm/foo new file mode 100644 index 0000000000..5716ca5987 --- /dev/null +++ b/components/engine/integration-cli/build_tests/TestBuildRm/foo @@ -0,0 +1 @@ +bar diff --git a/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index ff6db58b47..0bcd6deced 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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 9b9e25f1db19bb0768e816c605d16382be94b1a6 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) Upstream-commit: a87bcefb8bf0cee47bf114a46fc33708ce843208 Component: engine --- .../engine/pkg/libcontainer/mount/init.go | 5 +++- .../pkg/libcontainer/mount/nodes/nodes.go | 24 +++++++++++++++---- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/components/engine/pkg/libcontainer/mount/init.go b/components/engine/pkg/libcontainer/mount/init.go index 6f3080cf08..c4148131ad 100644 --- a/components/engine/pkg/libcontainer/mount/init.go +++ b/components/engine/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/components/engine/pkg/libcontainer/mount/nodes/nodes.go b/components/engine/pkg/libcontainer/mount/nodes/nodes.go index 5022f85b0b..1384682729 100644 --- a/components/engine/pkg/libcontainer/mount/nodes/nodes.go +++ b/components/engine/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 bf729dca8bd9fd81d46c13280bf3ac22f6e1b4c3 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) Upstream-commit: 599cb12bb8aa13aa2f1be940dd8de4c9d3a06959 Component: engine --- components/engine/Dockerfile | 3 +++ components/engine/integration-cli/docker_cli_build_test.go | 7 ++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/engine/Dockerfile b/components/engine/Dockerfile index 8a8818d757..3aff4f3e2e 100644 --- a/components/engine/Dockerfile +++ b/components/engine/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/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index c4d6f12925..e8ca7eae73 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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 27fffbebb61ca72a8165a0580d78e941bce381b1 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) Upstream-commit: c12dd770908acc06ab402840ad7736e0c056b567 Component: engine --- components/engine/daemon/daemon.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go index fc9f07052e..5df5d434fb 100644 --- a/components/engine/daemon/daemon.go +++ b/components/engine/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 09f949815a4fff41431c3b2bbea669f07ec8f2c0 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) Upstream-commit: e454be7567e144dc836ff283d4f4e059db307a91 Component: engine --- components/engine/daemon/daemon.go | 3 + components/engine/daemon/volumes.go | 222 ++++++++++++++------------- components/engine/graph/graph.go | 15 +- components/engine/runconfig/merge.go | 4 +- components/engine/server/server.go | 8 +- 5 files changed, 138 insertions(+), 114 deletions(-) diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go index fc9f07052e..bea11d8b64 100644 --- a/components/engine/daemon/daemon.go +++ b/components/engine/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/components/engine/daemon/volumes.go b/components/engine/daemon/volumes.go index eac743b2d9..5a0215b8fb 100644 --- a/components/engine/daemon/volumes.go +++ b/components/engine/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/components/engine/graph/graph.go b/components/engine/graph/graph.go index 5de9cbe7a1..a5a433d901 100644 --- a/components/engine/graph/graph.go +++ b/components/engine/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/components/engine/runconfig/merge.go b/components/engine/runconfig/merge.go index 252a2fe434..e30b4cec24 100644 --- a/components/engine/runconfig/merge.go +++ b/components/engine/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/components/engine/server/server.go b/components/engine/server/server.go index 49fb00ce54..490f482596 100644 --- a/components/engine/server/server.go +++ b/components/engine/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 2c620b948696c2b37d39fb4b58aa54e64bba3631 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) Upstream-commit: d535d9810012b18ca83f86aa416ff4a08bb49804 Component: engine --- components/engine/daemon/volumes.go | 79 +++++++++++++++++----------- components/engine/runconfig/parse.go | 4 +- 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/components/engine/daemon/volumes.go b/components/engine/daemon/volumes.go index 5a0215b8fb..793bd18509 100644 --- a/components/engine/daemon/volumes.go +++ b/components/engine/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 != nil { + 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/components/engine/runconfig/parse.go b/components/engine/runconfig/parse.go index 54fc51ec07..0fa287adb1 100644 --- a/components/engine/runconfig/parse.go +++ b/components/engine/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 446a0e28d37f250bf678fbcf641bb05822326afc 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) Upstream-commit: d31c37fceb6c09a48b5dd9d6c33a95d734e02704 Component: engine --- .../integration-cli/docker_cli_commit_test.go | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/components/engine/integration-cli/docker_cli_commit_test.go b/components/engine/integration-cli/docker_cli_commit_test.go index 7635add913..c02c89cd30 100644 --- a/components/engine/integration-cli/docker_cli_commit_test.go +++ b/components/engine/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 7428c89c956129067e713f11cb2f7fc7ac75a311 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) Upstream-commit: 4052a81830e57c9f1d7d567ba9e53813945c9810 Component: engine --- components/engine/docs/sources/reference/commandline/cli.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/docs/sources/reference/commandline/cli.md b/components/engine/docs/sources/reference/commandline/cli.md index 40abfe45a2..da9f49583b 100644 --- a/components/engine/docs/sources/reference/commandline/cli.md +++ b/components/engine/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 16c9a15cd6cfb8783471cae91b92d5a5db43f4e9 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) Upstream-commit: 000a37fe9d13a173ab46fcd5b8e693950a438f98 Component: engine --- components/engine/daemon/container.go | 12 ++++++++++-- components/engine/daemon/volumes.go | 5 ++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go index 9ca94b2b56..54e720deb8 100644 --- a/components/engine/daemon/container.go +++ b/components/engine/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/components/engine/daemon/volumes.go b/components/engine/daemon/volumes.go index eac743b2d9..f96ce05c5a 100644 --- a/components/engine/daemon/volumes.go +++ b/components/engine/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 d043d726b20cc9b5dd8c0e25fe1e2ca4f34ea9fc 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) Upstream-commit: 602950435056baa939f428223b6d3ff26ca5403d Component: engine --- .../cgroups/systemd/apply_systemd.go | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/components/engine/pkg/libcontainer/cgroups/systemd/apply_systemd.go b/components/engine/pkg/libcontainer/cgroups/systemd/apply_systemd.go index 52940f6fae..4d6b68b2cd 100644 --- a/components/engine/pkg/libcontainer/cgroups/systemd/apply_systemd.go +++ b/components/engine/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 da7177cbfd059f22e190b5ff64247a67052475d3 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) Upstream-commit: fcf2e9a9107c6c9aebaf63ce044f636333e7eed8 Component: engine --- .../daemon/execdriver/native/template/default_template.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/engine/daemon/execdriver/native/template/default_template.go b/components/engine/daemon/execdriver/native/template/default_template.go index 5c41603428..ba52499c24 100644 --- a/components/engine/daemon/execdriver/native/template/default_template.go +++ b/components/engine/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 df1cb6f1b5ea686052b1fce27ee96860d70c6bbb 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) Upstream-commit: bc081a03d85a88cc2160ade9256d96d7b84c4caa Component: engine --- components/engine/api/client/cli.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/engine/api/client/cli.go b/components/engine/api/client/cli.go index 49fb3c978f..44cff0dc46 100644 --- a/components/engine/api/client/cli.go +++ b/components/engine/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 417fd6d53a34b2b48725cf5b1a84a5df28599453 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) Upstream-commit: 0f278940947d74f2b7889ada18808779312f9608 Component: engine --- components/engine/daemon/execdriver/lxc/lxc_template.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/engine/daemon/execdriver/lxc/lxc_template.go b/components/engine/daemon/execdriver/lxc/lxc_template.go index f2364cb77f..a16f8990e2 100644 --- a/components/engine/daemon/execdriver/lxc/lxc_template.go +++ b/components/engine/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 24dcd76db5e4eafd8c9236d218e0f8bf6ed9d55d 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) Upstream-commit: 24c73ce2d3d572313fe56bad08819e0ca8b74d26 Component: engine --- components/engine/api/server/server.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/components/engine/api/server/server.go b/components/engine/api/server/server.go index f08f395400..58a994c64c 100644 --- a/components/engine/api/server/server.go +++ b/components/engine/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 74d86a8d614cf458346dd377a381d48f09578576 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) Upstream-commit: 603e00a3a7644caf118d3efd0932500b4dfc4de3 Component: engine --- components/engine/api/server/server.go | 12 +- components/engine/daemon/daemon.go | 5 + components/engine/daemon/inspect.go | 27 ++++ components/engine/graph/service.go | 53 +++++++- components/engine/integration/api_test.go | 3 +- .../engine/integration/buildfile_test.go | 25 +++- .../engine/integration/commands_test.go | 7 +- components/engine/integration/server_test.go | 4 +- components/engine/server/server.go | 126 ++++-------------- 9 files changed, 137 insertions(+), 125 deletions(-) create mode 100644 components/engine/daemon/inspect.go diff --git a/components/engine/api/server/server.go b/components/engine/api/server/server.go index f08f395400..19986058c6 100644 --- a/components/engine/api/server/server.go +++ b/components/engine/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/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go index 5df5d434fb..24e7aa57ed 100644 --- a/components/engine/daemon/daemon.go +++ b/components/engine/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/components/engine/daemon/inspect.go b/components/engine/daemon/inspect.go new file mode 100644 index 0000000000..0f771a3ca2 --- /dev/null +++ b/components/engine/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/components/engine/graph/service.go b/components/engine/graph/service.go index 211babd0bd..881a199043 100644 --- a/components/engine/graph/service.go +++ b/components/engine/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/components/engine/integration/api_test.go b/components/engine/integration/api_test.go index 04611dfe3d..969e0fbaf2 100644 --- a/components/engine/integration/api_test.go +++ b/components/engine/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/components/engine/integration/buildfile_test.go b/components/engine/integration/buildfile_test.go index b87fa116eb..268e9eaf4f 100644 --- a/components/engine/integration/buildfile_test.go +++ b/components/engine/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/components/engine/integration/commands_test.go b/components/engine/integration/commands_test.go index b91ba603a7..2ee5842e1c 100644 --- a/components/engine/integration/commands_test.go +++ b/components/engine/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/components/engine/integration/server_test.go b/components/engine/integration/server_test.go index 4da3e6e368..3752c9b7e6 100644 --- a/components/engine/integration/server_test.go +++ b/components/engine/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/components/engine/server/server.go b/components/engine/server/server.go index 49fb00ce54..ee35d29a63 100644 --- a/components/engine/server/server.go +++ b/components/engine/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 5d70d92ff7b3c624c8fef4317e13e1eeaf5a81d7 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) Upstream-commit: 94f01184beb731b8c28dd43cf259621ba14f76ff Component: engine --- components/engine/Dockerfile | 5 +---- components/engine/hack/PACKAGERS.md | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/components/engine/Dockerfile b/components/engine/Dockerfile index 3aff4f3e2e..183ec89dbd 100644 --- a/components/engine/Dockerfile +++ b/components/engine/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/components/engine/hack/PACKAGERS.md b/components/engine/hack/PACKAGERS.md index 9edb4a3e14..82d959c9e2 100644 --- a/components/engine/hack/PACKAGERS.md +++ b/components/engine/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 aad71a085ef93e42f0c6c052f5f42c770ec82289 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) Upstream-commit: e2d79bec3ab76357625ca57d83299012f584c024 Component: engine --- components/engine/runconfig/config_test.go | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/components/engine/runconfig/config_test.go b/components/engine/runconfig/config_test.go index f71528ff8e..b426253b9e 100644 --- a/components/engine/runconfig/config_test.go +++ b/components/engine/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 2fdc01b4888601378abd1c477d26456a6837f7c6 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) Upstream-commit: d98af1236c9aeef6f9eef34970e13cbe0ea06ff8 Component: engine --- components/engine/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 ++++++++ .../src/github.com/gorilla/context/doc.go | 2 +- .../src/github.com/gorilla/mux/.travis.yml | 7 + .../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 ++- .../src/github.com/gorilla/mux/mux_test.go | 225 ++++++++++++++++-- .../src/github.com/gorilla/mux/old_test.go | 8 +- .../src/github.com/gorilla/mux/regexp.go | 17 +- .../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 +++ .../src/github.com/kr/pty/ioctl_linux.go | 42 ++++ .../vendor/src/github.com/kr/pty/mktypes.bash | 19 ++ .../src/github.com/kr/pty/pty_darwin.go | 25 +- .../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 + .../src/github.com/kr/pty/types_freebsd.go | 15 ++ .../src/github.com/kr/pty/ztypes_386.go | 9 + .../src/github.com/kr/pty/ztypes_amd64.go | 9 + .../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 components/engine/vendor/src/github.com/gorilla/context/.travis.yml create mode 100644 components/engine/vendor/src/github.com/gorilla/mux/.travis.yml create mode 100644 components/engine/vendor/src/github.com/kr/pty/ioctl.go create mode 100644 components/engine/vendor/src/github.com/kr/pty/ioctl_bsd.go create mode 100644 components/engine/vendor/src/github.com/kr/pty/ioctl_linux.go create mode 100755 components/engine/vendor/src/github.com/kr/pty/mktypes.bash create mode 100644 components/engine/vendor/src/github.com/kr/pty/types.go create mode 100644 components/engine/vendor/src/github.com/kr/pty/types_freebsd.go create mode 100644 components/engine/vendor/src/github.com/kr/pty/ztypes_386.go create mode 100644 components/engine/vendor/src/github.com/kr/pty/ztypes_amd64.go create mode 100644 components/engine/vendor/src/github.com/kr/pty/ztypes_arm.go create mode 100644 components/engine/vendor/src/github.com/kr/pty/ztypes_freebsd_386.go create mode 100644 components/engine/vendor/src/github.com/kr/pty/ztypes_freebsd_amd64.go create mode 100644 components/engine/vendor/src/github.com/kr/pty/ztypes_freebsd_arm.go diff --git a/components/engine/hack/vendor.sh b/components/engine/hack/vendor.sh index 1119c37b25..7f01a236c8 100755 --- a/components/engine/hack/vendor.sh +++ b/components/engine/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/components/engine/vendor/src/github.com/gorilla/context/.travis.yml b/components/engine/vendor/src/github.com/gorilla/context/.travis.yml new file mode 100644 index 0000000000..d87d465768 --- /dev/null +++ b/components/engine/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/components/engine/vendor/src/github.com/gorilla/context/README.md b/components/engine/vendor/src/github.com/gorilla/context/README.md index 8ee62b4263..c60a31b053 100644 --- a/components/engine/vendor/src/github.com/gorilla/context/README.md +++ b/components/engine/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/components/engine/vendor/src/github.com/gorilla/context/context.go b/components/engine/vendor/src/github.com/gorilla/context/context.go index 35d65561f3..a7f7d85bb4 100644 --- a/components/engine/vendor/src/github.com/gorilla/context/context.go +++ b/components/engine/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/components/engine/vendor/src/github.com/gorilla/context/context_test.go b/components/engine/vendor/src/github.com/gorilla/context/context_test.go index ff9e2ad5fc..6ada8ec31f 100644 --- a/components/engine/vendor/src/github.com/gorilla/context/context_test.go +++ b/components/engine/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/components/engine/vendor/src/github.com/gorilla/context/doc.go b/components/engine/vendor/src/github.com/gorilla/context/doc.go index 297606455c..73c7400311 100644 --- a/components/engine/vendor/src/github.com/gorilla/context/doc.go +++ b/components/engine/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/components/engine/vendor/src/github.com/gorilla/mux/.travis.yml b/components/engine/vendor/src/github.com/gorilla/mux/.travis.yml new file mode 100644 index 0000000000..d87d465768 --- /dev/null +++ b/components/engine/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/components/engine/vendor/src/github.com/gorilla/mux/README.md b/components/engine/vendor/src/github.com/gorilla/mux/README.md index f6db41ad81..e60301b033 100644 --- a/components/engine/vendor/src/github.com/gorilla/mux/README.md +++ b/components/engine/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/components/engine/vendor/src/github.com/gorilla/mux/doc.go b/components/engine/vendor/src/github.com/gorilla/mux/doc.go index 8ee5540a4f..b2deed34c4 100644 --- a/components/engine/vendor/src/github.com/gorilla/mux/doc.go +++ b/components/engine/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/components/engine/vendor/src/github.com/gorilla/mux/mux.go b/components/engine/vendor/src/github.com/gorilla/mux/mux.go index 385717394c..8b23c39d39 100644 --- a/components/engine/vendor/src/github.com/gorilla/mux/mux.go +++ b/components/engine/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/components/engine/vendor/src/github.com/gorilla/mux/mux_test.go b/components/engine/vendor/src/github.com/gorilla/mux/mux_test.go index 55159bd10d..0e2e48067a 100644 --- a/components/engine/vendor/src/github.com/gorilla/mux/mux_test.go +++ b/components/engine/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/components/engine/vendor/src/github.com/gorilla/mux/old_test.go b/components/engine/vendor/src/github.com/gorilla/mux/old_test.go index 7e266bb695..42530590e7 100644 --- a/components/engine/vendor/src/github.com/gorilla/mux/old_test.go +++ b/components/engine/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/components/engine/vendor/src/github.com/gorilla/mux/regexp.go b/components/engine/vendor/src/github.com/gorilla/mux/regexp.go index 4c3482bfbd..925f268abe 100644 --- a/components/engine/vendor/src/github.com/gorilla/mux/regexp.go +++ b/components/engine/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/components/engine/vendor/src/github.com/gorilla/mux/route.go b/components/engine/vendor/src/github.com/gorilla/mux/route.go index cb538ea4ec..5cb2526d61 100644 --- a/components/engine/vendor/src/github.com/gorilla/mux/route.go +++ b/components/engine/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/components/engine/vendor/src/github.com/kr/pty/ioctl.go b/components/engine/vendor/src/github.com/kr/pty/ioctl.go new file mode 100644 index 0000000000..5b856e8711 --- /dev/null +++ b/components/engine/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/components/engine/vendor/src/github.com/kr/pty/ioctl_bsd.go b/components/engine/vendor/src/github.com/kr/pty/ioctl_bsd.go new file mode 100644 index 0000000000..73b12c53cf --- /dev/null +++ b/components/engine/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/components/engine/vendor/src/github.com/kr/pty/ioctl_linux.go b/components/engine/vendor/src/github.com/kr/pty/ioctl_linux.go new file mode 100644 index 0000000000..9fe7b0b0f9 --- /dev/null +++ b/components/engine/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/components/engine/vendor/src/github.com/kr/pty/mktypes.bash b/components/engine/vendor/src/github.com/kr/pty/mktypes.bash new file mode 100755 index 0000000000..9952c88838 --- /dev/null +++ b/components/engine/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/components/engine/vendor/src/github.com/kr/pty/pty_darwin.go b/components/engine/vendor/src/github.com/kr/pty/pty_darwin.go index 597bb03e57..4f4d5ca26e 100644 --- a/components/engine/vendor/src/github.com/kr/pty/pty_darwin.go +++ b/components/engine/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/components/engine/vendor/src/github.com/kr/pty/pty_freebsd.go b/components/engine/vendor/src/github.com/kr/pty/pty_freebsd.go index 13b64d722e..b341babd05 100644 --- a/components/engine/vendor/src/github.com/kr/pty/pty_freebsd.go +++ b/components/engine/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/components/engine/vendor/src/github.com/kr/pty/pty_linux.go b/components/engine/vendor/src/github.com/kr/pty/pty_linux.go index a5edfbb394..6e5a04241c 100644 --- a/components/engine/vendor/src/github.com/kr/pty/pty_linux.go +++ b/components/engine/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/components/engine/vendor/src/github.com/kr/pty/pty_unsupported.go b/components/engine/vendor/src/github.com/kr/pty/pty_unsupported.go index d4958b3583..898c7303c4 100644 --- a/components/engine/vendor/src/github.com/kr/pty/pty_unsupported.go +++ b/components/engine/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/components/engine/vendor/src/github.com/kr/pty/types.go b/components/engine/vendor/src/github.com/kr/pty/types.go new file mode 100644 index 0000000000..5aecb6bcdc --- /dev/null +++ b/components/engine/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/components/engine/vendor/src/github.com/kr/pty/types_freebsd.go b/components/engine/vendor/src/github.com/kr/pty/types_freebsd.go new file mode 100644 index 0000000000..ce3eb95181 --- /dev/null +++ b/components/engine/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/components/engine/vendor/src/github.com/kr/pty/ztypes_386.go b/components/engine/vendor/src/github.com/kr/pty/ztypes_386.go new file mode 100644 index 0000000000..ff0b8fd838 --- /dev/null +++ b/components/engine/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/components/engine/vendor/src/github.com/kr/pty/ztypes_amd64.go b/components/engine/vendor/src/github.com/kr/pty/ztypes_amd64.go new file mode 100644 index 0000000000..ff0b8fd838 --- /dev/null +++ b/components/engine/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/components/engine/vendor/src/github.com/kr/pty/ztypes_arm.go b/components/engine/vendor/src/github.com/kr/pty/ztypes_arm.go new file mode 100644 index 0000000000..ff0b8fd838 --- /dev/null +++ b/components/engine/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/components/engine/vendor/src/github.com/kr/pty/ztypes_freebsd_386.go b/components/engine/vendor/src/github.com/kr/pty/ztypes_freebsd_386.go new file mode 100644 index 0000000000..d9975374e3 --- /dev/null +++ b/components/engine/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/components/engine/vendor/src/github.com/kr/pty/ztypes_freebsd_amd64.go b/components/engine/vendor/src/github.com/kr/pty/ztypes_freebsd_amd64.go new file mode 100644 index 0000000000..5fa102fcdf --- /dev/null +++ b/components/engine/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/components/engine/vendor/src/github.com/kr/pty/ztypes_freebsd_arm.go b/components/engine/vendor/src/github.com/kr/pty/ztypes_freebsd_arm.go new file mode 100644 index 0000000000..d9975374e3 --- /dev/null +++ b/components/engine/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 76507afd3a449bba9cff511712392202e484cff9 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) Upstream-commit: 9810da853bd890b6c963017555c3555ef9e0d842 Component: engine --- components/engine/server/buildfile.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/engine/server/buildfile.go b/components/engine/server/buildfile.go index efe1869509..46f214eac7 100644 --- a/components/engine/server/buildfile.go +++ b/components/engine/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 9c8b2dfbeb14ae9dc5e445a8d8013f0490f7ccdc 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) Upstream-commit: bcfe2ceffb1c4c7006570d4ba21ed2068bb448a1 Component: engine --- components/engine/server/buildfile.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/components/engine/server/buildfile.go b/components/engine/server/buildfile.go index 46f214eac7..b37053ac16 100644 --- a/components/engine/server/buildfile.go +++ b/components/engine/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 83451c4f0ad1f0158ddf275db4072bbe554e0081 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) Upstream-commit: 03a109e446351ba40ea5d74e8eb6733b2a9a5045 Component: engine --- .../TestBuildCacheADD/1/Dockerfile | 2 ++ .../TestBuildCacheADD/2/Dockerfile | 2 ++ .../integration-cli/docker_cli_build_test.go | 31 +++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 components/engine/integration-cli/build_tests/TestBuildCacheADD/1/Dockerfile create mode 100644 components/engine/integration-cli/build_tests/TestBuildCacheADD/2/Dockerfile diff --git a/components/engine/integration-cli/build_tests/TestBuildCacheADD/1/Dockerfile b/components/engine/integration-cli/build_tests/TestBuildCacheADD/1/Dockerfile new file mode 100644 index 0000000000..7287771992 --- /dev/null +++ b/components/engine/integration-cli/build_tests/TestBuildCacheADD/1/Dockerfile @@ -0,0 +1,2 @@ +FROM busybox +ADD https://index.docker.io/robots.txt / diff --git a/components/engine/integration-cli/build_tests/TestBuildCacheADD/2/Dockerfile b/components/engine/integration-cli/build_tests/TestBuildCacheADD/2/Dockerfile new file mode 100644 index 0000000000..afe79b84b6 --- /dev/null +++ b/components/engine/integration-cli/build_tests/TestBuildCacheADD/2/Dockerfile @@ -0,0 +1,2 @@ +FROM busybox +ADD http://example.com/index.html / diff --git a/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index e8ca7eae73..041b10d8bc 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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 ada6c057b6268608b9bdcf6b48a7d66d922eb152 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) Upstream-commit: 34c05c58c8d41ee2bb02cd8059e9928ee2f061ea Component: engine --- .../engine/daemon/execdriver/native/create.go | 10 ++++--- .../native/template/default_template.go | 5 ++++ .../engine/pkg/libcontainer/container.go | 27 ++++++++++--------- .../engine/pkg/libcontainer/container.json | 12 ++++++++- .../engine/pkg/libcontainer/container_test.go | 21 ++++++++++----- .../engine/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 components/engine/pkg/libcontainer/mount/nodes/nodes_unsupported.go diff --git a/components/engine/daemon/execdriver/native/create.go b/components/engine/daemon/execdriver/native/create.go index 76816e0b9c..9ed0491940 100644 --- a/components/engine/daemon/execdriver/native/create.go +++ b/components/engine/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/components/engine/daemon/execdriver/native/template/default_template.go b/components/engine/daemon/execdriver/native/template/default_template.go index ba52499c24..dbe3985f9b 100644 --- a/components/engine/daemon/execdriver/native/template/default_template.go +++ b/components/engine/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/components/engine/pkg/libcontainer/container.go b/components/engine/pkg/libcontainer/container.go index 0ea8d37c20..092cd5d93a 100644 --- a/components/engine/pkg/libcontainer/container.go +++ b/components/engine/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/components/engine/pkg/libcontainer/container.json b/components/engine/pkg/libcontainer/container.json index 07950fe58a..c3b0196b4a 100644 --- a/components/engine/pkg/libcontainer/container.json +++ b/components/engine/pkg/libcontainer/container.json @@ -43,5 +43,15 @@ { "type": "devtmpfs" } - ] + ], + "device_nodes": { + "required": [ + "null", + "zero", + "full", + "random", + "urandom", + "tty" + ] + } } diff --git a/components/engine/pkg/libcontainer/container_test.go b/components/engine/pkg/libcontainer/container_test.go index b3f240740c..d77ce313ae 100644 --- a/components/engine/pkg/libcontainer/container_test.go +++ b/components/engine/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/components/engine/pkg/libcontainer/mount/init.go b/components/engine/pkg/libcontainer/mount/init.go index c4148131ad..184df1e8ec 100644 --- a/components/engine/pkg/libcontainer/mount/init.go +++ b/components/engine/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/components/engine/pkg/libcontainer/mount/nodes/nodes.go b/components/engine/pkg/libcontainer/mount/nodes/nodes.go index 1384682729..14b6f5ae57 100644 --- a/components/engine/pkg/libcontainer/mount/nodes/nodes.go +++ b/components/engine/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/components/engine/pkg/libcontainer/mount/nodes/nodes_unsupported.go b/components/engine/pkg/libcontainer/mount/nodes/nodes_unsupported.go new file mode 100644 index 0000000000..24409f411f --- /dev/null +++ b/components/engine/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 34fdbfe296b385a02c0db90be5959fe9ed957c17 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) Upstream-commit: ed5892ed4efa995950e2fdeb5fd718b3bb1aa1c2 Component: engine --- .../engine/pkg/libcontainer/container.go | 100 ++++++++++++++---- 1 file changed, 77 insertions(+), 23 deletions(-) diff --git a/components/engine/pkg/libcontainer/container.go b/components/engine/pkg/libcontainer/container.go index 092cd5d93a..f7aa245855 100644 --- a/components/engine/pkg/libcontainer/container.go +++ b/components/engine/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 6b24ea22aec08e05b0eab4e71b6c23e79307d3a3 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) Upstream-commit: 5eef0a28cb70881d8f1e34519e9c0df6cb1da071 Component: engine --- components/engine/Dockerfile | 2 + .../integration-cli/docker_cli_cp_test.go | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/components/engine/Dockerfile b/components/engine/Dockerfile index 183ec89dbd..41f0f92947 100644 --- a/components/engine/Dockerfile +++ b/components/engine/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/components/engine/integration-cli/docker_cli_cp_test.go b/components/engine/integration-cli/docker_cli_cp_test.go index b5a70a45ed..7421ed0fa1 100644 --- a/components/engine/integration-cli/docker_cli_cp_test.go +++ b/components/engine/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 786a16cf577bab9d6501d668a9311f3993cace30 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) Upstream-commit: 9e51b7abaea3fb30dc994a1d004cd79f2e100c1a Component: engine --- components/engine/api/client/commands.go | 2 +- components/engine/archive/archive.go | 8 ++++---- components/engine/archive/archive_test.go | 5 +++-- components/engine/archive/diff.go | 7 ++++--- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/components/engine/api/client/commands.go b/components/engine/api/client/commands.go index 6562709f3e..28c1a9c515 100644 --- a/components/engine/api/client/commands.go +++ b/components/engine/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/components/engine/archive/archive.go b/components/engine/archive/archive.go index 160ea0737e..76c6e31289 100644 --- a/components/engine/archive/archive.go +++ b/components/engine/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/components/engine/archive/archive_test.go b/components/engine/archive/archive_test.go index e959a2b073..72ffd99565 100644 --- a/components/engine/archive/archive_test.go +++ b/components/engine/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/components/engine/archive/diff.go b/components/engine/archive/diff.go index 49d8cb4984..d169669126 100644 --- a/components/engine/archive/diff.go +++ b/components/engine/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 37f08c7066d0829e04528a562dc16d742fdaed2b 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) Upstream-commit: f042c3c15759fce5cc139f2b3362b791ac7d4829 Component: engine --- .../engine/daemon/execdriver/native/create.go | 4 ++-- .../native/template/default_template.go | 8 +++----- .../engine/pkg/libcontainer/container.go | 17 ++++++++--------- .../engine/pkg/libcontainer/container.json | 18 ++++++++---------- .../engine/pkg/libcontainer/container_test.go | 2 +- .../engine/pkg/libcontainer/mount/init.go | 6 +++--- 6 files changed, 25 insertions(+), 30 deletions(-) diff --git a/components/engine/daemon/execdriver/native/create.go b/components/engine/daemon/execdriver/native/create.go index 9ed0491940..d35043bd08 100644 --- a/components/engine/daemon/execdriver/native/create.go +++ b/components/engine/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/components/engine/daemon/execdriver/native/template/default_template.go b/components/engine/daemon/execdriver/native/template/default_template.go index dbe3985f9b..cbef06fbf1 100644 --- a/components/engine/daemon/execdriver/native/template/default_template.go +++ b/components/engine/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/components/engine/pkg/libcontainer/container.go b/components/engine/pkg/libcontainer/container.go index f7aa245855..6734bfd590 100644 --- a/components/engine/pkg/libcontainer/container.go +++ b/components/engine/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/components/engine/pkg/libcontainer/container.json b/components/engine/pkg/libcontainer/container.json index c3b0196b4a..7156260bc2 100644 --- a/components/engine/pkg/libcontainer/container.json +++ b/components/engine/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/components/engine/pkg/libcontainer/container_test.go b/components/engine/pkg/libcontainer/container_test.go index d77ce313ae..f6e991edf5 100644 --- a/components/engine/pkg/libcontainer/container_test.go +++ b/components/engine/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/components/engine/pkg/libcontainer/mount/init.go b/components/engine/pkg/libcontainer/mount/init.go index 184df1e8ec..3fb9667b16 100644 --- a/components/engine/pkg/libcontainer/mount/init.go +++ b/components/engine/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 3921a34ddb565f64fab6daa2dceef848108326a0 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) Upstream-commit: 789a8f26161ca86a721fe6b7295e3eaf9051b3a2 Component: engine --- components/engine/daemon/container.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go index 9ca94b2b56..a2dc5977a5 100644 --- a/components/engine/daemon/container.go +++ b/components/engine/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 96096d6c1fe15ca3cd803d7490e8183289d3ba2d 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) Upstream-commit: accea18920e856fc1099d85a3fced06dc7b53799 Component: engine --- .../docs/sources/installation/windows.md | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/components/engine/docs/sources/installation/windows.md b/components/engine/docs/sources/installation/windows.md index ec633508c4..189be00748 100644 --- a/components/engine/docs/sources/installation/windows.md +++ b/components/engine/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 5793c36d34848fe7174d807238faf42e8300aaca 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) Upstream-commit: cfe7211a2241b5e898af11445a2470032b0ab242 Component: engine --- components/engine/contrib/man/md/docker-run.1.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/contrib/man/md/docker-run.1.md b/components/engine/contrib/man/md/docker-run.1.md index 3cf56c24bc..ef03539ed9 100644 --- a/components/engine/contrib/man/md/docker-run.1.md +++ b/components/engine/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 a2fa77b792b6f95c11bf4de0f9b940b3cb65f590 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) Upstream-commit: 6d9e64b27bbee9bb699ebc0f0ff98bb7f56961b3 Component: engine --- .../engine/contrib/man/md/docker-run.1.md | 4 ++-- .../engine/contrib/man/old-man/docker-run.1 | 2 +- .../docs/sources/articles/runmetrics.md | 2 +- .../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/components/engine/contrib/man/md/docker-run.1.md b/components/engine/contrib/man/md/docker-run.1.md index 3cf56c24bc..2ebf82b6a5 100644 --- a/components/engine/contrib/man/md/docker-run.1.md +++ b/components/engine/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/components/engine/contrib/man/old-man/docker-run.1 b/components/engine/contrib/man/old-man/docker-run.1 index fd449374e3..ae0295943d 100644 --- a/components/engine/contrib/man/old-man/docker-run.1 +++ b/components/engine/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/components/engine/docs/sources/articles/runmetrics.md b/components/engine/docs/sources/articles/runmetrics.md index 15f53fb0d0..bf4fe21c4e 100644 --- a/components/engine/docs/sources/articles/runmetrics.md +++ b/components/engine/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/components/engine/docs/sources/examples/postgresql_service.md b/components/engine/docs/sources/examples/postgresql_service.md index 14d9e647a3..6f0a3e6bb1 100644 --- a/components/engine/docs/sources/examples/postgresql_service.md +++ b/components/engine/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/components/engine/docs/sources/examples/python_web_app.md b/components/engine/docs/sources/examples/python_web_app.md index e761003a9e..f4b76d061d 100644 --- a/components/engine/docs/sources/examples/python_web_app.md +++ b/components/engine/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/components/engine/docs/sources/use/ambassador_pattern_linking.md b/components/engine/docs/sources/use/ambassador_pattern_linking.md index 01962f19a9..755fa4dc9c 100644 --- a/components/engine/docs/sources/use/ambassador_pattern_linking.md +++ b/components/engine/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/components/engine/docs/sources/use/working_with_volumes.md b/components/engine/docs/sources/use/working_with_volumes.md index f6d1db04fa..4c0a46ff1a 100644 --- a/components/engine/docs/sources/use/working_with_volumes.md +++ b/components/engine/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/components/engine/docs/sources/use/workingwithrepository.md b/components/engine/docs/sources/use/workingwithrepository.md index 88c3f6c44e..b78d9bdc0f 100644 --- a/components/engine/docs/sources/use/workingwithrepository.md +++ b/components/engine/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 4857deee3f561b6f1334d9ae776339fe6fdc8e07 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) Upstream-commit: 7e42505083cca90d445eb8a9d7d4a4bf9ffb35a4 Component: engine --- components/engine/contrib/mkimage.sh | 105 +++++++++++++++ .../contrib/mkimage/.febootstrap-minimize | 28 ++++ .../engine/contrib/mkimage/busybox-static | 34 +++++ components/engine/contrib/mkimage/debootstrap | 125 ++++++++++++++++++ components/engine/contrib/mkimage/rinse | 25 ++++ 5 files changed, 317 insertions(+) create mode 100755 components/engine/contrib/mkimage.sh create mode 100755 components/engine/contrib/mkimage/.febootstrap-minimize create mode 100755 components/engine/contrib/mkimage/busybox-static create mode 100755 components/engine/contrib/mkimage/debootstrap create mode 100755 components/engine/contrib/mkimage/rinse diff --git a/components/engine/contrib/mkimage.sh b/components/engine/contrib/mkimage.sh new file mode 100755 index 0000000000..db4815c204 --- /dev/null +++ b/components/engine/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/components/engine/contrib/mkimage/.febootstrap-minimize b/components/engine/contrib/mkimage/.febootstrap-minimize new file mode 100755 index 0000000000..7dab4eb8b5 --- /dev/null +++ b/components/engine/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/components/engine/contrib/mkimage/busybox-static b/components/engine/contrib/mkimage/busybox-static new file mode 100755 index 0000000000..e15322b49d --- /dev/null +++ b/components/engine/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/components/engine/contrib/mkimage/debootstrap b/components/engine/contrib/mkimage/debootstrap new file mode 100755 index 0000000000..fe13ccde9f --- /dev/null +++ b/components/engine/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/components/engine/contrib/mkimage/rinse b/components/engine/contrib/mkimage/rinse new file mode 100755 index 0000000000..75eb4f0d9d --- /dev/null +++ b/components/engine/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 bcf6aedaa9c523d4fcf2cc71307099e30d5cdcd8 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) Upstream-commit: 51f707cf9dfb2dbe31cfd0cd92b922ca5d98e842 Component: engine --- components/engine/contrib/mkimage-busybox.sh | 4 ++++ components/engine/contrib/mkimage-debootstrap.sh | 4 ++++ components/engine/contrib/mkimage-rinse.sh | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/components/engine/contrib/mkimage-busybox.sh b/components/engine/contrib/mkimage-busybox.sh index c1bb88c350..cbaa567834 100755 --- a/components/engine/contrib/mkimage-busybox.sh +++ b/components/engine/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/components/engine/contrib/mkimage-debootstrap.sh b/components/engine/contrib/mkimage-debootstrap.sh index 613066e16b..808f393549 100755 --- a/components/engine/contrib/mkimage-debootstrap.sh +++ b/components/engine/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/components/engine/contrib/mkimage-rinse.sh b/components/engine/contrib/mkimage-rinse.sh index dfe9999d92..0692ae1794 100755 --- a/components/engine/contrib/mkimage-rinse.sh +++ b/components/engine/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 ff6d32db91b42bd5602fa17cbda71f80c500d2b9 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) Upstream-commit: f0489ce3a9a4023265bbbd5e9cb333e95f950088 Component: engine --- .../portallocator/portallocator.go | 183 ++++++------------ 1 file changed, 62 insertions(+), 121 deletions(-) diff --git a/components/engine/daemon/networkdriver/portallocator/portallocator.go b/components/engine/daemon/networkdriver/portallocator/portallocator.go index 9ecd447116..251ab94473 100644 --- a/components/engine/daemon/networkdriver/portallocator/portallocator.go +++ b/components/engine/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 1e8e32fb7758cbcbc4e9973d89a5e5379cc433bf 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) Upstream-commit: 91ba3379312a74132e2578d167c9e36eeb889525 Component: engine --- .../daemon/networkdriver/bridge/driver.go | 46 +++++++++++++------ 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/components/engine/daemon/networkdriver/bridge/driver.go b/components/engine/daemon/networkdriver/bridge/driver.go index c64aa423d1..a14941a8f3 100644 --- a/components/engine/daemon/networkdriver/bridge/driver.go +++ b/components/engine/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 1e7aa2e3c424efcfbad2582c6aa893e909236b99 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) Upstream-commit: 56a53c72d285b3f9fa6b34dcb81c9720a1974bde Component: engine --- .../engine/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 +++++++++++++++ components/engine/server/buildfile.go | 3 --- 6 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 components/engine/integration-cli/build_tests/TestAdd/EtcToRoot/Dockerfile diff --git a/components/engine/docs/sources/reference/builder.md b/components/engine/docs/sources/reference/builder.md index c3ba939550..cd7f4eff81 100644 --- a/components/engine/docs/sources/reference/builder.md +++ b/components/engine/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/components/engine/integration-cli/build_tests/TestAdd/EtcToRoot/Dockerfile b/components/engine/integration-cli/build_tests/TestAdd/EtcToRoot/Dockerfile new file mode 100644 index 0000000000..58c75b00f3 --- /dev/null +++ b/components/engine/integration-cli/build_tests/TestAdd/EtcToRoot/Dockerfile @@ -0,0 +1,2 @@ +FROM scratch +ADD . / diff --git a/components/engine/integration-cli/build_tests/TestAdd/SingleFileToRoot/Dockerfile b/components/engine/integration-cli/build_tests/TestAdd/SingleFileToRoot/Dockerfile index e96201d858..561dbe9c55 100644 --- a/components/engine/integration-cli/build_tests/TestAdd/SingleFileToRoot/Dockerfile +++ b/components/engine/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/components/engine/integration-cli/build_tests/TestAdd/WholeDirToRoot/Dockerfile b/components/engine/integration-cli/build_tests/TestAdd/WholeDirToRoot/Dockerfile index 2f10979487..03e9ac0b1c 100644 --- a/components/engine/integration-cli/build_tests/TestAdd/WholeDirToRoot/Dockerfile +++ b/components/engine/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/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index 041b10d8bc..455264d9f8 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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/components/engine/server/buildfile.go b/components/engine/server/buildfile.go index b37053ac16..f71f945748 100644 --- a/components/engine/server/buildfile.go +++ b/components/engine/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 7594078fbe0aec6c7a0f8369bf5ecaa409145102 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) Upstream-commit: bb431a719008ae183179d9d71090bd1224d61480 Component: engine --- .../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 components/engine/integration-cli/build_tests/TestAdd/SingleFileToRoot/test_file delete mode 100644 components/engine/integration-cli/build_tests/TestAdd/WholeDirToRoot/test_dir/test_file diff --git a/components/engine/integration-cli/build_tests/TestAdd/SingleFileToRoot/test_file b/components/engine/integration-cli/build_tests/TestAdd/SingleFileToRoot/test_file deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/components/engine/integration-cli/build_tests/TestAdd/WholeDirToRoot/test_dir/test_file b/components/engine/integration-cli/build_tests/TestAdd/WholeDirToRoot/test_dir/test_file deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index 455264d9f8..ed61afe7c4 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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 d2e4e6b06943155251f64369315047c9d6314483 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) Upstream-commit: 8e967fe8028d8362fe3dfb293a8e07a959a4dd7f Component: engine --- components/engine/daemon/execdriver/lxc/lxc_template.go | 1 - components/engine/graph/graph.go | 1 - components/engine/pkg/libcontainer/mount/init.go | 1 - 3 files changed, 3 deletions(-) diff --git a/components/engine/daemon/execdriver/lxc/lxc_template.go b/components/engine/daemon/execdriver/lxc/lxc_template.go index a16f8990e2..d660df902a 100644 --- a/components/engine/daemon/execdriver/lxc/lxc_template.go +++ b/components/engine/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/components/engine/graph/graph.go b/components/engine/graph/graph.go index a5a433d901..649b39b12f 100644 --- a/components/engine/graph/graph.go +++ b/components/engine/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/components/engine/pkg/libcontainer/mount/init.go b/components/engine/pkg/libcontainer/mount/init.go index c4148131ad..338623d84d 100644 --- a/components/engine/pkg/libcontainer/mount/init.go +++ b/components/engine/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 4f4d0b00649f3294997b8febf8d7b3e2bb11f12b 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) Upstream-commit: 0056884090726f736af2594b09aaa44e373a84d1 Component: engine --- components/engine/docs/mkdocs.yml | 2 - components/engine/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 components/engine/docs/sources/introduction/get-docker.md delete mode 100644 components/engine/docs/sources/introduction/technology.md diff --git a/components/engine/docs/mkdocs.yml b/components/engine/docs/mkdocs.yml index 32d0852791..e89262cc60 100755 --- a/components/engine/docs/mkdocs.yml +++ b/components/engine/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/components/engine/docs/sources/index.md b/components/engine/docs/sources/index.md index d582321563..7860a82f2c 100644 --- a/components/engine/docs/sources/index.md +++ b/components/engine/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/components/engine/docs/sources/introduction/get-docker.md b/components/engine/docs/sources/introduction/get-docker.md deleted file mode 100644 index e0d6f16654..0000000000 --- a/components/engine/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/components/engine/docs/sources/introduction/technology.md b/components/engine/docs/sources/introduction/technology.md deleted file mode 100644 index a724e4aae6..0000000000 --- a/components/engine/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/components/engine/docs/sources/introduction/understanding-docker.md b/components/engine/docs/sources/introduction/understanding-docker.md index 53f5e43179..1d99be7046 100644 --- a/components/engine/docs/sources/introduction/understanding-docker.md +++ b/components/engine/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/components/engine/docs/sources/introduction/working-with-docker.md b/components/engine/docs/sources/introduction/working-with-docker.md index 1b5b43fec4..1abee1ce34 100644 --- a/components/engine/docs/sources/introduction/working-with-docker.md +++ b/components/engine/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 189f43a3bafe4c92850ede084ad983e3ca9534aa 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) Upstream-commit: 811d93326bc2d9451eb444e2343bb3063611de7a Component: engine --- .../engine/daemon/execdriver/native/create.go | 13 +++- .../engine/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 +++++++- .../engine/pkg/libcontainer/cgroups/utils.go | 26 ++++++++ 7 files changed, 118 insertions(+), 49 deletions(-) diff --git a/components/engine/daemon/execdriver/native/create.go b/components/engine/daemon/execdriver/native/create.go index 76816e0b9c..d177ca7840 100644 --- a/components/engine/daemon/execdriver/native/create.go +++ b/components/engine/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/components/engine/daemon/execdriver/native/driver.go b/components/engine/daemon/execdriver/native/driver.go index 59d193ed00..425403fa4e 100644 --- a/components/engine/daemon/execdriver/native/driver.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/apply_raw.go b/components/engine/pkg/libcontainer/cgroups/fs/apply_raw.go index 65aabcc52b..fd52c6074a 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/apply_raw.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/devices.go b/components/engine/pkg/libcontainer/cgroups/fs/devices.go index a2f91eda14..fd9a39d9b9 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/devices.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/systemd/apply_nosystemd.go b/components/engine/pkg/libcontainer/cgroups/systemd/apply_nosystemd.go index 302fab773b..0fff3e4c6b 100644 --- a/components/engine/pkg/libcontainer/cgroups/systemd/apply_nosystemd.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/systemd/apply_systemd.go b/components/engine/pkg/libcontainer/cgroups/systemd/apply_systemd.go index 4d6b68b2cd..0f6beb658e 100644 --- a/components/engine/pkg/libcontainer/cgroups/systemd/apply_systemd.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/utils.go b/components/engine/pkg/libcontainer/cgroups/utils.go index 02a7f357f6..111c871477 100644 --- a/components/engine/pkg/libcontainer/cgroups/utils.go +++ b/components/engine/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 0ca8d92d2e6983ef1406f7456effc438a68e7054 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) Upstream-commit: 26b4a4920adca614f6be17a96f254f331271faf0 Component: engine --- components/engine/api/client/cli.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/api/client/cli.go b/components/engine/api/client/cli.go index 44cff0dc46..bb5d191e16 100644 --- a/components/engine/api/client/cli.go +++ b/components/engine/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 8fa886baf36c44c58c3c68ad52041984655bfbea 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) Upstream-commit: 5579bec47be6279569e69c72cccf87864af481de Component: engine --- .../integration-cli/docker_cli_links_test.go | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/components/engine/integration-cli/docker_cli_links_test.go b/components/engine/integration-cli/docker_cli_links_test.go index ed30288b7d..0480183bc7 100644 --- a/components/engine/integration-cli/docker_cli_links_test.go +++ b/components/engine/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 519b053861397292b72d3dde3b0a3a70e83cfc8b 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) Upstream-commit: 61ac745d7a7dd192948e0c1cfbdff87af7715c92 Component: engine --- components/engine/integration-cli/docker_cli_run_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/integration-cli/docker_cli_run_test.go b/components/engine/integration-cli/docker_cli_run_test.go index 154b72e27a..10b9f6a7c7 100644 --- a/components/engine/integration-cli/docker_cli_run_test.go +++ b/components/engine/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 fe98fe4b111b16aa1137f136bf013f0d79a186a1 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) Upstream-commit: df5585b273686324e9fd8269a6492409698623ee Component: engine --- components/engine/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 components/engine/vendor/src/github.com/coreos/go-systemd/login1/dbus.go create mode 100644 components/engine/vendor/src/github.com/coreos/go-systemd/login1/dbus_test.go diff --git a/components/engine/hack/vendor.sh b/components/engine/hack/vendor.sh index 7f01a236c8..8084f2eb9d 100755 --- a/components/engine/hack/vendor.sh +++ b/components/engine/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/components/engine/vendor/src/github.com/coreos/go-systemd/dbus/methods.go b/components/engine/vendor/src/github.com/coreos/go-systemd/dbus/methods.go index 11d5cda945..a60de059e6 100644 --- a/components/engine/vendor/src/github.com/coreos/go-systemd/dbus/methods.go +++ b/components/engine/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/components/engine/vendor/src/github.com/coreos/go-systemd/dbus/methods_test.go b/components/engine/vendor/src/github.com/coreos/go-systemd/dbus/methods_test.go index d943e7ebfc..8c7ab93eb3 100644 --- a/components/engine/vendor/src/github.com/coreos/go-systemd/dbus/methods_test.go +++ b/components/engine/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/components/engine/vendor/src/github.com/coreos/go-systemd/dbus/subscription.go b/components/engine/vendor/src/github.com/coreos/go-systemd/dbus/subscription.go index 3d896d896f..fcd29b6e8f 100644 --- a/components/engine/vendor/src/github.com/coreos/go-systemd/dbus/subscription.go +++ b/components/engine/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/components/engine/vendor/src/github.com/coreos/go-systemd/dbus/subscription_set_test.go b/components/engine/vendor/src/github.com/coreos/go-systemd/dbus/subscription_set_test.go index db600850c2..4ecd15376d 100644 --- a/components/engine/vendor/src/github.com/coreos/go-systemd/dbus/subscription_set_test.go +++ b/components/engine/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/components/engine/vendor/src/github.com/coreos/go-systemd/dbus/subscription_test.go b/components/engine/vendor/src/github.com/coreos/go-systemd/dbus/subscription_test.go index 6f4d0b32a6..f2b5dfc28c 100644 --- a/components/engine/vendor/src/github.com/coreos/go-systemd/dbus/subscription_test.go +++ b/components/engine/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/components/engine/vendor/src/github.com/coreos/go-systemd/journal/send.go b/components/engine/vendor/src/github.com/coreos/go-systemd/journal/send.go index a29bcbf0fa..b52e120988 100644 --- a/components/engine/vendor/src/github.com/coreos/go-systemd/journal/send.go +++ b/components/engine/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/components/engine/vendor/src/github.com/coreos/go-systemd/login1/dbus.go b/components/engine/vendor/src/github.com/coreos/go-systemd/login1/dbus.go new file mode 100644 index 0000000000..d00dd110b5 --- /dev/null +++ b/components/engine/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/components/engine/vendor/src/github.com/coreos/go-systemd/login1/dbus_test.go b/components/engine/vendor/src/github.com/coreos/go-systemd/login1/dbus_test.go new file mode 100644 index 0000000000..4439d37380 --- /dev/null +++ b/components/engine/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 9eed719a4d61d3addd78cdc67d544afd0f369cea 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) Upstream-commit: 62468a3eba4d115c16388fb4f67d6adfe4bbb0fc Component: engine --- .../engine/docs/sources/docker-io/builds.md | 125 +++++++++++++++--- .../docs/sources/use/workingwithrepository.md | 20 +-- 2 files changed, 114 insertions(+), 31 deletions(-) diff --git a/components/engine/docs/sources/docker-io/builds.md b/components/engine/docs/sources/docker-io/builds.md index 0ca058663a..1f6e002208 100644 --- a/components/engine/docs/sources/docker-io/builds.md +++ b/components/engine/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/components/engine/docs/sources/use/workingwithrepository.md b/components/engine/docs/sources/use/workingwithrepository.md index b78d9bdc0f..2b4ad613cc 100644 --- a/components/engine/docs/sources/use/workingwithrepository.md +++ b/components/engine/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 411c9abd35b0d452afdf767a2dcb2dd173cc98da 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) Upstream-commit: b1464b2daa58057bc0aaa59adc9d5ae3b0671654 Component: engine --- components/engine/MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/engine/MAINTAINERS b/components/engine/MAINTAINERS index 581953cf8d..1543c8f823 100644 --- a/components/engine/MAINTAINERS +++ b/components/engine/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 beaedbfd4c563a99bf1bbbe249f8d96dc16ae5a8 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) Upstream-commit: e7babb28ccb2d451baec9a8cb83e627390cbcdd4 Component: engine --- .../engine/docs/sources/use/working_with_links_names.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/docs/sources/use/working_with_links_names.md b/components/engine/docs/sources/use/working_with_links_names.md index ea3a25ab75..4b7030d242 100644 --- a/components/engine/docs/sources/use/working_with_links_names.md +++ b/components/engine/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 3bbd8860d6f65c33131c1c3744a6e028a5417ca7 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) Upstream-commit: 42708181b1976a768428568e664c566717ade8c4 Component: engine --- components/engine/daemon/graphdriver/devmapper/deviceset.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/daemon/graphdriver/devmapper/deviceset.go b/components/engine/daemon/graphdriver/devmapper/deviceset.go index 744f1e11b9..4de7858c1f 100644 --- a/components/engine/daemon/graphdriver/devmapper/deviceset.go +++ b/components/engine/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 022fe9a0d3728d063b3b88b8e5a39d0a6be9bd0a 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) Upstream-commit: f92d68a6cedd069c138ac83961ad54ceb937ebd0 Component: engine --- components/engine/server/server.go | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/components/engine/server/server.go b/components/engine/server/server.go index 25aa1b0058..63b110531e 100644 --- a/components/engine/server/server.go +++ b/components/engine/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 c13069d05fd2b22f1f929e3818f00ac0c2bcab5b 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) Upstream-commit: 4a33a757d540acd059ac957542527d641548e2cf Component: engine --- .../pkg/libcontainer/cgroups/fs/apply_raw.go | 4 +- .../pkg/libcontainer/cgroups/fs/blkio.go | 8 ++-- .../pkg/libcontainer/cgroups/fs/blkio_test.go | 34 +++++++-------- .../engine/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 +-- .../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/components/engine/pkg/libcontainer/cgroups/fs/apply_raw.go b/components/engine/pkg/libcontainer/cgroups/fs/apply_raw.go index fd52c6074a..be500781ec 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/apply_raw.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/blkio.go b/components/engine/pkg/libcontainer/cgroups/fs/blkio.go index 213d250d2b..5cbef69f55 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/blkio.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/blkio_test.go b/components/engine/pkg/libcontainer/cgroups/fs/blkio_test.go index 5279ac437b..d0244ad716 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/blkio_test.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/cpu.go b/components/engine/pkg/libcontainer/cgroups/fs/cpu.go index 6a7f66c72d..ad3078b3b8 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/cpu.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/cpu_test.go b/components/engine/pkg/libcontainer/cgroups/fs/cpu_test.go index 698ae921d8..cacf2f4ced 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/cpu_test.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/cpuacct.go b/components/engine/pkg/libcontainer/cgroups/fs/cpuacct.go index 3382440d2d..c52049f3e9 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/cpuacct.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/cpuset.go b/components/engine/pkg/libcontainer/cgroups/fs/cpuset.go index f9f65ba422..af2dd528d0 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/cpuset.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/devices.go b/components/engine/pkg/libcontainer/cgroups/fs/devices.go index fd9a39d9b9..00fea608f9 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/devices.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/freezer.go b/components/engine/pkg/libcontainer/cgroups/fs/freezer.go index f3b1985a51..0738ec1f09 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/freezer.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/memory.go b/components/engine/pkg/libcontainer/cgroups/fs/memory.go index 837640c088..9964f83767 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/memory.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/memory_test.go b/components/engine/pkg/libcontainer/cgroups/fs/memory_test.go index 6c1fb735e9..190d437b1c 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/memory_test.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/perf_event.go b/components/engine/pkg/libcontainer/cgroups/fs/perf_event.go index 063cec4b04..1cf1aeef12 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/perf_event.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/test_util.go b/components/engine/pkg/libcontainer/cgroups/fs/test_util.go index 11b90b21d6..333386c5de 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/test_util.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/utils.go b/components/engine/pkg/libcontainer/cgroups/fs/utils.go index 8be65c97ea..7213b5d6a0 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/utils.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/utils_test.go b/components/engine/pkg/libcontainer/cgroups/fs/utils_test.go index c8f1b0172b..4dd2243efa 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/utils_test.go +++ b/components/engine/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 e0f1623f01d64679739791a24f7de8fb0ea88f0e 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) Upstream-commit: 0abb52c7a97940dc17c45ac45226af8156d0e712 Component: engine --- .../native/template/default_template.go | 2 +- .../pkg/libcontainer/mount/nodes/nodes.go | 44 ++++++++++++------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/components/engine/daemon/execdriver/native/template/default_template.go b/components/engine/daemon/execdriver/native/template/default_template.go index cbef06fbf1..a80b609a1e 100644 --- a/components/engine/daemon/execdriver/native/template/default_template.go +++ b/components/engine/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/components/engine/pkg/libcontainer/mount/nodes/nodes.go b/components/engine/pkg/libcontainer/mount/nodes/nodes.go index 14b6f5ae57..f8e6e97450 100644 --- a/components/engine/pkg/libcontainer/mount/nodes/nodes.go +++ b/components/engine/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 cb5e9c4323881823c75cadd98e24010417c42d4f 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) Upstream-commit: b6c65df093111072bb44d42d650b591adb1bbbe0 Component: engine --- components/engine/pkg/libcontainer/container.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/components/engine/pkg/libcontainer/container.json b/components/engine/pkg/libcontainer/container.json index 7156260bc2..ba8117091d 100644 --- a/components/engine/pkg/libcontainer/container.json +++ b/components/engine/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 0b384856c1f4736ecaf4526d8212fdc54c404674 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) Upstream-commit: c8ada301baf66360af6a3445b7c2abd629da3fd1 Component: engine --- components/engine/server/buildfile.go | 97 ++++++++++++++------------- 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/components/engine/server/buildfile.go b/components/engine/server/buildfile.go index b3c614b9fa..d206664445 100644 --- a/components/engine/server/buildfile.go +++ b/components/engine/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 dae04453b90003a1c1beaaf353b020b257c10918 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) Upstream-commit: 1ce5457d57d783d6c61557681970bf0977621e78 Component: engine --- .../TestAdd/SingleFileToWorkdir/Dockerfile | 2 + .../integration-cli/docker_cli_build_test.go | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 components/engine/integration-cli/build_tests/TestAdd/SingleFileToWorkdir/Dockerfile diff --git a/components/engine/integration-cli/build_tests/TestAdd/SingleFileToWorkdir/Dockerfile b/components/engine/integration-cli/build_tests/TestAdd/SingleFileToWorkdir/Dockerfile new file mode 100644 index 0000000000..3f076718f2 --- /dev/null +++ b/components/engine/integration-cli/build_tests/TestAdd/SingleFileToWorkdir/Dockerfile @@ -0,0 +1,2 @@ +FROM busybox +ADD test_file . diff --git a/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index 8e4ba05519..4bbe4c6dc3 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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 d002ab21b5ffad21f941d9427d80c33677a98886 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) Upstream-commit: b315c380f4acd65cc0428009702f99a266f96c59 Component: engine --- components/engine/pkg/iptables/iptables.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/pkg/iptables/iptables.go b/components/engine/pkg/iptables/iptables.go index 4cdd67ef7c..6809daef63 100644 --- a/components/engine/pkg/iptables/iptables.go +++ b/components/engine/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 c375228771347454b6f8e95e82516b60f21102ea 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) Upstream-commit: c0529e5fc1a50f6fff47646bcc2461aeef20f906 Component: engine --- .../docs/sources/installation/cruxlinux.md | 68 +++++++------------ 1 file changed, 26 insertions(+), 42 deletions(-) diff --git a/components/engine/docs/sources/installation/cruxlinux.md b/components/engine/docs/sources/installation/cruxlinux.md index d1a4de7367..6f0bfff74a 100644 --- a/components/engine/docs/sources/installation/cruxlinux.md +++ b/components/engine/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 d454476d0048ab19f622a3dd9575eb01aae2e04a 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) Upstream-commit: a3ff8a98f770ae35103e3ce3c4221c00b4efcb7f Component: engine --- components/engine/daemon/daemon.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go index 210e1a540e..4ea6416ca5 100644 --- a/components/engine/daemon/daemon.go +++ b/components/engine/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 ff0983f32e0680eedab7042a9af709deaad77924 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) Upstream-commit: 2a35e41a9c825b6891440015ae95633fb84b9604 Component: engine --- components/engine/docs/mkdocs.yml | 1 + .../docs/sources/installation/centos.md | 89 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 components/engine/docs/sources/installation/centos.md diff --git a/components/engine/docs/mkdocs.yml b/components/engine/docs/mkdocs.yml index e3f9e28196..1e6b8b16d8 100755 --- a/components/engine/docs/mkdocs.yml +++ b/components/engine/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/components/engine/docs/sources/installation/centos.md b/components/engine/docs/sources/installation/centos.md new file mode 100644 index 0000000000..74d1ec858e --- /dev/null +++ b/components/engine/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 cd12b8d562f5d293b427269e0a0578ea2b58c47b 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) Upstream-commit: 0c0ffb777354ab0d65fdb92fac47272584b2d217 Component: engine --- components/engine/contrib/check-config.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/contrib/check-config.sh b/components/engine/contrib/check-config.sh index 498ede8af3..8dd618cb67 100755 --- a/components/engine/contrib/check-config.sh +++ b/components/engine/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 2c5a72f5fe6a471aa76406fb165dce6b9e6e4771 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) Upstream-commit: 1e74cd0e01009bfa8afb6058f748f282da5200d0 Component: engine --- components/engine/docs/sources/use/working_with_links_names.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/docs/sources/use/working_with_links_names.md b/components/engine/docs/sources/use/working_with_links_names.md index 4b7030d242..d69f3f1751 100644 --- a/components/engine/docs/sources/use/working_with_links_names.md +++ b/components/engine/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 7dcc66f3df14224ec08ad3ce49e2089c9a233b9e 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) Upstream-commit: 581e8e891886e6db387ed27aabda7dd8f1d14174 Component: engine --- .../engine/integration-cli/docker_cli_run_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/engine/integration-cli/docker_cli_run_test.go b/components/engine/integration-cli/docker_cli_run_test.go index 10b9f6a7c7..3be5cbd93d 100644 --- a/components/engine/integration-cli/docker_cli_run_test.go +++ b/components/engine/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 4ce695adffd7ef3d4f8c0bb0674b45a1eb2f429c 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. Upstream-commit: 9429fe6f49a4396cdd0ab2f0b88240ff0239a070 Component: engine --- components/engine/docs/sources/examples/using_supervisord.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/docs/sources/examples/using_supervisord.md b/components/engine/docs/sources/examples/using_supervisord.md index 29d2fa4525..6fc47b0c03 100644 --- a/components/engine/docs/sources/examples/using_supervisord.md +++ b/components/engine/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 46f785b005453eb4bac4df62571812d39fb210c8 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) Upstream-commit: 24872379375dd66518f09b8063698c2d1fb08df9 Component: engine --- .../integration-cli/docker_cli_run_test.go | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/components/engine/integration-cli/docker_cli_run_test.go b/components/engine/integration-cli/docker_cli_run_test.go index 3be5cbd93d..b72d8e32ca 100644 --- a/components/engine/integration-cli/docker_cli_run_test.go +++ b/components/engine/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 92526dc8b1bc227444833b57f0fb0f5ee093a02f 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) Upstream-commit: efa79a09a92b41b07d0aee3c728d98eec5120730 Component: engine --- components/engine/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/Dockerfile b/components/engine/Dockerfile index 41f0f92947..dd8ccfd1f9 100644 --- a/components/engine/Dockerfile +++ b/components/engine/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 19aaa7241bdac65fa79bb71cc9d3d0963d7c89ad 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) Upstream-commit: 886d3c939687672c6589120229c05435273b7d59 Component: engine --- components/engine/Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/engine/Dockerfile b/components/engine/Dockerfile index dd8ccfd1f9..283e0a3262 100644 --- a/components/engine/Dockerfile +++ b/components/engine/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 3391df135057647a9ea204ed9e6056a45bc918a0 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) Upstream-commit: 321b457044f287435780274bef0b4a65231892bc Component: engine --- .../engine/pkg/libcontainer/cgroups/stats.go | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 components/engine/pkg/libcontainer/cgroups/stats.go diff --git a/components/engine/pkg/libcontainer/cgroups/stats.go b/components/engine/pkg/libcontainer/cgroups/stats.go new file mode 100644 index 0000000000..fbcd5dd234 --- /dev/null +++ b/components/engine/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 418706706bd61877e180eddedf3127c9bdd00071 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) Upstream-commit: 0be44d1a0a2dfd70c35367d7ee8383feeab8d3b9 Component: engine --- .../integration-cli/docker_cli_nat_test.go | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/components/engine/integration-cli/docker_cli_nat_test.go b/components/engine/integration-cli/docker_cli_nat_test.go index 90af933be9..3816e54050 100644 --- a/components/engine/integration-cli/docker_cli_nat_test.go +++ b/components/engine/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 b33af77b2ca58c9c22f7065af50517f1fc900a7f 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) Upstream-commit: 034babf1753741184c1155a7346ecec86fc51e2c Component: engine --- components/engine/pkg/iptables/iptables.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/components/engine/pkg/iptables/iptables.go b/components/engine/pkg/iptables/iptables.go index 6809daef63..b44c452233 100644 --- a/components/engine/pkg/iptables/iptables.go +++ b/components/engine/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 c6fc71df203b6f18d4116561f8d24f3e9dd89e48 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) Upstream-commit: e89cb9a5e0105afbb0c1ba1176bda07d110295bc Component: engine --- components/engine/docs/sources/reference/builder.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/engine/docs/sources/reference/builder.md b/components/engine/docs/sources/reference/builder.md index cd7f4eff81..6a4ae4ad25 100644 --- a/components/engine/docs/sources/reference/builder.md +++ b/components/engine/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 150de13c9e06e3328d018809cc168311d08a3fd7 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) Upstream-commit: 6ec86cb6e517bfb5ded818244b9db9510a2ed0b9 Component: engine --- components/engine/daemon/daemon.go | 75 ++++++++++++------- components/engine/daemon/utils.go | 19 +---- .../pkg/namesgenerator/names-generator.go | 21 +++--- .../namesgenerator/names-generator_test.go | 28 +------ 4 files changed, 63 insertions(+), 80 deletions(-) diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go index 4ea6416ca5..78dee62523 100644 --- a/components/engine/daemon/daemon.go +++ b/components/engine/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/components/engine/daemon/utils.go b/components/engine/daemon/utils.go index 15b62e2a06..d60d985152 100644 --- a/components/engine/daemon/utils.go +++ b/components/engine/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/components/engine/pkg/namesgenerator/names-generator.go b/components/engine/pkg/namesgenerator/names-generator.go index 07fadf8171..a89e5b29e2 100644 --- a/components/engine/pkg/namesgenerator/names-generator.go +++ b/components/engine/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/components/engine/pkg/namesgenerator/names-generator_test.go b/components/engine/pkg/namesgenerator/names-generator_test.go index bcee7c86a7..2652d42ab4 100644 --- a/components/engine/pkg/namesgenerator/names-generator_test.go +++ b/components/engine/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 e52cd2deb7f3742dbf39c5c10a3343adaf8da266 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) Upstream-commit: 328d2cba116067a2ad0f161b9ee098ed024825b3 Component: engine --- components/engine/daemon/container.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go index e8bc7d478b..2ae263289d 100644 --- a/components/engine/daemon/container.go +++ b/components/engine/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 ce6aa0bad5a955c42e25b2e2bd5a2e8ca0a192d7 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) Upstream-commit: ff24a328765335d9bff1babaac4fac6cf4180af6 Component: engine --- .../integration-cli/docker_cli_cp_test.go | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/components/engine/integration-cli/docker_cli_cp_test.go b/components/engine/integration-cli/docker_cli_cp_test.go index 7421ed0fa1..aecc68edb4 100644 --- a/components/engine/integration-cli/docker_cli_cp_test.go +++ b/components/engine/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 584879665ea53b3556e06313b48844638bfba326 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) Upstream-commit: 1cb2570a276020749a0b87444b17f4e6ce78b92d Component: engine --- .../engine/docs/sources/installation/centos.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/components/engine/docs/sources/installation/centos.md b/components/engine/docs/sources/installation/centos.md index 74d1ec858e..97c3402c78 100644 --- a/components/engine/docs/sources/installation/centos.md +++ b/components/engine/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 438f9224cd230c1514aa835b45f5ef022f8cc38f 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) Upstream-commit: 955bfda99a991e71af3352d18a007d39970cc508 Component: engine --- components/engine/docs/sources/index.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/components/engine/docs/sources/index.md b/components/engine/docs/sources/index.md index 7860a82f2c..16f29f5708 100644 --- a/components/engine/docs/sources/index.md +++ b/components/engine/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 335a844d29a95f714bd573eb7a7a9f9f6b2a4777 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) Upstream-commit: c52889db27a2af09ed7f6c92f2d6c6fd9737bf63 Component: engine --- components/engine/contrib/init/upstart/docker.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/contrib/init/upstart/docker.conf b/components/engine/contrib/init/upstart/docker.conf index db00e4f47f..5a3f88887e 100644 --- a/components/engine/contrib/init/upstart/docker.conf +++ b/components/engine/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 7cbab5a63ccc531abad9f26e8ea83b20525c9b52 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) Upstream-commit: dd3319636d749bac3d57fbcd1c3cf4d31fded8b7 Component: engine --- components/engine/contrib/mkimage/debootstrap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/contrib/mkimage/debootstrap b/components/engine/contrib/mkimage/debootstrap index fe13ccde9f..4747a84d31 100755 --- a/components/engine/contrib/mkimage/debootstrap +++ b/components/engine/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 4c7d9e6500026fe66145ab99f8b7363501ed6bb8 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: ) Upstream-commit: 41a913b3906348f6ec3f1929e6d33926c84c1bf1 Component: engine --- .../engine/contrib/man/md/Dockerfile.5.md | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 components/engine/contrib/man/md/Dockerfile.5.md diff --git a/components/engine/contrib/man/md/Dockerfile.5.md b/components/engine/contrib/man/md/Dockerfile.5.md new file mode 100644 index 0000000000..c90ebf9a58 --- /dev/null +++ b/components/engine/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 7e9d849ecca20fa90555e919a10b6f8293d534b1 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.) Upstream-commit: b26bebac318803d9dbcd908ccc3be4f2fb9a88b8 Component: engine --- components/engine/docs/sources/installation/fedora.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/docs/sources/installation/fedora.md b/components/engine/docs/sources/installation/fedora.md index 93b5b05b13..20ab4477d8 100644 --- a/components/engine/docs/sources/installation/fedora.md +++ b/components/engine/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 ee7d24f0a09198f7f7d6297c3c4134c2ebaa5530 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) Upstream-commit: 11f7f0bf9b510e09a853a9fd2030a95b72462164 Component: engine --- .../integration-cli/docker_cli_build_test.go | 33 +++++++++++++++++++ .../engine/integration/buildfile_test.go | 20 ----------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index 4bbe4c6dc3..931a61b57d 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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/components/engine/integration/buildfile_test.go b/components/engine/integration/buildfile_test.go index e113cdf512..c326e94cff 100644 --- a/components/engine/integration/buildfile_test.go +++ b/components/engine/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 07ea87f779e1c960d7ecd816427b7a833fdb0909 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) Upstream-commit: 3dd4c5f49977bb9538ae1c39605895fde69c86ee Component: engine --- .../integration-cli/docker_cli_build_test.go | 14 ++++++++++++++ components/engine/integration/buildfile_test.go | 14 -------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index 931a61b57d..696f95b8e0 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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/components/engine/integration/buildfile_test.go b/components/engine/integration/buildfile_test.go index c326e94cff..89154e343e 100644 --- a/components/engine/integration/buildfile_test.go +++ b/components/engine/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 cfeef64a2d269a3a338e9b97e68e8aca14d3887b 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) Upstream-commit: 360fb3d4ea192e28a4d2579589cd16954bb959c1 Component: engine --- .../integration-cli/docker_cli_build_test.go | 16 ++++++++++++++++ components/engine/integration/buildfile_test.go | 14 -------------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index 696f95b8e0..af106a95e3 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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/components/engine/integration/buildfile_test.go b/components/engine/integration/buildfile_test.go index 89154e343e..26a41c6514 100644 --- a/components/engine/integration/buildfile_test.go +++ b/components/engine/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 23e7a90af84fa12d8ab1716fbc8f1f70c5b3455f 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) Upstream-commit: 40630ce4b65755683e496bb7dbf3779b96ec5393 Component: engine --- .../integration-cli/docker_cli_build_test.go | 20 +++++++++++++++++++ .../engine/integration/buildfile_test.go | 19 ------------------ 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index af106a95e3..b2cea9e52b 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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/components/engine/integration/buildfile_test.go b/components/engine/integration/buildfile_test.go index 26a41c6514..2416e140b5 100644 --- a/components/engine/integration/buildfile_test.go +++ b/components/engine/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 966a80b8ce953c2a4e501063966ee30815ef5c52 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) Upstream-commit: b05be686ec490ba68de82434f1ecaff01a84fcfa Component: engine --- .../integration-cli/docker_cli_build_test.go | 15 +++++++++++++ .../engine/integration/buildfile_test.go | 22 ------------------- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index b2cea9e52b..e22b09476c 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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/components/engine/integration/buildfile_test.go b/components/engine/integration/buildfile_test.go index 2416e140b5..28d5f3011c 100644 --- a/components/engine/integration/buildfile_test.go +++ b/components/engine/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 26be5cc63295e1cb698ee679495cb9d464e390b9 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) Upstream-commit: c58991f31a6a2ceaee7a3a327bfbc697b059ff51 Component: engine --- .../integration-cli/docker_cli_build_test.go | 14 +++++++++++++ .../engine/integration/buildfile_test.go | 20 ------------------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index e22b09476c..ade7efe99f 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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/components/engine/integration/buildfile_test.go b/components/engine/integration/buildfile_test.go index 28d5f3011c..5d508ff8f3 100644 --- a/components/engine/integration/buildfile_test.go +++ b/components/engine/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 ed1a6f580d8ca70ac1f405e462fb57ad4961c9a7 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) Upstream-commit: 81d16411398c20a7ddc0bbe3e5e1ce0bd95d6519 Component: engine --- .../integration-cli/docker_cli_build_test.go | 15 +++++++++++++++ components/engine/integration/buildfile_test.go | 15 --------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index ade7efe99f..4a2c5eda6d 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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/components/engine/integration/buildfile_test.go b/components/engine/integration/buildfile_test.go index 5d508ff8f3..db56537ad5 100644 --- a/components/engine/integration/buildfile_test.go +++ b/components/engine/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 d887d96ffe88e529dec5d579c1e35ef8bf6a581a 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) Upstream-commit: b25a9b7138e44950bee646c1bcb691a3dd273ea1 Component: engine --- .../integration-cli/docker_cli_build_test.go | 14 ++++++++++++++ components/engine/integration/buildfile_test.go | 16 ---------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index 4a2c5eda6d..d804ba7962 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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/components/engine/integration/buildfile_test.go b/components/engine/integration/buildfile_test.go index db56537ad5..c60bb64c7f 100644 --- a/components/engine/integration/buildfile_test.go +++ b/components/engine/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 94670aa7c33cd27967ec4f79f950b9a53aaba1a8 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 Upstream-commit: 250229605f895226fa868120f1a0ea7f4928bf8c Component: engine --- components/engine/docs/sources/installation/debian.md | 1 + components/engine/docs/sources/installation/ubuntulinux.md | 1 + 2 files changed, 2 insertions(+) diff --git a/components/engine/docs/sources/installation/debian.md b/components/engine/docs/sources/installation/debian.md index 3deda47637..8f4f31c29d 100644 --- a/components/engine/docs/sources/installation/debian.md +++ b/components/engine/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/components/engine/docs/sources/installation/ubuntulinux.md b/components/engine/docs/sources/installation/ubuntulinux.md index bf9992f716..876eb5d290 100644 --- a/components/engine/docs/sources/installation/ubuntulinux.md +++ b/components/engine/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 aef0b90283831207398a55e8d4356558c6eac96b 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) Upstream-commit: b7e88250f421d34d25f89737c65954150248ee28 Component: engine --- components/engine/docs/sources/installation/centos.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/components/engine/docs/sources/installation/centos.md b/components/engine/docs/sources/installation/centos.md index 97c3402c78..7d2a9ae5d7 100644 --- a/components/engine/docs/sources/installation/centos.md +++ b/components/engine/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 de60cddde3b78b619000edc1bbc06a0a24c06549 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) Upstream-commit: 838e69e11e15dcf6cc1bc73f26602f814ce3698c Component: engine --- .../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 +------ .../sources/installation/install_header.inc | 7 ----- .../installation/install_unofficial.inc | 7 ----- .../docs/sources/installation/openSUSE.md | 14 +--------- .../docs/sources/installation/rackspace.md | 8 +----- .../engine/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 components/engine/docs/sources/installation/install_header.inc delete mode 100644 components/engine/docs/sources/installation/install_unofficial.inc diff --git a/components/engine/docs/sources/installation/amazon.md b/components/engine/docs/sources/installation/amazon.md index 61a12d6b43..f7abec1550 100644 --- a/components/engine/docs/sources/installation/amazon.md +++ b/components/engine/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/components/engine/docs/sources/installation/archlinux.md b/components/engine/docs/sources/installation/archlinux.md index c6d4f73fb8..81cc21fb02 100644 --- a/components/engine/docs/sources/installation/archlinux.md +++ b/components/engine/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/components/engine/docs/sources/installation/binaries.md b/components/engine/docs/sources/installation/binaries.md index 36aa0ae249..494530a239 100644 --- a/components/engine/docs/sources/installation/binaries.md +++ b/components/engine/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/components/engine/docs/sources/installation/cruxlinux.md b/components/engine/docs/sources/installation/cruxlinux.md index 6f0bfff74a..28efde376a 100644 --- a/components/engine/docs/sources/installation/cruxlinux.md +++ b/components/engine/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/components/engine/docs/sources/installation/debian.md b/components/engine/docs/sources/installation/debian.md index 8f4f31c29d..def8cb77cf 100644 --- a/components/engine/docs/sources/installation/debian.md +++ b/components/engine/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/components/engine/docs/sources/installation/fedora.md b/components/engine/docs/sources/installation/fedora.md index 20ab4477d8..d615d4f6c0 100644 --- a/components/engine/docs/sources/installation/fedora.md +++ b/components/engine/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/components/engine/docs/sources/installation/frugalware.md b/components/engine/docs/sources/installation/frugalware.md index eb409d8d39..2c2f922613 100644 --- a/components/engine/docs/sources/installation/frugalware.md +++ b/components/engine/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/components/engine/docs/sources/installation/gentoolinux.md b/components/engine/docs/sources/installation/gentoolinux.md index 92329dca90..62fdc9f00e 100644 --- a/components/engine/docs/sources/installation/gentoolinux.md +++ b/components/engine/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/components/engine/docs/sources/installation/google.md b/components/engine/docs/sources/installation/google.md index 29ffe0d73f..bb8a961470 100644 --- a/components/engine/docs/sources/installation/google.md +++ b/components/engine/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/components/engine/docs/sources/installation/install_header.inc b/components/engine/docs/sources/installation/install_header.inc deleted file mode 100644 index c9b9e4c494..0000000000 --- a/components/engine/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/components/engine/docs/sources/installation/install_unofficial.inc b/components/engine/docs/sources/installation/install_unofficial.inc deleted file mode 100644 index 8d121918b5..0000000000 --- a/components/engine/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/components/engine/docs/sources/installation/openSUSE.md b/components/engine/docs/sources/installation/openSUSE.md index 07f2ca43d2..d2c8e54848 100644 --- a/components/engine/docs/sources/installation/openSUSE.md +++ b/components/engine/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/components/engine/docs/sources/installation/rackspace.md b/components/engine/docs/sources/installation/rackspace.md index c93af388ed..1aa969d1e5 100644 --- a/components/engine/docs/sources/installation/rackspace.md +++ b/components/engine/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/components/engine/docs/sources/installation/rhel.md b/components/engine/docs/sources/installation/rhel.md index 632743a2b9..7cb71ec0d6 100644 --- a/components/engine/docs/sources/installation/rhel.md +++ b/components/engine/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/components/engine/docs/sources/installation/softlayer.md b/components/engine/docs/sources/installation/softlayer.md index 11a192c61a..80b12741ff 100644 --- a/components/engine/docs/sources/installation/softlayer.md +++ b/components/engine/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/components/engine/docs/sources/installation/ubuntulinux.md b/components/engine/docs/sources/installation/ubuntulinux.md index 876eb5d290..daba2c4ad1 100644 --- a/components/engine/docs/sources/installation/ubuntulinux.md +++ b/components/engine/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 2d0986f24f21ecdce3a4310728cbbdf21bda0a97 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) Upstream-commit: 2bbf87504bc9eb6ee9a4694ce4d1d98287bccbc9 Component: engine --- .../engine/contrib/man/md/Dockerfile.5.md | 195 ++++++++++++++++-- 1 file changed, 180 insertions(+), 15 deletions(-) diff --git a/components/engine/contrib/man/md/Dockerfile.5.md b/components/engine/contrib/man/md/Dockerfile.5.md index c90ebf9a58..06970eb871 100644 --- a/components/engine/contrib/man/md/Dockerfile.5.md +++ b/components/engine/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 beb63ec76f418704a819502510954eb88f90bbfa 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 Upstream-commit: 2cb184ade59ccb578d50f5ff535d1d02e7855563 Component: engine --- .../engine/contrib/man/md/docker-attach.1.md | 7 ++-- .../engine/contrib/man/md/docker-build.1.md | 37 ++++++++++++++++++- .../engine/contrib/man/md/docker-run.1.md | 5 ++- .../engine/contrib/man/md/docker-tag.1.md | 9 +++-- 4 files changed, 49 insertions(+), 9 deletions(-) diff --git a/components/engine/contrib/man/md/docker-attach.1.md b/components/engine/contrib/man/md/docker-attach.1.md index 2755fd27f5..5a3b7a2856 100644 --- a/components/engine/contrib/man/md/docker-attach.1.md +++ b/components/engine/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/components/engine/contrib/man/md/docker-build.1.md b/components/engine/contrib/man/md/docker-build.1.md index b3e9a2842e..f242bdd2c9 100644 --- a/components/engine/contrib/man/md/docker-build.1.md +++ b/components/engine/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/components/engine/contrib/man/md/docker-run.1.md b/components/engine/contrib/man/md/docker-run.1.md index 2ebf82b6a5..72b01419bf 100644 --- a/components/engine/contrib/man/md/docker-run.1.md +++ b/components/engine/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/components/engine/contrib/man/md/docker-tag.1.md b/components/engine/contrib/man/md/docker-tag.1.md index 49f5a6c4d1..687b6ddb47 100644 --- a/components/engine/contrib/man/md/docker-tag.1.md +++ b/components/engine/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 20b77a0abf71125b318fb6972bf9887d24fe84f2 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 Upstream-commit: a14a88e53dc71b1013d7b629d23d3908d15d57f4 Component: engine --- .../engine/contrib/man/md/docker-build.1.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/components/engine/contrib/man/md/docker-build.1.md b/components/engine/contrib/man/md/docker-build.1.md index f242bdd2c9..da4f5c017e 100644 --- a/components/engine/contrib/man/md/docker-build.1.md +++ b/components/engine/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 d8e73d4132a287840a67fad0d03566466a27ed8a 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) Upstream-commit: ef94ac7d2fd42a09c99567b0393fb48b9d782a9e Component: engine --- .../networkdriver/ipallocator/allocator.go | 114 +++++++----------- .../ipallocator/allocator_test.go | 17 +++ 2 files changed, 60 insertions(+), 71 deletions(-) diff --git a/components/engine/daemon/networkdriver/ipallocator/allocator.go b/components/engine/daemon/networkdriver/ipallocator/allocator.go index 4bcce65174..f154b0bd49 100644 --- a/components/engine/daemon/networkdriver/ipallocator/allocator.go +++ b/components/engine/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/components/engine/daemon/networkdriver/ipallocator/allocator_test.go b/components/engine/daemon/networkdriver/ipallocator/allocator_test.go index 2b63d9a03c..6897a0a44b 100644 --- a/components/engine/daemon/networkdriver/ipallocator/allocator_test.go +++ b/components/engine/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 05b57dfef1eb37dd15f6e0179aee5ac178068920 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) Upstream-commit: 92e41a02ce40c7d3446b8ca7ec5c5671ac3d8917 Component: engine --- components/engine/daemon/container.go | 1 + components/engine/daemon/execdriver/lxc/driver.go | 1 + components/engine/pkg/libcontainer/nsinit/exec.go | 5 +++++ components/engine/pkg/libcontainer/nsinit/init.go | 4 +++- 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go index 2ae263289d..20b86f7e20 100644 --- a/components/engine/daemon/container.go +++ b/components/engine/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/components/engine/daemon/execdriver/lxc/driver.go b/components/engine/daemon/execdriver/lxc/driver.go index 6b2b2cc46b..2e84fcc84d 100644 --- a/components/engine/daemon/execdriver/lxc/driver.go +++ b/components/engine/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/components/engine/pkg/libcontainer/nsinit/exec.go b/components/engine/pkg/libcontainer/nsinit/exec.go index fbc7512047..3545278738 100644 --- a/components/engine/pkg/libcontainer/nsinit/exec.go +++ b/components/engine/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/components/engine/pkg/libcontainer/nsinit/init.go b/components/engine/pkg/libcontainer/nsinit/init.go index 3012106769..509f3a2796 100644 --- a/components/engine/pkg/libcontainer/nsinit/init.go +++ b/components/engine/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 efa2549ad13b3fed2dce9d26427834c5501ec8de 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) Upstream-commit: b01c3283fafa01228a566e128dbf4b016c308d04 Component: engine --- components/engine/pkg/libcontainer/nsinit/exec.go | 1 - components/engine/pkg/libcontainer/nsinit/tty_term.go | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/pkg/libcontainer/nsinit/exec.go b/components/engine/pkg/libcontainer/nsinit/exec.go index 3545278738..0813470a90 100644 --- a/components/engine/pkg/libcontainer/nsinit/exec.go +++ b/components/engine/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/components/engine/pkg/libcontainer/nsinit/tty_term.go b/components/engine/pkg/libcontainer/nsinit/tty_term.go index fcbd085c82..fc6e1ab499 100644 --- a/components/engine/pkg/libcontainer/nsinit/tty_term.go +++ b/components/engine/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 3ed3d24076dda72e64b8eef2fd47ec27966f4988 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) Upstream-commit: 8b77e0183eaa7b3a87921f9655e2799d300bf775 Component: engine --- components/engine/daemon/container.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go index 2ae263289d..a4bc835390 100644 --- a/components/engine/daemon/container.go +++ b/components/engine/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 562071c97e06bb64b46426bed83d84e2c969ace5 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) Upstream-commit: b9de22e82860a7e389f356d349ccb61b7d530c54 Component: engine --- components/engine/pkg/libcontainer/nsinit/exec.go | 6 +++--- components/engine/pkg/libcontainer/nsinit/init.go | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/components/engine/pkg/libcontainer/nsinit/exec.go b/components/engine/pkg/libcontainer/nsinit/exec.go index 0813470a90..f266303f8c 100644 --- a/components/engine/pkg/libcontainer/nsinit/exec.go +++ b/components/engine/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/components/engine/pkg/libcontainer/nsinit/init.go b/components/engine/pkg/libcontainer/nsinit/init.go index 509f3a2796..8139865a02 100644 --- a/components/engine/pkg/libcontainer/nsinit/init.go +++ b/components/engine/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 0ef8d97b63ae7d9ee7c13fb8a16e4b762d68bce0 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) Upstream-commit: 5310e8575f5a251000bbecd6d998eb11cb95fe04 Component: engine --- components/engine/daemon/execdriver/lxc/driver.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/engine/daemon/execdriver/lxc/driver.go b/components/engine/daemon/execdriver/lxc/driver.go index 2e84fcc84d..54f1054191 100644 --- a/components/engine/daemon/execdriver/lxc/driver.go +++ b/components/engine/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 dc559e048aaac5f187a2ff0f361853abb84f5c2a 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) Upstream-commit: f2baa364a13ea3e22719e3637bf6f5f42e5dc677 Component: engine --- components/engine/utils/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go index 1a140f650d..0495dc6fa8 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/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 9f78769fba6649fce528205012bf674e96246e60 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) Upstream-commit: 5f5d66ade63f8040dd14114f2c7161b16a806c5c Component: engine --- .../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 .../engine/docs/sources/installation/mac.md | 196 ++++-------------- .../docs/sources/installation/windows.md | 99 ++++----- 5 files changed, 86 insertions(+), 209 deletions(-) create mode 100644 components/engine/docs/sources/installation/images/osx-installer.png create mode 100644 components/engine/docs/sources/installation/images/windows-boot2docker-start.png create mode 100644 components/engine/docs/sources/installation/images/windows-installer.png diff --git a/components/engine/docs/sources/installation/images/osx-installer.png b/components/engine/docs/sources/installation/images/osx-installer.png new file mode 100644 index 0000000000000000000000000000000000000000..635ac354edf9c96c8362e14c394dcad4f94653d6 GIT binary patch literal 117896 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

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/components/engine/docs/sources/installation/images/windows-installer.png b/components/engine/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/components/engine/docs/sources/installation/windows.md b/components/engine/docs/sources/installation/windows.md index 189be00748..2980cad147 100644 --- a/components/engine/docs/sources/installation/windows.md +++ b/components/engine/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 687a71e998f596d7caf63b9c430f78d7a2c6c4d6 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) Upstream-commit: 1de52caedca11c083e92039e2ecd8671944dfc47 Component: engine --- components/engine/daemon/container.go | 28 +++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go index 647f4c187d..9d396c2812 100644 --- a/components/engine/daemon/container.go +++ b/components/engine/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 17bb4fbe20ae0227b801afa86744495e8d0f89f2 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) Upstream-commit: 147ad3480da6c9e966b1db0d9d607dc6b1483327 Component: engine --- components/engine/CONTRIBUTING.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/components/engine/CONTRIBUTING.md b/components/engine/CONTRIBUTING.md index f59e49d465..cb0d333479 100644 --- a/components/engine/CONTRIBUTING.md +++ b/components/engine/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 8f1ec933f0403d3324ceaa4ee5f3e7745a45ce56 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) Upstream-commit: b64535166f4e930121bf9cb624bf2d044c97e472 Component: engine --- .../engine/docs/sources/reference/commandline/cli.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/engine/docs/sources/reference/commandline/cli.md b/components/engine/docs/sources/reference/commandline/cli.md index 2a1be89086..e0c5eb1069 100644 --- a/components/engine/docs/sources/reference/commandline/cli.md +++ b/components/engine/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 5ea71372783ba547cbd62294ebfa847ef9de9f80 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) Upstream-commit: 69640123826cf73d3d83182cb81e5de4ad0cc3a7 Component: engine --- .../daemon/graphdriver/devmapper/devmapper.go | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/components/engine/daemon/graphdriver/devmapper/devmapper.go b/components/engine/daemon/graphdriver/devmapper/devmapper.go index 315f0c0db5..d78ae1ef8b 100644 --- a/components/engine/daemon/graphdriver/devmapper/devmapper.go +++ b/components/engine/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 b049a6c18114441ae4d34daf4d85248617ecab0f 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) Upstream-commit: 02f4ae6c56474b1f4e747916812b38134d503349 Component: engine --- components/engine/registry/registry.go | 11 +++++++ components/engine/server/server.go | 44 +++++++++++++++++++------- components/engine/utils/timeoutconn.go | 26 +++++++++++++++ 3 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 components/engine/utils/timeoutconn.go diff --git a/components/engine/registry/registry.go b/components/engine/registry/registry.go index 2e3e7e03a7..3d0a3ed2da 100644 --- a/components/engine/registry/registry.go +++ b/components/engine/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/components/engine/server/server.go b/components/engine/server/server.go index 6d398807bd..0deda0d955 100644 --- a/components/engine/server/server.go +++ b/components/engine/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/components/engine/utils/timeoutconn.go b/components/engine/utils/timeoutconn.go new file mode 100644 index 0000000000..a3231c7ee3 --- /dev/null +++ b/components/engine/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 9dd5fe97c9a767bf580333d8cfe3233508186600 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) Upstream-commit: 31f0a61a3dfabe363d08d4ff55e8a1efb29d84eb Component: engine --- .../engine/pkg/collections/orderedintset.go | 95 ------------------- .../pkg/collections/orderedintset_test.go | 71 -------------- 2 files changed, 166 deletions(-) delete mode 100644 components/engine/pkg/collections/orderedintset.go delete mode 100644 components/engine/pkg/collections/orderedintset_test.go diff --git a/components/engine/pkg/collections/orderedintset.go b/components/engine/pkg/collections/orderedintset.go deleted file mode 100644 index 7442f2e93f..0000000000 --- a/components/engine/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/components/engine/pkg/collections/orderedintset_test.go b/components/engine/pkg/collections/orderedintset_test.go deleted file mode 100644 index 0ac4ca5455..0000000000 --- a/components/engine/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 a9a33a3f258a3adb02f8a4a82bf057ce81279a3d 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) Upstream-commit: fd58524f81031eec112b5e9bd52bfaa186fc9c20 Component: engine --- components/engine/pkg/system/calls_linux.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/components/engine/pkg/system/calls_linux.go b/components/engine/pkg/system/calls_linux.go index faead0114e..6986051e1d 100644 --- a/components/engine/pkg/system/calls_linux.go +++ b/components/engine/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 9a698bd723ebc08a967bc81ccfd13e5ef3996a6a 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) Upstream-commit: 0563453b918b47c1f9d1e05b8650d2c8bf7ac3af Component: engine --- .../engine/pkg/libcontainer/nsinit/init.go | 24 ++++++++++++++++--- .../security/capabilities/capabilities.go | 19 +++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/components/engine/pkg/libcontainer/nsinit/init.go b/components/engine/pkg/libcontainer/nsinit/init.go index 8139865a02..0f1f484187 100644 --- a/components/engine/pkg/libcontainer/nsinit/init.go +++ b/components/engine/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/components/engine/pkg/libcontainer/security/capabilities/capabilities.go b/components/engine/pkg/libcontainer/security/capabilities/capabilities.go index ba72070f50..64ea961a18 100644 --- a/components/engine/pkg/libcontainer/security/capabilities/capabilities.go +++ b/components/engine/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 be791faeb851af7c21ceac8a56f6652555160615 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) Upstream-commit: 50417f6abc6d6de7cf32e956d538fd9103c5f9b4 Component: engine --- components/engine/api/client/commands.go | 4 ++-- components/engine/contrib/man/md/Dockerfile.5.md | 2 +- components/engine/contrib/man/md/docker-build.1.md | 2 +- components/engine/docs/sources/reference/builder.md | 2 +- components/engine/docs/sources/reference/commandline/cli.md | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/components/engine/api/client/commands.go b/components/engine/api/client/commands.go index 67c5aec2dc..4d191baf5e 100644 --- a/components/engine/api/client/commands.go +++ b/components/engine/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/components/engine/contrib/man/md/Dockerfile.5.md b/components/engine/contrib/man/md/Dockerfile.5.md index 06970eb871..2e964c9c57 100644 --- a/components/engine/contrib/man/md/Dockerfile.5.md +++ b/components/engine/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/components/engine/contrib/man/md/docker-build.1.md b/components/engine/contrib/man/md/docker-build.1.md index b3e9a2842e..455a21d50e 100644 --- a/components/engine/contrib/man/md/docker-build.1.md +++ b/components/engine/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/components/engine/docs/sources/reference/builder.md b/components/engine/docs/sources/reference/builder.md index 6a4ae4ad25..27af21d547 100644 --- a/components/engine/docs/sources/reference/builder.md +++ b/components/engine/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/components/engine/docs/sources/reference/commandline/cli.md b/components/engine/docs/sources/reference/commandline/cli.md index e0c5eb1069..f6a1b04b01 100644 --- a/components/engine/docs/sources/reference/commandline/cli.md +++ b/components/engine/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 fc7b9b154dd283604192ddb0024f10bc6d7680c7 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) Upstream-commit: bf7f360dcac38037d5c4f9e2e90d01adc240ed2b Component: engine --- .../engine/daemon/execdriver/lxc/init.go | 2 +- .../engine/pkg/libcontainer/container.go | 24 ++++ .../engine/pkg/libcontainer/container.json | 10 ++ .../engine/pkg/libcontainer/container_test.go | 5 + .../pkg/libcontainer/network/network.go | 4 +- .../engine/pkg/libcontainer/network/veth.go | 22 ++-- .../engine/pkg/libcontainer/nsinit/init.go | 13 ++ .../engine/pkg/netlink/netlink_linux.go | 113 +++++++++++++++--- .../engine/pkg/netlink/netlink_unsupported.go | 5 +- 9 files changed, 166 insertions(+), 32 deletions(-) diff --git a/components/engine/daemon/execdriver/lxc/init.go b/components/engine/daemon/execdriver/lxc/init.go index e21e717645..5e77190e5a 100644 --- a/components/engine/daemon/execdriver/lxc/init.go +++ b/components/engine/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/components/engine/pkg/libcontainer/container.go b/components/engine/pkg/libcontainer/container.go index 6734bfd590..27a4235de1 100644 --- a/components/engine/pkg/libcontainer/container.go +++ b/components/engine/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/components/engine/pkg/libcontainer/container.json b/components/engine/pkg/libcontainer/container.json index 7156260bc2..3487b47175 100644 --- a/components/engine/pkg/libcontainer/container.json +++ b/components/engine/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/components/engine/pkg/libcontainer/container_test.go b/components/engine/pkg/libcontainer/container_test.go index f6e991edf5..23b15d2cf3 100644 --- a/components/engine/pkg/libcontainer/container_test.go +++ b/components/engine/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/components/engine/pkg/libcontainer/network/network.go b/components/engine/pkg/libcontainer/network/network.go index f8dee45278..85a28dc37c 100644 --- a/components/engine/pkg/libcontainer/network/network.go +++ b/components/engine/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/components/engine/pkg/libcontainer/network/veth.go b/components/engine/pkg/libcontainer/network/veth.go index 3df0cd61ee..d3be221c60 100644 --- a/components/engine/pkg/libcontainer/network/veth.go +++ b/components/engine/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/components/engine/pkg/libcontainer/nsinit/init.go b/components/engine/pkg/libcontainer/nsinit/init.go index 3012106769..95d07a4961 100644 --- a/components/engine/pkg/libcontainer/nsinit/init.go +++ b/components/engine/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/components/engine/pkg/netlink/netlink_linux.go b/components/engine/pkg/netlink/netlink_linux.go index 6de293d42a..21d4593620 100644 --- a/components/engine/pkg/netlink/netlink_linux.go +++ b/components/engine/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/components/engine/pkg/netlink/netlink_unsupported.go b/components/engine/pkg/netlink/netlink_unsupported.go index 8a5531b9ef..1359345662 100644 --- a/components/engine/pkg/netlink/netlink_unsupported.go +++ b/components/engine/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 3731289bf5d59d6d3ea9c51602961463926006c5 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) Upstream-commit: 35d08bdd01e3c63414d7998efc0813803e2ba5d9 Component: engine --- components/engine/pkg/libcontainer/console/console.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/engine/pkg/libcontainer/console/console.go b/components/engine/pkg/libcontainer/console/console.go index 62edbfde8a..5f06aea225 100644 --- a/components/engine/pkg/libcontainer/console/console.go +++ b/components/engine/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 503e230354f8b7a0053d4e2251066dcdc8bf26f6 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) Upstream-commit: 7f5cd76824b500418ed168dfcfeb73de8badcb51 Component: engine --- .../pkg/libcontainer/console/console.go | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/components/engine/pkg/libcontainer/console/console.go b/components/engine/pkg/libcontainer/console/console.go index 5f06aea225..79a480418f 100644 --- a/components/engine/pkg/libcontainer/console/console.go +++ b/components/engine/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 e498aa81c88d49b352f50b8205fc8ef59898c4bc 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) Upstream-commit: 180c2a678510a93a442f1bcdb6a47287ec88ccd6 Component: engine --- .../engine/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 ++++++++++++++++++ components/engine/server/buildfile.go | 36 ++-- 16 files changed, 301 insertions(+), 13 deletions(-) create mode 100644 components/engine/integration-cli/build_tests/TestCopy/DirContentToExistDir/Dockerfile create mode 100644 components/engine/integration-cli/build_tests/TestCopy/DirContentToExistDir/test_dir/test_file create mode 100644 components/engine/integration-cli/build_tests/TestCopy/DirContentToRoot/Dockerfile create mode 100644 components/engine/integration-cli/build_tests/TestCopy/DirContentToRoot/test_dir/test_file create mode 100644 components/engine/integration-cli/build_tests/TestCopy/DisallowRemote/Dockerfile create mode 100644 components/engine/integration-cli/build_tests/TestCopy/EtcToRoot/Dockerfile create mode 100644 components/engine/integration-cli/build_tests/TestCopy/SingleFileToExistDir/Dockerfile create mode 100644 components/engine/integration-cli/build_tests/TestCopy/SingleFileToExistDir/test_file create mode 100644 components/engine/integration-cli/build_tests/TestCopy/SingleFileToNonExistDir/Dockerfile create mode 100644 components/engine/integration-cli/build_tests/TestCopy/SingleFileToNonExistDir/test_file create mode 100644 components/engine/integration-cli/build_tests/TestCopy/SingleFileToRoot/Dockerfile create mode 100644 components/engine/integration-cli/build_tests/TestCopy/SingleFileToWorkdir/Dockerfile create mode 100644 components/engine/integration-cli/build_tests/TestCopy/WholeDirToRoot/Dockerfile diff --git a/components/engine/docs/sources/reference/builder.md b/components/engine/docs/sources/reference/builder.md index 6a4ae4ad25..235d92e1b7 100644 --- a/components/engine/docs/sources/reference/builder.md +++ b/components/engine/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/components/engine/integration-cli/build_tests/TestCopy/DirContentToExistDir/Dockerfile b/components/engine/integration-cli/build_tests/TestCopy/DirContentToExistDir/Dockerfile new file mode 100644 index 0000000000..d63e8538bb --- /dev/null +++ b/components/engine/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/components/engine/integration-cli/build_tests/TestCopy/DirContentToExistDir/test_dir/test_file b/components/engine/integration-cli/build_tests/TestCopy/DirContentToExistDir/test_dir/test_file new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/engine/integration-cli/build_tests/TestCopy/DirContentToRoot/Dockerfile b/components/engine/integration-cli/build_tests/TestCopy/DirContentToRoot/Dockerfile new file mode 100644 index 0000000000..45df77e563 --- /dev/null +++ b/components/engine/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/components/engine/integration-cli/build_tests/TestCopy/DirContentToRoot/test_dir/test_file b/components/engine/integration-cli/build_tests/TestCopy/DirContentToRoot/test_dir/test_file new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/engine/integration-cli/build_tests/TestCopy/DisallowRemote/Dockerfile b/components/engine/integration-cli/build_tests/TestCopy/DisallowRemote/Dockerfile new file mode 100644 index 0000000000..e6bc0c0dd2 --- /dev/null +++ b/components/engine/integration-cli/build_tests/TestCopy/DisallowRemote/Dockerfile @@ -0,0 +1,2 @@ +FROM busybox +COPY https://index.docker.io/robots.txt / diff --git a/components/engine/integration-cli/build_tests/TestCopy/EtcToRoot/Dockerfile b/components/engine/integration-cli/build_tests/TestCopy/EtcToRoot/Dockerfile new file mode 100644 index 0000000000..b4f319f80f --- /dev/null +++ b/components/engine/integration-cli/build_tests/TestCopy/EtcToRoot/Dockerfile @@ -0,0 +1,2 @@ +FROM scratch +COPY . / diff --git a/components/engine/integration-cli/build_tests/TestCopy/SingleFileToExistDir/Dockerfile b/components/engine/integration-cli/build_tests/TestCopy/SingleFileToExistDir/Dockerfile new file mode 100644 index 0000000000..3edfe661d4 --- /dev/null +++ b/components/engine/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/components/engine/integration-cli/build_tests/TestCopy/SingleFileToExistDir/test_file b/components/engine/integration-cli/build_tests/TestCopy/SingleFileToExistDir/test_file new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/engine/integration-cli/build_tests/TestCopy/SingleFileToNonExistDir/Dockerfile b/components/engine/integration-cli/build_tests/TestCopy/SingleFileToNonExistDir/Dockerfile new file mode 100644 index 0000000000..33b65a62c7 --- /dev/null +++ b/components/engine/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/components/engine/integration-cli/build_tests/TestCopy/SingleFileToNonExistDir/test_file b/components/engine/integration-cli/build_tests/TestCopy/SingleFileToNonExistDir/test_file new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/engine/integration-cli/build_tests/TestCopy/SingleFileToRoot/Dockerfile b/components/engine/integration-cli/build_tests/TestCopy/SingleFileToRoot/Dockerfile new file mode 100644 index 0000000000..38fd09026d --- /dev/null +++ b/components/engine/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/components/engine/integration-cli/build_tests/TestCopy/SingleFileToWorkdir/Dockerfile b/components/engine/integration-cli/build_tests/TestCopy/SingleFileToWorkdir/Dockerfile new file mode 100644 index 0000000000..ba2d797e35 --- /dev/null +++ b/components/engine/integration-cli/build_tests/TestCopy/SingleFileToWorkdir/Dockerfile @@ -0,0 +1,2 @@ +FROM busybox +COPY test_file . diff --git a/components/engine/integration-cli/build_tests/TestCopy/WholeDirToRoot/Dockerfile b/components/engine/integration-cli/build_tests/TestCopy/WholeDirToRoot/Dockerfile new file mode 100644 index 0000000000..91be29fe7a --- /dev/null +++ b/components/engine/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/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index d804ba7962..f2bce99cfd 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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/components/engine/server/buildfile.go b/components/engine/server/buildfile.go index d206664445..26fc49890a 100644 --- a/components/engine/server/buildfile.go +++ b/components/engine/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 a8abe1db1ceeba6bda664530fd16700e36d92cd1 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) Upstream-commit: 5d04b9deafc5ce173364a24881f8e950c9430be0 Component: engine --- .../security/restrict/restrict.go | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/components/engine/pkg/libcontainer/security/restrict/restrict.go b/components/engine/pkg/libcontainer/security/restrict/restrict.go index a22a1aa73e..2dadc4fff6 100644 --- a/components/engine/pkg/libcontainer/security/restrict/restrict.go +++ b/components/engine/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 62daf025f0c020045200be83a7b44bff8d038381 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) Upstream-commit: a0505edc9a890b3d4cdf5cf1850273c250c3dc41 Component: engine --- components/engine/api/client/commands.go | 4 ++-- components/engine/contrib/completion/fish/docker.fish | 2 +- components/engine/contrib/man/md/docker-build.1.md | 2 +- components/engine/contrib/man/md/docker.1.md | 2 +- components/engine/contrib/man/old-man/docker-build.1 | 2 +- components/engine/contrib/man/old-man/docker.1 | 2 +- .../docs/sources/examples/cfengine_process_management.md | 4 ++-- components/engine/docs/sources/examples/using_supervisord.md | 4 ++-- .../engine/docs/sources/introduction/working-with-docker.md | 2 +- components/engine/docs/sources/reference/api/README.md | 2 +- components/engine/docs/sources/reference/commandline/cli.md | 2 +- components/engine/docs/sources/use/basics.md | 4 ++-- components/engine/hack/RELEASE-CHECKLIST.md | 2 +- components/engine/hack/make.sh | 2 +- 14 files changed, 18 insertions(+), 18 deletions(-) diff --git a/components/engine/api/client/commands.go b/components/engine/api/client/commands.go index 67c5aec2dc..6080595849 100644 --- a/components/engine/api/client/commands.go +++ b/components/engine/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/components/engine/contrib/completion/fish/docker.fish b/components/engine/contrib/completion/fish/docker.fish index e3bb72aebe..7ea478d051 100644 --- a/components/engine/contrib/completion/fish/docker.fish +++ b/components/engine/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/components/engine/contrib/man/md/docker-build.1.md b/components/engine/contrib/man/md/docker-build.1.md index b3e9a2842e..b01bb9f8b7 100644 --- a/components/engine/contrib/man/md/docker-build.1.md +++ b/components/engine/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/components/engine/contrib/man/md/docker.1.md b/components/engine/contrib/man/md/docker.1.md index 0071a71c92..c73b083e61 100644 --- a/components/engine/contrib/man/md/docker.1.md +++ b/components/engine/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/components/engine/contrib/man/old-man/docker-build.1 b/components/engine/contrib/man/old-man/docker-build.1 index 6546b7be2a..2d189eb0e3 100644 --- a/components/engine/contrib/man/old-man/docker-build.1 +++ b/components/engine/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/components/engine/contrib/man/old-man/docker.1 b/components/engine/contrib/man/old-man/docker.1 index 4a36e5baf5..5a55865120 100644 --- a/components/engine/contrib/man/old-man/docker.1 +++ b/components/engine/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/components/engine/docs/sources/examples/cfengine_process_management.md b/components/engine/docs/sources/examples/cfengine_process_management.md index 0c7b6a8a1f..ee5ba238a0 100644 --- a/components/engine/docs/sources/examples/cfengine_process_management.md +++ b/components/engine/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/components/engine/docs/sources/examples/using_supervisord.md b/components/engine/docs/sources/examples/using_supervisord.md index 6fc47b0c03..0f10945325 100644 --- a/components/engine/docs/sources/examples/using_supervisord.md +++ b/components/engine/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/components/engine/docs/sources/introduction/working-with-docker.md b/components/engine/docs/sources/introduction/working-with-docker.md index 1abee1ce34..aefdc586c0 100644 --- a/components/engine/docs/sources/introduction/working-with-docker.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/README.md b/components/engine/docs/sources/reference/api/README.md index a7b8ae1b44..ec1cbcb2c3 100644 --- a/components/engine/docs/sources/reference/api/README.md +++ b/components/engine/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/components/engine/docs/sources/reference/commandline/cli.md b/components/engine/docs/sources/reference/commandline/cli.md index e0c5eb1069..8325bc794f 100644 --- a/components/engine/docs/sources/reference/commandline/cli.md +++ b/components/engine/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/components/engine/docs/sources/use/basics.md b/components/engine/docs/sources/use/basics.md index e4422034d4..1b8c0a7b1d 100644 --- a/components/engine/docs/sources/use/basics.md +++ b/components/engine/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/components/engine/hack/RELEASE-CHECKLIST.md b/components/engine/hack/RELEASE-CHECKLIST.md index 31f0adb757..583328b46d 100644 --- a/components/engine/hack/RELEASE-CHECKLIST.md +++ b/components/engine/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/components/engine/hack/make.sh b/components/engine/hack/make.sh index 8636756c87..e7dc09cfbe 100755 --- a/components/engine/hack/make.sh +++ b/components/engine/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 9e0e3e5d33bad7b974ce609347f5d94ee2daf785 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) Upstream-commit: 456ba11d89b52f946d293489b52508a2e270f1a7 Component: engine --- .../docs/sources/installation/google.md | 57 ++++++++----------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/components/engine/docs/sources/installation/google.md b/components/engine/docs/sources/installation/google.md index bb8a961470..fe3d0619ce 100644 --- a/components/engine/docs/sources/installation/google.md +++ b/components/engine/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 ee4e3699c5beb160190c7641cd052fc825d73297 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) Upstream-commit: 5e3b643ce6f43d02fc7fe88eba41d583044a2efd Component: engine --- components/engine/pkg/iptables/iptables.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/engine/pkg/iptables/iptables.go b/components/engine/pkg/iptables/iptables.go index b44c452233..e3f4d74b9d 100644 --- a/components/engine/pkg/iptables/iptables.go +++ b/components/engine/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 32c2485efa196c1cb25f7d41767267e88f903707 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) Upstream-commit: 75754e69f6cce80c34ebc72817ada0a807fd635a Component: engine --- components/engine/daemon/graphdriver/btrfs/btrfs.go | 8 ++++++-- components/engine/daemon/graphdriver/driver.go | 7 ++++--- .../engine/daemon/graphdriver/graphtest/graphtest.go | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/components/engine/daemon/graphdriver/btrfs/btrfs.go b/components/engine/daemon/graphdriver/btrfs/btrfs.go index 614dc1ff06..06c3fd0667 100644 --- a/components/engine/daemon/graphdriver/btrfs/btrfs.go +++ b/components/engine/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/components/engine/daemon/graphdriver/driver.go b/components/engine/daemon/graphdriver/driver.go index 96f8d3ab3e..a3039e0309 100644 --- a/components/engine/daemon/graphdriver/driver.go +++ b/components/engine/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/components/engine/daemon/graphdriver/graphtest/graphtest.go b/components/engine/daemon/graphdriver/graphtest/graphtest.go index f8ccb2ef33..56ea8f7d42 100644 --- a/components/engine/daemon/graphdriver/graphtest/graphtest.go +++ b/components/engine/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 c82b5a7f36b750fff8c549d2fca624ac900e1136 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) Upstream-commit: 72e6e5ff7edc9c054e154897a4c547d89c082293 Component: engine --- .../pkg/libcontainer/cgroups/fs/apply_raw.go | 17 ++- .../pkg/libcontainer/cgroups/fs/blkio.go | 129 ++++++++++-------- .../pkg/libcontainer/cgroups/fs/blkio_test.go | 69 +++++----- .../engine/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 +++-- .../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 +- .../engine/pkg/libcontainer/cgroups/stats.go | 32 +++-- 16 files changed, 329 insertions(+), 231 deletions(-) create mode 100644 components/engine/pkg/libcontainer/cgroups/fs/stats_test_util.go diff --git a/components/engine/pkg/libcontainer/cgroups/fs/apply_raw.go b/components/engine/pkg/libcontainer/cgroups/fs/apply_raw.go index be500781ec..291d1e84b5 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/apply_raw.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/blkio.go b/components/engine/pkg/libcontainer/cgroups/fs/blkio.go index 5cbef69f55..0c7a4e7b39 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/blkio.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/blkio_test.go b/components/engine/pkg/libcontainer/cgroups/fs/blkio_test.go index d0244ad716..d91a6479a9 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/blkio_test.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/cpu.go b/components/engine/pkg/libcontainer/cgroups/fs/cpu.go index ad3078b3b8..1c266f4a10 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/cpu.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/cpu_test.go b/components/engine/pkg/libcontainer/cgroups/fs/cpu_test.go index cacf2f4ced..ad0674083a 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/cpu_test.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/cpuacct.go b/components/engine/pkg/libcontainer/cgroups/fs/cpuacct.go index c52049f3e9..36a50b8d4f 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/cpuacct.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/cpuset.go b/components/engine/pkg/libcontainer/cgroups/fs/cpuset.go index af2dd528d0..c0b03c559e 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/cpuset.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/devices.go b/components/engine/pkg/libcontainer/cgroups/fs/devices.go index 00fea608f9..569cbbf0d9 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/devices.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/freezer.go b/components/engine/pkg/libcontainer/cgroups/fs/freezer.go index 0738ec1f09..a9a27ef5a9 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/freezer.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/memory.go b/components/engine/pkg/libcontainer/cgroups/fs/memory.go index 9964f83767..f202b16009 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/memory.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/memory_test.go b/components/engine/pkg/libcontainer/cgroups/fs/memory_test.go index 190d437b1c..e7d2018712 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/memory_test.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/perf_event.go b/components/engine/pkg/libcontainer/cgroups/fs/perf_event.go index 1cf1aeef12..1eb4df11b5 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/perf_event.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/stats_test_util.go b/components/engine/pkg/libcontainer/cgroups/fs/stats_test_util.go new file mode 100644 index 0000000000..bebd0cb3e3 --- /dev/null +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/test_util.go b/components/engine/pkg/libcontainer/cgroups/fs/test_util.go index 333386c5de..548870a8a3 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/test_util.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/utils.go b/components/engine/pkg/libcontainer/cgroups/fs/utils.go index 7213b5d6a0..ff0586345d 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/utils.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/stats.go b/components/engine/pkg/libcontainer/cgroups/stats.go index fbcd5dd234..7918d78fd0 100644 --- a/components/engine/pkg/libcontainer/cgroups/stats.go +++ b/components/engine/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 7903608c88f8a63ceac5d80e989f684536b61668 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) Upstream-commit: e8a87120d459dfeffee5474c3253e787d38b2ac0 Component: engine --- .../engine/daemon/graphdriver/aufs/aufs.go | 27 +++++++++++++++---- .../engine/daemon/graphdriver/driver.go | 9 ++++--- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/components/engine/daemon/graphdriver/aufs/aufs.go b/components/engine/daemon/graphdriver/aufs/aufs.go index 2b7aa1b68a..a8d728bbb8 100644 --- a/components/engine/daemon/graphdriver/aufs/aufs.go +++ b/components/engine/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/components/engine/daemon/graphdriver/driver.go b/components/engine/daemon/graphdriver/driver.go index a3039e0309..33965ccae6 100644 --- a/components/engine/daemon/graphdriver/driver.go +++ b/components/engine/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 13f57580355b180f61836bcf4d09ffba7facf685 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) Upstream-commit: 23dd221e5250398d4120a1f3d1bcb591923f4892 Component: engine --- .../engine/docs/sources/installation/mac.md | 26 +++++++++++++++++++ .../docs/sources/installation/windows.md | 26 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/components/engine/docs/sources/installation/mac.md b/components/engine/docs/sources/installation/mac.md index 9b8acc70ee..ef91081a53 100644 --- a/components/engine/docs/sources/installation/mac.md +++ b/components/engine/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/components/engine/docs/sources/installation/windows.md b/components/engine/docs/sources/installation/windows.md index 2980cad147..a736114296 100644 --- a/components/engine/docs/sources/installation/windows.md +++ b/components/engine/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 82589e5ffd96c09543b7cfdcb323ef54a43773e2 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) Upstream-commit: c4990ab999d49e261c5079925f0b13ef735a729f Component: engine --- components/engine/graph/tags.go | 53 +++++++++++++++++++++++------- components/engine/server/server.go | 15 ++------- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/components/engine/graph/tags.go b/components/engine/graph/tags.go index 524e1a1f9d..7af6d383d8 100644 --- a/components/engine/graph/tags.go +++ b/components/engine/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/components/engine/server/server.go b/components/engine/server/server.go index 0deda0d955..b817068369 100644 --- a/components/engine/server/server.go +++ b/components/engine/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 39456c1da33cd0bfc9f178fa8955744476d82478 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) Upstream-commit: ce9e9ff4a101eec5632704003fae772e8762eb15 Component: engine --- components/engine/server/server.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/components/engine/server/server.go b/components/engine/server/server.go index b817068369..85f1125ba6 100644 --- a/components/engine/server/server.go +++ b/components/engine/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 bfa0c1d204e53403b943e15e878ae1b4abbe18fb 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) Upstream-commit: 4e5f6951f2af836c3d9163c8a150298ee87468d4 Component: engine --- .../daemon/networkdriver/bridge/driver.go | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/components/engine/daemon/networkdriver/bridge/driver.go b/components/engine/daemon/networkdriver/bridge/driver.go index a14941a8f3..a960aead61 100644 --- a/components/engine/daemon/networkdriver/bridge/driver.go +++ b/components/engine/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 57a9b63e5e920b1bbc5081585e6437ad53f7d247 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) Upstream-commit: 64bd6a6a5342c87db7096f60365d270d0d69e9d2 Component: engine --- components/engine/daemon/execdriver/native/create.go | 4 ++++ components/engine/daemon/execdriver/native/driver.go | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/components/engine/daemon/execdriver/native/create.go b/components/engine/daemon/execdriver/native/create.go index 3a7001db1a..f4c2a5bd6b 100644 --- a/components/engine/daemon/execdriver/native/create.go +++ b/components/engine/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/components/engine/daemon/execdriver/native/driver.go b/components/engine/daemon/execdriver/native/driver.go index 425403fa4e..c84dbf7dc9 100644 --- a/components/engine/daemon/execdriver/native/driver.go +++ b/components/engine/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 f31e7878cfb28e4c0101ada19dc67c15b4ef0e93 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) Upstream-commit: af17b01ad7ba0c4d243e2e234710e44a14b6dad4 Component: engine --- components/engine/daemon/daemon.go | 71 ++++++++++++++++++------------ 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go index 78dee62523..cffe58e1f8 100644 --- a/components/engine/daemon/daemon.go +++ b/components/engine/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 95b0c9bbd68f0f94f475108c2daed65ffff2c957 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) Upstream-commit: adb639117b5c61479d65dbf8398c0fbeda1d6cad Component: engine --- components/engine/pkg/netlink/netlink_linux.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/components/engine/pkg/netlink/netlink_linux.go b/components/engine/pkg/netlink/netlink_linux.go index 21d4593620..14e30aa026 100644 --- a/components/engine/pkg/netlink/netlink_linux.go +++ b/components/engine/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 6fd1c7b5eed70911c693c60c8a4fe9060114fc01 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) Upstream-commit: ba1c0f4c817e958693cfcef30f1de9991765d5db Component: engine --- components/engine/contrib/man/md/docker-run.1.md | 4 ++-- components/engine/contrib/man/old-man/docker-run.1 | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/engine/contrib/man/md/docker-run.1.md b/components/engine/contrib/man/md/docker-run.1.md index e90ecab85f..3d2420cdbd 100644 --- a/components/engine/contrib/man/md/docker-run.1.md +++ b/components/engine/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/components/engine/contrib/man/old-man/docker-run.1 b/components/engine/contrib/man/old-man/docker-run.1 index ae0295943d..0e06e8d682 100644 --- a/components/engine/contrib/man/old-man/docker-run.1 +++ b/components/engine/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 1c65521b44f6ea8c49b8886a13e2e3699c8e63a7 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) Upstream-commit: 0fd0deb75d6e38973f154c5b41c7d4e22a9c4fef Component: engine --- components/engine/daemon/daemon.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go index cffe58e1f8..e080e989f9 100644 --- a/components/engine/daemon/daemon.go +++ b/components/engine/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 13006a3fdcf5a964ea2c112b44ae0bb976b129a2 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) Upstream-commit: e70a5ab0149562609d3a16105aada365ed940cb5 Component: engine --- components/engine/daemon/daemon.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go index cffe58e1f8..f018ad2468 100644 --- a/components/engine/daemon/daemon.go +++ b/components/engine/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 52c8a31f21a0aa65450335f18a2452361d6ff5eb 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) Upstream-commit: 608702b98064a4dfd70b5ff0bd6fb45d2429f45b Component: engine --- components/engine/daemon/container.go | 27 +- components/engine/daemon/execdriver/driver.go | 32 ++- .../engine/daemon/execdriver/lxc/driver.go | 5 + .../daemon/execdriver/lxc/lxc_template.go | 35 +-- .../execdriver/lxc/lxc_template_unit_test.go | 3 + .../engine/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 +--- .../engine/pkg/libcontainer/container.go | 10 +- .../engine/pkg/libcontainer/container.json | 56 +++- .../engine/pkg/libcontainer/container_test.go | 9 - .../pkg/libcontainer/devices/devices.go | 239 ++++++++++++++++++ .../engine/pkg/libcontainer/devices/number.go | 26 ++ .../engine/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 components/engine/pkg/libcontainer/devices/devices.go create mode 100644 components/engine/pkg/libcontainer/devices/number.go diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go index 647f4c187d..edb596e221 100644 --- a/components/engine/daemon/container.go +++ b/components/engine/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/components/engine/daemon/execdriver/driver.go b/components/engine/daemon/execdriver/driver.go index a3e43e60ac..2e43661496 100644 --- a/components/engine/daemon/execdriver/driver.go +++ b/components/engine/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/components/engine/daemon/execdriver/lxc/driver.go b/components/engine/daemon/execdriver/lxc/driver.go index 54f1054191..1df1d68f6d 100644 --- a/components/engine/daemon/execdriver/lxc/driver.go +++ b/components/engine/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/components/engine/daemon/execdriver/lxc/lxc_template.go b/components/engine/daemon/execdriver/lxc/lxc_template.go index d660df902a..fcebe134e7 100644 --- a/components/engine/daemon/execdriver/lxc/lxc_template.go +++ b/components/engine/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/components/engine/daemon/execdriver/lxc/lxc_template_unit_test.go b/components/engine/daemon/execdriver/lxc/lxc_template_unit_test.go index 96d11b204b..26474284bf 100644 --- a/components/engine/daemon/execdriver/lxc/lxc_template_unit_test.go +++ b/components/engine/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/components/engine/daemon/execdriver/native/create.go b/components/engine/daemon/execdriver/native/create.go index 3a7001db1a..8344670ca5 100644 --- a/components/engine/daemon/execdriver/native/create.go +++ b/components/engine/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/components/engine/daemon/execdriver/native/template/default_template.go b/components/engine/daemon/execdriver/native/template/default_template.go index a80b609a1e..21c888a034 100644 --- a/components/engine/daemon/execdriver/native/template/default_template.go +++ b/components/engine/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/components/engine/integration-cli/docker_cli_run_test.go b/components/engine/integration-cli/docker_cli_run_test.go index b72d8e32ca..209d730f93 100644 --- a/components/engine/integration-cli/docker_cli_run_test.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/cgroups.go b/components/engine/pkg/libcontainer/cgroups/cgroups.go index 0f93320725..537a27f25c 100644 --- a/components/engine/pkg/libcontainer/cgroups/cgroups.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/devices.go b/components/engine/pkg/libcontainer/cgroups/fs/devices.go index 00fea608f9..68941e1e28 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/devices.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/systemd/apply_systemd.go b/components/engine/pkg/libcontainer/cgroups/systemd/apply_systemd.go index 0f6beb658e..e57cf16b2a 100644 --- a/components/engine/pkg/libcontainer/cgroups/systemd/apply_systemd.go +++ b/components/engine/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/components/engine/pkg/libcontainer/container.go b/components/engine/pkg/libcontainer/container.go index 6734bfd590..fcb5dc9928 100644 --- a/components/engine/pkg/libcontainer/container.go +++ b/components/engine/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/components/engine/pkg/libcontainer/container.json b/components/engine/pkg/libcontainer/container.json index ba8117091d..6dcd1d2934 100644 --- a/components/engine/pkg/libcontainer/container.json +++ b/components/engine/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/components/engine/pkg/libcontainer/container_test.go b/components/engine/pkg/libcontainer/container_test.go index f6e991edf5..10548810dc 100644 --- a/components/engine/pkg/libcontainer/container_test.go +++ b/components/engine/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/components/engine/pkg/libcontainer/devices/devices.go b/components/engine/pkg/libcontainer/devices/devices.go new file mode 100644 index 0000000000..6423337d24 --- /dev/null +++ b/components/engine/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/components/engine/pkg/libcontainer/devices/number.go b/components/engine/pkg/libcontainer/devices/number.go new file mode 100644 index 0000000000..3aae380bb1 --- /dev/null +++ b/components/engine/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/components/engine/pkg/libcontainer/mount/init.go b/components/engine/pkg/libcontainer/mount/init.go index 82c76aad72..af7a521c46 100644 --- a/components/engine/pkg/libcontainer/mount/init.go +++ b/components/engine/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/components/engine/pkg/libcontainer/mount/nodes/nodes.go b/components/engine/pkg/libcontainer/mount/nodes/nodes.go index f8e6e97450..18ef487a1c 100644 --- a/components/engine/pkg/libcontainer/mount/nodes/nodes.go +++ b/components/engine/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/components/engine/pkg/libcontainer/mount/nodes/nodes_unsupported.go b/components/engine/pkg/libcontainer/mount/nodes/nodes_unsupported.go index 24409f411f..b92f89b0e1 100644 --- a/components/engine/pkg/libcontainer/mount/nodes/nodes_unsupported.go +++ b/components/engine/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 54c61632f8376816a3e9bc6fa6735933f3e51195 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) Upstream-commit: 5708aa62f36eadca5ada235ca05fddeb1510c1c6 Component: engine --- components/engine/pkg/iptables/iptables.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/pkg/iptables/iptables.go b/components/engine/pkg/iptables/iptables.go index e3f4d74b9d..748b69a444 100644 --- a/components/engine/pkg/iptables/iptables.go +++ b/components/engine/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 3acef7fcd08cf50619e73de7f99e36993ab80112 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) Upstream-commit: b64209f7b6818337a90d2f04627664b5964a29ce Component: engine --- components/engine/archive/changes.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/components/engine/archive/changes.go b/components/engine/archive/changes.go index 88cea0f709..1e588b8eb5 100644 --- a/components/engine/archive/changes.go +++ b/components/engine/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 d5186289b569514d5d042098d702f220c0a18e8b 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) Upstream-commit: b0ea389c694484c0e4907e12a9cf8ff10f80c164 Component: engine --- components/engine/pkg/graphdb/conn_sqlite3.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/components/engine/pkg/graphdb/conn_sqlite3.go b/components/engine/pkg/graphdb/conn_sqlite3.go index 33355ae4dc..b6a8027a81 100644 --- a/components/engine/pkg/graphdb/conn_sqlite3.go +++ b/components/engine/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 2a289bda145c72430acdca19bea1c73e85d1cbf8 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) Upstream-commit: 68fb7f4b744bf71206898d32fe203556a6261e5d Component: engine --- components/engine/api/server/server.go | 6 +++ components/engine/daemon/inspect.go | 38 +++++++++++++++--- components/engine/graph/service.go | 39 ++++++++++++++----- .../integration-cli/docker_cli_build_test.go | 16 ++++---- .../integration-cli/docker_cli_tag_test.go | 2 +- .../engine/integration/commands_test.go | 2 +- 6 files changed, 77 insertions(+), 26 deletions(-) diff --git a/components/engine/api/server/server.go b/components/engine/api/server/server.go index 9c838c4bbf..25b377ffdb 100644 --- a/components/engine/api/server/server.go +++ b/components/engine/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/components/engine/daemon/inspect.go b/components/engine/daemon/inspect.go index 0f771a3ca2..cf70d1579a 100644 --- a/components/engine/daemon/inspect.go +++ b/components/engine/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/components/engine/graph/service.go b/components/engine/graph/service.go index 881a199043..4bce6b5645 100644 --- a/components/engine/graph/service.go +++ b/components/engine/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/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index d804ba7962..4360032bb3 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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/components/engine/integration-cli/docker_cli_tag_test.go b/components/engine/integration-cli/docker_cli_tag_test.go index d75b7db385..ef51f64644 100644 --- a/components/engine/integration-cli/docker_cli_tag_test.go +++ b/components/engine/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/components/engine/integration/commands_test.go b/components/engine/integration/commands_test.go index 2ee5842e1c..4ad225bb43 100644 --- a/components/engine/integration/commands_test.go +++ b/components/engine/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 636c18b736daee02276ce470d4b654c042e38de2 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) Upstream-commit: 1dd02ff4a09218eab47533032d1222008d6419b8 Component: engine --- .../docs/sources/reference/api/docker_remote_api.md | 11 ++++++++++- .../sources/reference/api/docker_remote_api_v1.12.md | 10 +++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/components/engine/docs/sources/reference/api/docker_remote_api.md b/components/engine/docs/sources/reference/api/docker_remote_api.md index 84b56866ba..64837817e1 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md index 188d4fe0a2..9cc84f4ffc 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md +++ b/components/engine/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 3248c6e81c14b38de4cfbbe51aa03fd16c4a685e 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) Upstream-commit: 69989b7c06b0ca6737e83ddf8fcfa2dfccc57a7c Component: engine --- components/engine/daemon/execdriver/driver.go | 4 +- .../execdriver/lxc/lxc_template_unit_test.go | 4 +- .../engine/daemon/execdriver/native/create.go | 7 + .../pkg/libcontainer/cgroups/cgroups.go | 20 +- .../engine/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 components/engine/pkg/libcontainer/devices/defaults.go diff --git a/components/engine/daemon/execdriver/driver.go b/components/engine/daemon/execdriver/driver.go index 2e43661496..b2af92d084 100644 --- a/components/engine/daemon/execdriver/driver.go +++ b/components/engine/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/components/engine/daemon/execdriver/lxc/lxc_template_unit_test.go b/components/engine/daemon/execdriver/lxc/lxc_template_unit_test.go index 26474284bf..12760adb7a 100644 --- a/components/engine/daemon/execdriver/lxc/lxc_template_unit_test.go +++ b/components/engine/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/components/engine/daemon/execdriver/native/create.go b/components/engine/daemon/execdriver/native/create.go index 0e13643138..9de500dbe5 100644 --- a/components/engine/daemon/execdriver/native/create.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/cgroups.go b/components/engine/pkg/libcontainer/cgroups/cgroups.go index 537a27f25c..905d0ca0b8 100644 --- a/components/engine/pkg/libcontainer/cgroups/cgroups.go +++ b/components/engine/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/components/engine/pkg/libcontainer/container.go b/components/engine/pkg/libcontainer/container.go index d56e037b1a..c5864e948a 100644 --- a/components/engine/pkg/libcontainer/container.go +++ b/components/engine/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/components/engine/pkg/libcontainer/devices/defaults.go b/components/engine/pkg/libcontainer/devices/defaults.go new file mode 100644 index 0000000000..393c438c59 --- /dev/null +++ b/components/engine/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/components/engine/pkg/libcontainer/devices/devices.go b/components/engine/pkg/libcontainer/devices/devices.go index 6423337d24..f6bee56df2 100644 --- a/components/engine/pkg/libcontainer/devices/devices.go +++ b/components/engine/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/components/engine/pkg/libcontainer/mount/nodes/nodes.go b/components/engine/pkg/libcontainer/mount/nodes/nodes.go index 18ef487a1c..dd67ae2d58 100644 --- a/components/engine/pkg/libcontainer/mount/nodes/nodes.go +++ b/components/engine/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/components/engine/pkg/libcontainer/mount/nodes/nodes_unsupported.go b/components/engine/pkg/libcontainer/mount/nodes/nodes_unsupported.go index b92f89b0e1..0e5d12c73e 100644 --- a/components/engine/pkg/libcontainer/mount/nodes/nodes_unsupported.go +++ b/components/engine/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 a710a9b84a7df6fbed0ea443ad4adf0a1b6163e6 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) Upstream-commit: f9705477d023c63fb316a30204761aa1e3cb3e6d Component: engine --- components/engine/daemon/execdriver/lxc/driver.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/engine/daemon/execdriver/lxc/driver.go b/components/engine/daemon/execdriver/lxc/driver.go index 1df1d68f6d..e1b4763582 100644 --- a/components/engine/daemon/execdriver/lxc/driver.go +++ b/components/engine/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 81343783d4777028f9df1af68916948df1e6a5c5 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 Upstream-commit: 0658868c7f81dd225df8678f31f7ef70274d8d94 Component: engine --- components/engine/docs/sources/examples/nodejs_web_app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/docs/sources/examples/nodejs_web_app.md b/components/engine/docs/sources/examples/nodejs_web_app.md index bc0e908d2d..fe52aa9453 100644 --- a/components/engine/docs/sources/examples/nodejs_web_app.md +++ b/components/engine/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 1b862f62b6e33adeb21fafe97ca71439f9180f02 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 Upstream-commit: e471a87f1975a101cf7464c0c2cc48db3b79ec26 Component: engine --- .../engine/contrib/man/md/docker-build.1.md | 20 ++++++++++--------- .../engine/contrib/man/md/docker-run.1.md | 4 ++-- .../engine/contrib/man/md/docker-tag.1.md | 11 +++++----- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/components/engine/contrib/man/md/docker-build.1.md b/components/engine/contrib/man/md/docker-build.1.md index da4f5c017e..1f8cb9cc10 100644 --- a/components/engine/contrib/man/md/docker-build.1.md +++ b/components/engine/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/components/engine/contrib/man/md/docker-run.1.md b/components/engine/contrib/man/md/docker-run.1.md index 72b01419bf..a1e380ff58 100644 --- a/components/engine/contrib/man/md/docker-run.1.md +++ b/components/engine/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/components/engine/contrib/man/md/docker-tag.1.md b/components/engine/contrib/man/md/docker-tag.1.md index 687b6ddb47..eca821ebff 100644 --- a/components/engine/contrib/man/md/docker-tag.1.md +++ b/components/engine/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 1dae3a407bd92f2aef50c5e8d2e0474098d20180 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) Upstream-commit: 953abf6ae7b31a3563fa57a9898305018add0571 Component: engine --- components/engine/hack/RELEASE-CHECKLIST.md | 28 ++++++++++++++++----- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/components/engine/hack/RELEASE-CHECKLIST.md b/components/engine/hack/RELEASE-CHECKLIST.md index 583328b46d..2652e16c44 100644 --- a/components/engine/hack/RELEASE-CHECKLIST.md +++ b/components/engine/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 1b1746cd694b5b5181b1ce85e34e75a40f30be5f 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) Upstream-commit: a7b2c4804b2d98c2b5622db40d3d70b88529d7fe Component: engine --- components/engine/docs/mkdocs.yml | 80 ++-- components/engine/docs/s3_website.json | 9 +- components/engine/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 .../sources/{examples => articles}/https.md | 0 .../sources/{use => articles}/networking.md | 18 +- .../docs/sources/{use => articles}/puppet.md | 0 .../engine/docs/sources/articles/security.md | 2 +- .../using_supervisord.md | 8 +- .../engine/docs/sources/docker-io/builds.md | 78 ++-- .../engine/docs/sources/docker-io/index.md | 8 + components/engine/docs/sources/examples.md | 30 +- .../docs/sources/examples/apt-cacher-ng.md | 8 +- .../sources/examples/couchdb_data_volumes.md | 8 +- .../docs/sources/examples/example_header.inc | 8 - .../docs/sources/examples/hello_world.md | 162 ------- .../engine/docs/sources/examples/mongodb.md | 20 +- .../docs/sources/examples/nodejs_web_app.md | 13 +- .../sources/examples/postgresql_service.md | 24 +- .../docs/sources/examples/python_web_app.md | 127 ------ .../sources/examples/running_redis_service.md | 12 +- .../sources/examples/running_riak_service.md | 19 +- .../sources/examples/running_ssh_service.md | 27 +- components/engine/docs/sources/faq.md | 14 +- components/engine/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 +- .../engine/docs/sources/installation/mac.md | 34 +- .../docs/sources/installation/openSUSE.md | 5 +- .../engine/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 +- .../engine/docs/sources/reference/builder.md | 12 +- .../docs/sources/reference/commandline/cli.md | 33 +- .../engine/docs/sources/reference/run.md | 24 +- .../engine/docs/sources/terms/container.md | 23 +- components/engine/docs/sources/terms/image.md | 4 +- components/engine/docs/sources/terms/layer.md | 2 +- .../engine/docs/sources/terms/registry.md | 10 +- .../engine/docs/sources/terms/repository.md | 6 +- components/engine/docs/sources/use.md | 13 - .../docs/sources/use/port_redirection.md | 127 ------ .../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 ++++++++++++++++++ .../engine/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 +++++++ .../engine/docs/sources/userguide/index.md | 98 +++++ .../docs/sources/userguide/login-web.png | Bin 0 -> 31788 bytes .../sources/userguide/register-confirm.png | Bin 0 -> 65441 bytes .../docs/sources/userguide/register-web.png | Bin 0 -> 72463 bytes .../engine/docs/sources/userguide/search.png | Bin 0 -> 101216 bytes .../docs/sources/userguide/usingdocker.md | 316 ++++++++++++++ .../engine/docs/sources/userguide/webapp1.png | Bin 0 -> 51848 bytes 73 files changed, 1958 insertions(+), 1707 deletions(-) rename components/engine/docs/sources/{use => articles}/ambassador_pattern_linking.md (100%) rename components/engine/docs/sources/{use => articles}/basics.md (95%) rename components/engine/docs/sources/{examples => articles}/cfengine_process_management.md (100%) rename components/engine/docs/sources/{use => articles}/chef.md (100%) rename components/engine/docs/sources/{use => articles}/host_integration.md (100%) rename components/engine/docs/sources/{examples => articles}/https.md (100%) rename components/engine/docs/sources/{use => articles}/networking.md (97%) rename components/engine/docs/sources/{use => articles}/puppet.md (100%) rename components/engine/docs/sources/{examples => articles}/using_supervisord.md (93%) create mode 100644 components/engine/docs/sources/docker-io/index.md delete mode 100644 components/engine/docs/sources/examples/example_header.inc delete mode 100644 components/engine/docs/sources/examples/hello_world.md delete mode 100644 components/engine/docs/sources/examples/python_web_app.md delete mode 100644 components/engine/docs/sources/introduction/working-with-docker.md delete mode 100644 components/engine/docs/sources/use.md delete mode 100644 components/engine/docs/sources/use/port_redirection.md delete mode 100644 components/engine/docs/sources/use/working_with_links_names.md delete mode 100644 components/engine/docs/sources/use/working_with_volumes.md delete mode 100644 components/engine/docs/sources/use/workingwithrepository.md create mode 100644 components/engine/docs/sources/userguide/dockerimages.md create mode 100644 components/engine/docs/sources/userguide/dockerio.md create mode 100644 components/engine/docs/sources/userguide/dockerizing.md create mode 100644 components/engine/docs/sources/userguide/dockerlinks.md create mode 100644 components/engine/docs/sources/userguide/dockerrepos.md create mode 100644 components/engine/docs/sources/userguide/dockervolumes.md create mode 100644 components/engine/docs/sources/userguide/index.md create mode 100644 components/engine/docs/sources/userguide/login-web.png create mode 100644 components/engine/docs/sources/userguide/register-confirm.png create mode 100644 components/engine/docs/sources/userguide/register-web.png create mode 100644 components/engine/docs/sources/userguide/search.png create mode 100644 components/engine/docs/sources/userguide/usingdocker.md create mode 100644 components/engine/docs/sources/userguide/webapp1.png diff --git a/components/engine/docs/mkdocs.yml b/components/engine/docs/mkdocs.yml index 1e6b8b16d8..2835cd9dde 100755 --- a/components/engine/docs/mkdocs.yml +++ b/components/engine/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/components/engine/docs/s3_website.json b/components/engine/docs/s3_website.json index 2d158cf9de..35c86f462d 100644 --- a/components/engine/docs/s3_website.json +++ b/components/engine/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/components/engine/docs/sources/articles.md b/components/engine/docs/sources/articles.md index 54c067d0cc..51335c6afd 100644 --- a/components/engine/docs/sources/articles.md +++ b/components/engine/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/components/engine/docs/sources/use/ambassador_pattern_linking.md b/components/engine/docs/sources/articles/ambassador_pattern_linking.md similarity index 100% rename from components/engine/docs/sources/use/ambassador_pattern_linking.md rename to components/engine/docs/sources/articles/ambassador_pattern_linking.md diff --git a/components/engine/docs/sources/use/basics.md b/components/engine/docs/sources/articles/basics.md similarity index 95% rename from components/engine/docs/sources/use/basics.md rename to components/engine/docs/sources/articles/basics.md index 1b8c0a7b1d..1f21e3454b 100644 --- a/components/engine/docs/sources/use/basics.md +++ b/components/engine/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/components/engine/docs/sources/examples/cfengine_process_management.md b/components/engine/docs/sources/articles/cfengine_process_management.md similarity index 100% rename from components/engine/docs/sources/examples/cfengine_process_management.md rename to components/engine/docs/sources/articles/cfengine_process_management.md diff --git a/components/engine/docs/sources/use/chef.md b/components/engine/docs/sources/articles/chef.md similarity index 100% rename from components/engine/docs/sources/use/chef.md rename to components/engine/docs/sources/articles/chef.md diff --git a/components/engine/docs/sources/use/host_integration.md b/components/engine/docs/sources/articles/host_integration.md similarity index 100% rename from components/engine/docs/sources/use/host_integration.md rename to components/engine/docs/sources/articles/host_integration.md diff --git a/components/engine/docs/sources/examples/https.md b/components/engine/docs/sources/articles/https.md similarity index 100% rename from components/engine/docs/sources/examples/https.md rename to components/engine/docs/sources/articles/https.md diff --git a/components/engine/docs/sources/use/networking.md b/components/engine/docs/sources/articles/networking.md similarity index 97% rename from components/engine/docs/sources/use/networking.md rename to components/engine/docs/sources/articles/networking.md index 5b76233eff..aedc5aaa35 100644 --- a/components/engine/docs/sources/use/networking.md +++ b/components/engine/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/components/engine/docs/sources/use/puppet.md b/components/engine/docs/sources/articles/puppet.md similarity index 100% rename from components/engine/docs/sources/use/puppet.md rename to components/engine/docs/sources/articles/puppet.md diff --git a/components/engine/docs/sources/articles/security.md b/components/engine/docs/sources/articles/security.md index 69284db836..eef2577304 100644 --- a/components/engine/docs/sources/articles/security.md +++ b/components/engine/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/components/engine/docs/sources/examples/using_supervisord.md b/components/engine/docs/sources/articles/using_supervisord.md similarity index 93% rename from components/engine/docs/sources/examples/using_supervisord.md rename to components/engine/docs/sources/articles/using_supervisord.md index 0f10945325..fd7c07cabf 100644 --- a/components/engine/docs/sources/examples/using_supervisord.md +++ b/components/engine/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/components/engine/docs/sources/docker-io/builds.md b/components/engine/docs/sources/docker-io/builds.md index 1f6e002208..dd18907cf6 100644 --- a/components/engine/docs/sources/docker-io/builds.md +++ b/components/engine/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/components/engine/docs/sources/docker-io/index.md b/components/engine/docs/sources/docker-io/index.md new file mode 100644 index 0000000000..dc83f0b281 --- /dev/null +++ b/components/engine/docs/sources/docker-io/index.md @@ -0,0 +1,8 @@ +# Docker.io + +## Contents: + +- [Accounts](accounts/) +- [Repositories](repos/) +- [Automated Builds](builds/) + diff --git a/components/engine/docs/sources/examples.md b/components/engine/docs/sources/examples.md index f1d1567f52..9dcd67a643 100644 --- a/components/engine/docs/sources/examples.md +++ b/components/engine/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/components/engine/docs/sources/examples/apt-cacher-ng.md b/components/engine/docs/sources/examples/apt-cacher-ng.md index 0293ac5d0b..34e4a4bf02 100644 --- a/components/engine/docs/sources/examples/apt-cacher-ng.md +++ b/components/engine/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/components/engine/docs/sources/examples/couchdb_data_volumes.md b/components/engine/docs/sources/examples/couchdb_data_volumes.md index ec1a0d9476..44043d6411 100644 --- a/components/engine/docs/sources/examples/couchdb_data_volumes.md +++ b/components/engine/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/components/engine/docs/sources/examples/example_header.inc b/components/engine/docs/sources/examples/example_header.inc deleted file mode 100644 index 5841141e59..0000000000 --- a/components/engine/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/components/engine/docs/sources/examples/hello_world.md b/components/engine/docs/sources/examples/hello_world.md deleted file mode 100644 index 177857816c..0000000000 --- a/components/engine/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/components/engine/docs/sources/examples/mongodb.md b/components/engine/docs/sources/examples/mongodb.md index e397cb0e58..5b0b7d3e28 100644 --- a/components/engine/docs/sources/examples/mongodb.md +++ b/components/engine/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/components/engine/docs/sources/examples/nodejs_web_app.md b/components/engine/docs/sources/examples/nodejs_web_app.md index fe52aa9453..99946e99e0 100644 --- a/components/engine/docs/sources/examples/nodejs_web_app.md +++ b/components/engine/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/components/engine/docs/sources/examples/postgresql_service.md b/components/engine/docs/sources/examples/postgresql_service.md index 6f0a3e6bb1..b931fd8ba4 100644 --- a/components/engine/docs/sources/examples/postgresql_service.md +++ b/components/engine/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/components/engine/docs/sources/examples/python_web_app.md b/components/engine/docs/sources/examples/python_web_app.md deleted file mode 100644 index f4b76d061d..0000000000 --- a/components/engine/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/components/engine/docs/sources/examples/running_redis_service.md b/components/engine/docs/sources/examples/running_redis_service.md index ca67048625..0eeef0625d 100644 --- a/components/engine/docs/sources/examples/running_redis_service.md +++ b/components/engine/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/components/engine/docs/sources/examples/running_riak_service.md b/components/engine/docs/sources/examples/running_riak_service.md index 852035f9a4..098cc9094b 100644 --- a/components/engine/docs/sources/examples/running_riak_service.md +++ b/components/engine/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/components/engine/docs/sources/examples/running_ssh_service.md b/components/engine/docs/sources/examples/running_ssh_service.md index 534e22e1b3..5be2c7053e 100644 --- a/components/engine/docs/sources/examples/running_ssh_service.md +++ b/components/engine/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/components/engine/docs/sources/faq.md b/components/engine/docs/sources/faq.md index 2494f33e9c..8dbdfd184f 100644 --- a/components/engine/docs/sources/faq.md +++ b/components/engine/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/components/engine/docs/sources/index.md b/components/engine/docs/sources/index.md index 16f29f5708..f7295d9c10 100644 --- a/components/engine/docs/sources/index.md +++ b/components/engine/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/components/engine/docs/sources/installation/amazon.md b/components/engine/docs/sources/installation/amazon.md index f7abec1550..86443147e7 100644 --- a/components/engine/docs/sources/installation/amazon.md +++ b/components/engine/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/components/engine/docs/sources/installation/binaries.md b/components/engine/docs/sources/installation/binaries.md index 494530a239..97e2f93c4e 100644 --- a/components/engine/docs/sources/installation/binaries.md +++ b/components/engine/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/components/engine/docs/sources/installation/centos.md b/components/engine/docs/sources/installation/centos.md index 7d2a9ae5d7..3966d0f092 100644 --- a/components/engine/docs/sources/installation/centos.md +++ b/components/engine/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/components/engine/docs/sources/installation/debian.md b/components/engine/docs/sources/installation/debian.md index def8cb77cf..0ad54b4328 100644 --- a/components/engine/docs/sources/installation/debian.md +++ b/components/engine/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/components/engine/docs/sources/installation/fedora.md b/components/engine/docs/sources/installation/fedora.md index d615d4f6c0..bcd54e6bd6 100644 --- a/components/engine/docs/sources/installation/fedora.md +++ b/components/engine/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/components/engine/docs/sources/installation/mac.md b/components/engine/docs/sources/installation/mac.md index ef91081a53..135130ef8e 100644 --- a/components/engine/docs/sources/installation/mac.md +++ b/components/engine/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/components/engine/docs/sources/installation/openSUSE.md b/components/engine/docs/sources/installation/openSUSE.md index d2c8e54848..ce79de2699 100644 --- a/components/engine/docs/sources/installation/openSUSE.md +++ b/components/engine/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/components/engine/docs/sources/installation/rhel.md b/components/engine/docs/sources/installation/rhel.md index 7cb71ec0d6..c144573687 100644 --- a/components/engine/docs/sources/installation/rhel.md +++ b/components/engine/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/components/engine/docs/sources/installation/softlayer.md b/components/engine/docs/sources/installation/softlayer.md index 80b12741ff..d01866720c 100644 --- a/components/engine/docs/sources/installation/softlayer.md +++ b/components/engine/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/components/engine/docs/sources/installation/ubuntulinux.md b/components/engine/docs/sources/installation/ubuntulinux.md index daba2c4ad1..bd32802bba 100644 --- a/components/engine/docs/sources/installation/ubuntulinux.md +++ b/components/engine/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/components/engine/docs/sources/introduction/understanding-docker.md b/components/engine/docs/sources/introduction/understanding-docker.md index 1d99be7046..e9041420af 100644 --- a/components/engine/docs/sources/introduction/understanding-docker.md +++ b/components/engine/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/components/engine/docs/sources/introduction/working-with-docker.md b/components/engine/docs/sources/introduction/working-with-docker.md deleted file mode 100644 index aefdc586c0..0000000000 --- a/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.md index f743cb0b22..336edfbe5b 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md index 8f4709ee69..4133deaea6 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.6.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.6.md index bca09a3a0e..cd5b949c6e 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.6.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.7.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.7.md index 818fbba11c..3ad2a729c5 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.7.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.8.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.8.md index 0d2997693c..597922e1f0 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.8.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.9.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.9.md index d8be62a7a7..2030959f96 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.9.md +++ b/components/engine/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/components/engine/docs/sources/reference/builder.md b/components/engine/docs/sources/reference/builder.md index 235d92e1b7..10973665b1 100644 --- a/components/engine/docs/sources/reference/builder.md +++ b/components/engine/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/components/engine/docs/sources/reference/commandline/cli.md b/components/engine/docs/sources/reference/commandline/cli.md index 8325bc794f..f2e370ace5 100644 --- a/components/engine/docs/sources/reference/commandline/cli.md +++ b/components/engine/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/components/engine/docs/sources/reference/run.md b/components/engine/docs/sources/reference/run.md index aa3d941b13..7d5fcbc51f 100644 --- a/components/engine/docs/sources/reference/run.md +++ b/components/engine/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/components/engine/docs/sources/terms/container.md b/components/engine/docs/sources/terms/container.md index 5bedc3160e..8b42868788 100644 --- a/components/engine/docs/sources/terms/container.md +++ b/components/engine/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/components/engine/docs/sources/terms/image.md b/components/engine/docs/sources/terms/image.md index b10debcc6a..40438be631 100644 --- a/components/engine/docs/sources/terms/image.md +++ b/components/engine/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/components/engine/docs/sources/terms/layer.md b/components/engine/docs/sources/terms/layer.md index b4b2ea4b7a..561807fc44 100644 --- a/components/engine/docs/sources/terms/layer.md +++ b/components/engine/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/components/engine/docs/sources/terms/registry.md b/components/engine/docs/sources/terms/registry.md index 2006710607..08f8e8f69d 100644 --- a/components/engine/docs/sources/terms/registry.md +++ b/components/engine/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/components/engine/docs/sources/terms/repository.md b/components/engine/docs/sources/terms/repository.md index 1e035c95f4..52c83d45d8 100644 --- a/components/engine/docs/sources/terms/repository.md +++ b/components/engine/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/components/engine/docs/sources/use.md b/components/engine/docs/sources/use.md deleted file mode 100644 index 5b2524361e..0000000000 --- a/components/engine/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/components/engine/docs/sources/use/port_redirection.md b/components/engine/docs/sources/use/port_redirection.md deleted file mode 100644 index 315ef2650d..0000000000 --- a/components/engine/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/components/engine/docs/sources/use/working_with_links_names.md b/components/engine/docs/sources/use/working_with_links_names.md deleted file mode 100644 index d69f3f1751..0000000000 --- a/components/engine/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/components/engine/docs/sources/use/working_with_volumes.md b/components/engine/docs/sources/use/working_with_volumes.md deleted file mode 100644 index 4c0a46ff1a..0000000000 --- a/components/engine/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/components/engine/docs/sources/use/workingwithrepository.md b/components/engine/docs/sources/use/workingwithrepository.md deleted file mode 100644 index 2b4ad613cc..0000000000 --- a/components/engine/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/components/engine/docs/sources/userguide/dockerimages.md b/components/engine/docs/sources/userguide/dockerimages.md new file mode 100644 index 0000000000..1a69ab9aa6 --- /dev/null +++ b/components/engine/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/components/engine/docs/sources/userguide/dockerio.md b/components/engine/docs/sources/userguide/dockerio.md new file mode 100644 index 0000000000..e5c6c6dace --- /dev/null +++ b/components/engine/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/components/engine/docs/sources/userguide/dockerizing.md b/components/engine/docs/sources/userguide/dockerizing.md new file mode 100644 index 0000000000..79a2066c62 --- /dev/null +++ b/components/engine/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/components/engine/docs/sources/userguide/dockerlinks.md b/components/engine/docs/sources/userguide/dockerlinks.md new file mode 100644 index 0000000000..5c94879bf0 --- /dev/null +++ b/components/engine/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/components/engine/docs/sources/userguide/dockerrepos.md b/components/engine/docs/sources/userguide/dockerrepos.md new file mode 100644 index 0000000000..d18ec13ccd --- /dev/null +++ b/components/engine/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/components/engine/docs/sources/userguide/dockervolumes.md b/components/engine/docs/sources/userguide/dockervolumes.md new file mode 100644 index 0000000000..34cfe05b47 --- /dev/null +++ b/components/engine/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/components/engine/docs/sources/userguide/index.md b/components/engine/docs/sources/userguide/index.md new file mode 100644 index 0000000000..7f703b6854 --- /dev/null +++ b/components/engine/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/components/engine/docs/sources/userguide/login-web.png b/components/engine/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∓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%0A_0Y^2+O@| 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 literal 0 HcmV?d00001 diff --git a/components/engine/docs/sources/userguide/register-confirm.png b/components/engine/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/components/engine/docs/sources/userguide/webapp1.png b/components/engine/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 5d504d032ae667cfbd96509f7fb79e545473887e 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 Upstream-commit: 2858180a9a05dad999df0f4d9adc9750b0fb1c66 Component: engine --- components/engine/contrib/man/md/docker-tag.1.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/engine/contrib/man/md/docker-tag.1.md b/components/engine/contrib/man/md/docker-tag.1.md index eca821ebff..0c42769908 100644 --- a/components/engine/contrib/man/md/docker-tag.1.md +++ b/components/engine/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 0311fdd2c0039b742b8eb091bdaf2296fc618c87 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) Upstream-commit: ca231b3de52f718d96c0ff6e7af40c7c0fade623 Component: engine --- components/engine/engine/remote_test.go | 17 ++------------- components/engine/pkg/testutils/MAINTAINERS | 1 + components/engine/pkg/testutils/README.md | 2 ++ components/engine/pkg/testutils/testutils.go | 23 ++++++++++++++++++++ 4 files changed, 28 insertions(+), 15 deletions(-) create mode 100644 components/engine/pkg/testutils/MAINTAINERS create mode 100644 components/engine/pkg/testutils/README.md create mode 100644 components/engine/pkg/testutils/testutils.go diff --git a/components/engine/engine/remote_test.go b/components/engine/engine/remote_test.go index e59ac78cc0..1563660c97 100644 --- a/components/engine/engine/remote_test.go +++ b/components/engine/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/components/engine/pkg/testutils/MAINTAINERS b/components/engine/pkg/testutils/MAINTAINERS new file mode 100644 index 0000000000..012d27a2e0 --- /dev/null +++ b/components/engine/pkg/testutils/MAINTAINERS @@ -0,0 +1 @@ +Solomon Hykes (@shykes) diff --git a/components/engine/pkg/testutils/README.md b/components/engine/pkg/testutils/README.md new file mode 100644 index 0000000000..a208a90e68 --- /dev/null +++ b/components/engine/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/components/engine/pkg/testutils/testutils.go b/components/engine/pkg/testutils/testutils.go new file mode 100644 index 0000000000..4655e5844d --- /dev/null +++ b/components/engine/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 460cf60260a22c99ba5b6fbe6b5eb218ba3f998d 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) Upstream-commit: 0e9c40eb8243fa437bc6c3e93aaff64a10cb856e Component: engine --- components/engine/pkg/mflag/flag.go | 35 ++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/components/engine/pkg/mflag/flag.go b/components/engine/pkg/mflag/flag.go index ed85a4a4c5..2cfef331f6 100644 --- a/components/engine/pkg/mflag/flag.go +++ b/components/engine/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 af5c1612d8137e3d8a0a36ac813995110afd91b1 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) Upstream-commit: e4497feaba31dd33bad790f77f783afc0c695020 Component: engine --- components/engine/pkg/mflag/flag_test.go | 30 ++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/components/engine/pkg/mflag/flag_test.go b/components/engine/pkg/mflag/flag_test.go index b9e8a0ef3e..4c2222e54b 100644 --- a/components/engine/pkg/mflag/flag_test.go +++ b/components/engine/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 8148195e5a99161c066d684ef9bf99fb8726243b 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) Upstream-commit: f7b82be0dd1dc0d9b0fa6c9c194dc2c90af3d133 Component: engine --- .../engine/pkg/libcontainer/cgroups/fs/cpu.go | 4 ++ .../pkg/libcontainer/nsinit/nsinit/main.go | 44 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/components/engine/pkg/libcontainer/cgroups/fs/cpu.go b/components/engine/pkg/libcontainer/cgroups/fs/cpu.go index 1c266f4a10..2e9d588f4f 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/cpu.go +++ b/components/engine/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/components/engine/pkg/libcontainer/nsinit/nsinit/main.go b/components/engine/pkg/libcontainer/nsinit/nsinit/main.go index b076b5c55c..5d968375ed 100644 --- a/components/engine/pkg/libcontainer/nsinit/nsinit/main.go +++ b/components/engine/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 656618108cf54d32724181e7198d95155f7c7f4a 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) Upstream-commit: 3723d6341effa7698ccbb05ab708b0c25ee1af02 Component: engine --- .../pkg/libcontainer/cgroups/fs/cpuacct.go | 22 +++++++++++++++++++ .../pkg/libcontainer/cgroups/fs/utils.go | 2 +- .../engine/pkg/libcontainer/cgroups/stats.go | 3 ++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/components/engine/pkg/libcontainer/cgroups/fs/cpuacct.go b/components/engine/pkg/libcontainer/cgroups/fs/cpuacct.go index 36a50b8d4f..dc17fdf835 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/cpuacct.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/utils.go b/components/engine/pkg/libcontainer/cgroups/fs/utils.go index ff0586345d..f65622a80f 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/utils.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/stats.go b/components/engine/pkg/libcontainer/cgroups/stats.go index 7918d78fd0..eddf0ee0b1 100644 --- a/components/engine/pkg/libcontainer/cgroups/stats.go +++ b/components/engine/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 c0015f4cf44987701ba005c70d5923eaf1f32f55 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) Upstream-commit: 1a93d3b054751f6783064e5adb91f196687400b2 Component: engine --- components/engine/pkg/testutils/MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/pkg/testutils/MAINTAINERS b/components/engine/pkg/testutils/MAINTAINERS index 012d27a2e0..1c41a67440 100644 --- a/components/engine/pkg/testutils/MAINTAINERS +++ b/components/engine/pkg/testutils/MAINTAINERS @@ -1 +1,2 @@ Solomon Hykes (@shykes) +Cristian Staretu (github: unclejack) From d8a617a361139bd2ff39923fd16639f2aeb52c7d 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) Upstream-commit: 0a06e9bd91faff2c129fd5a7b30b85351c96601f Component: engine --- components/engine/integration-cli/MAINTAINERS | 2 +- components/engine/pkg/testutils/MAINTAINERS | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/integration-cli/MAINTAINERS b/components/engine/integration-cli/MAINTAINERS index 53c8a11858..6dde4769d7 100644 --- a/components/engine/integration-cli/MAINTAINERS +++ b/components/engine/integration-cli/MAINTAINERS @@ -1 +1 @@ -Cristian Staretu (github: unclejack) +Cristian Staretu (@unclejack) diff --git a/components/engine/pkg/testutils/MAINTAINERS b/components/engine/pkg/testutils/MAINTAINERS index 1c41a67440..f2e8c52e51 100644 --- a/components/engine/pkg/testutils/MAINTAINERS +++ b/components/engine/pkg/testutils/MAINTAINERS @@ -1,2 +1,2 @@ Solomon Hykes (@shykes) -Cristian Staretu (github: unclejack) +Cristian Staretu (@unclejack) From 071672b2fec935d87ac4ec00c47952cb0b829a53 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) Upstream-commit: 4ac216d8d3441b8546798711320cb5679d33984d Component: engine --- components/engine/docs/sources/articles/networking.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/docs/sources/articles/networking.md b/components/engine/docs/sources/articles/networking.md index aedc5aaa35..a4640f3665 100644 --- a/components/engine/docs/sources/articles/networking.md +++ b/components/engine/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 11c1a24c8e3c7942314dbd46fa02986d21e5ad87 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) Upstream-commit: 1ae37cef9189976c7d3838ba0899e874a0758379 Component: engine --- components/engine/utils/streamformatter.go | 11 +-- .../engine/utils/streamformatter_test.go | 67 +++++++++++++++++++ 2 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 components/engine/utils/streamformatter_test.go diff --git a/components/engine/utils/streamformatter.go b/components/engine/utils/streamformatter.go index d2758d3ca6..d0bc295bb3 100644 --- a/components/engine/utils/streamformatter.go +++ b/components/engine/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/components/engine/utils/streamformatter_test.go b/components/engine/utils/streamformatter_test.go new file mode 100644 index 0000000000..20610f6c01 --- /dev/null +++ b/components/engine/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 7eeb43108c85919c3cb21e0bf81fc742eb95ab29 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) Upstream-commit: cac24bbfaf59570863a75e34a21b70ce1cc5c51f Component: engine --- components/engine/docs/s3_website.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/docs/s3_website.json b/components/engine/docs/s3_website.json index 35c86f462d..eab6ae820c 100644 --- a/components/engine/docs/s3_website.json +++ b/components/engine/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 29a96223233536d2e84c60f35d930671039d7a26 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) Upstream-commit: 2a1b7f222a5eff596cabb5ebd88a481a83b24402 Component: engine --- components/engine/registry/registry.go | 45 +++++++++- components/engine/registry/registry_test.go | 4 +- components/engine/server/server.go | 2 +- .../engine/utils/resumablerequestreader.go | 87 +++++++++++++++++++ 4 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 components/engine/utils/resumablerequestreader.go diff --git a/components/engine/registry/registry.go b/components/engine/registry/registry.go index 3d0a3ed2da..7bcf066019 100644 --- a/components/engine/registry/registry.go +++ b/components/engine/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/components/engine/registry/registry_test.go b/components/engine/registry/registry_test.go index 0a5be5e543..e207359e61 100644 --- a/components/engine/registry/registry_test.go +++ b/components/engine/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/components/engine/server/server.go b/components/engine/server/server.go index 85f1125ba6..600e6fc5b1 100644 --- a/components/engine/server/server.go +++ b/components/engine/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/components/engine/utils/resumablerequestreader.go b/components/engine/utils/resumablerequestreader.go new file mode 100644 index 0000000000..e01f4e6d71 --- /dev/null +++ b/components/engine/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 2e677833d3f2b0becf6ddab9e1e65e58563df6b8 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) Upstream-commit: 30fdac2cb1bd5461972cef6efa3d67270db23491 Component: engine --- components/engine/pkg/libcontainer/cgroups/fs/cpu_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/pkg/libcontainer/cgroups/fs/cpu_test.go b/components/engine/pkg/libcontainer/cgroups/fs/cpu_test.go index ad0674083a..c5bfc50409 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/cpu_test.go +++ b/components/engine/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 ecc54a77db6cb6cecd73e56e0a3c3e199aaa821d 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) Upstream-commit: 2e85568816315a6544ce1c379a5b2b714fb16524 Component: engine --- .../engine/integration-cli/docker_utils.go | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/components/engine/integration-cli/docker_utils.go b/components/engine/integration-cli/docker_utils.go index 660d509e76..c1e306f2ee 100644 --- a/components/engine/integration-cli/docker_utils.go +++ b/components/engine/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 2b6fe57d0d203b67e19f91a5390635dd678a01ac 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) Upstream-commit: bf4d907092a81bf8c6bd8a6a2ce2105aa3c8c11d Component: engine --- .../integration-cli/docker_cli_build_test.go | 242 ++++++++++-------- 1 file changed, 139 insertions(+), 103 deletions(-) diff --git a/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index dbec07fab9..b44285957a 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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 21fda68864167a3a9b714caf483829a36433aa10 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) Upstream-commit: ae128437cefa93bba082a0231e2cd05d33991c81 Component: engine --- .../integration-cli/docker_cli_build_test.go | 337 +++++++++++++++++- .../engine/integration/buildfile_test.go | 220 ------------ 2 files changed, 335 insertions(+), 222 deletions(-) diff --git a/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index b44285957a..0a698308bc 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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/components/engine/integration/buildfile_test.go b/components/engine/integration/buildfile_test.go index c60bb64c7f..f91d5c2a69 100644 --- a/components/engine/integration/buildfile_test.go +++ b/components/engine/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 82a799b98b13aff684560e002e130b645205fc55 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) Upstream-commit: b8932abcd3f3932b6b56aa859b4c0762a95f7179 Component: engine --- components/engine/integration-cli/docker_cli_pull_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/integration-cli/docker_cli_pull_test.go b/components/engine/integration-cli/docker_cli_pull_test.go index 13b443f3d6..fa7d2be44e 100644 --- a/components/engine/integration-cli/docker_cli_pull_test.go +++ b/components/engine/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 da1e276039f4793d01c84376b93ae8cda5504de5 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) Upstream-commit: 730d9ba17421e82012664c44aef7f64600b756cb Component: engine --- components/engine/integration/runtime_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/integration/runtime_test.go b/components/engine/integration/runtime_test.go index 9c59d38e01..96df15be60 100644 --- a/components/engine/integration/runtime_test.go +++ b/components/engine/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 f90ca07ea964802855e8eb7a3aeb2614969cb9cc 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) Upstream-commit: 5f3812ec97f9a951bceb482fdcd355b6cbc1b113 Component: engine --- components/engine/api/client/commands.go | 54 +- components/engine/api/server/server.go | 2 + .../sources/reference/commandline/cli.rst | 1499 +++++++++++++++++ components/engine/server/server.go | 43 +- components/engine/utils/filters/filter.go | 53 + components/engine/utils/filters/parse.go | 47 + 6 files changed, 1678 insertions(+), 20 deletions(-) create mode 100644 components/engine/docs/sources/reference/commandline/cli.rst create mode 100644 components/engine/utils/filters/filter.go create mode 100644 components/engine/utils/filters/parse.go diff --git a/components/engine/api/client/commands.go b/components/engine/api/client/commands.go index 6080595849..836fe37208 100644 --- a/components/engine/api/client/commands.go +++ b/components/engine/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/components/engine/api/server/server.go b/components/engine/api/server/server.go index 25b377ffdb..0bed582260 100644 --- a/components/engine/api/server/server.go +++ b/components/engine/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/components/engine/docs/sources/reference/commandline/cli.rst b/components/engine/docs/sources/reference/commandline/cli.rst new file mode 100644 index 0000000000..6af6a85c87 --- /dev/null +++ b/components/engine/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/components/engine/server/server.go b/components/engine/server/server.go index 85f1125ba6..f0a8cea0b7 100644 --- a/components/engine/server/server.go +++ b/components/engine/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/components/engine/utils/filters/filter.go b/components/engine/utils/filters/filter.go new file mode 100644 index 0000000000..b9db01361a --- /dev/null +++ b/components/engine/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/components/engine/utils/filters/parse.go b/components/engine/utils/filters/parse.go new file mode 100644 index 0000000000..d0cbe23c2a --- /dev/null +++ b/components/engine/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 1771a9601a19213d099d8f2855621783881e3cc7 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) Upstream-commit: caf9b19b0c444467aff493781afb66df923250ff Component: engine --- components/engine/utils/filters/filter.go | 53 ----------------------- 1 file changed, 53 deletions(-) delete mode 100644 components/engine/utils/filters/filter.go diff --git a/components/engine/utils/filters/filter.go b/components/engine/utils/filters/filter.go deleted file mode 100644 index b9db01361a..0000000000 --- a/components/engine/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 bf13fd92ad52130ef1cc5d2fbecbab45ca954453 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) Upstream-commit: 55d95185edd4a646cd2a05b9397be6b8667d60d0 Component: engine --- .../sources/reference/commandline/cli.rst | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/components/engine/docs/sources/reference/commandline/cli.rst b/components/engine/docs/sources/reference/commandline/cli.rst index 6af6a85c87..dc83b0cd43 100644 --- a/components/engine/docs/sources/reference/commandline/cli.rst +++ b/components/engine/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 9030fc74aa186f82c8209b408e0dd28221978f8e 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) Upstream-commit: babd5720157898bb33d806bda920d9f9f2df73f0 Component: engine --- components/engine/api/client/commands.go | 16 ---------------- components/engine/utils/filters/parse.go | 6 +++--- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/components/engine/api/client/commands.go b/components/engine/api/client/commands.go index 836fe37208..d0ac621483 100644 --- a/components/engine/api/client/commands.go +++ b/components/engine/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/components/engine/utils/filters/parse.go b/components/engine/utils/filters/parse.go index d0cbe23c2a..a813be9ff9 100644 --- a/components/engine/utils/filters/parse.go +++ b/components/engine/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 9a7aaea6bbaf249821dbf5e94b41b7d6bd3116fd 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) Upstream-commit: cb7857de5d2595632b76e4e17acede85a519001a Component: engine --- components/engine/docs/sources/reference/commandline/cli.rst | 3 +-- components/engine/server/server.go | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/components/engine/docs/sources/reference/commandline/cli.rst b/components/engine/docs/sources/reference/commandline/cli.rst index dc83b0cd43..7f1fab2af4 100644 --- a/components/engine/docs/sources/reference/commandline/cli.rst +++ b/components/engine/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/components/engine/server/server.go b/components/engine/server/server.go index f0a8cea0b7..dd54ab67de 100644 --- a/components/engine/server/server.go +++ b/components/engine/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 f418e0d6395455a64e6be310b597159afcb059ca 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) Upstream-commit: 3391aac2b27ac95f58a20a2eb4046ad1f00b8be6 Component: engine --- .../docs/sources/reference/commandline/cli.md | 41 + .../sources/reference/commandline/cli.rst | 1512 ----------------- 2 files changed, 41 insertions(+), 1512 deletions(-) delete mode 100644 components/engine/docs/sources/reference/commandline/cli.rst diff --git a/components/engine/docs/sources/reference/commandline/cli.md b/components/engine/docs/sources/reference/commandline/cli.md index f2e370ace5..0c3623f9e2 100644 --- a/components/engine/docs/sources/reference/commandline/cli.md +++ b/components/engine/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/components/engine/docs/sources/reference/commandline/cli.rst b/components/engine/docs/sources/reference/commandline/cli.rst deleted file mode 100644 index 7f1fab2af4..0000000000 --- a/components/engine/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 2dd212e295dc9622fe6a4cbdf705215bd31de1de 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) Upstream-commit: f1cc7ce5d728d6bdb1b3ab668e36fb1fc66515ff Component: engine --- components/engine/api/client/commands.go | 8 +-- components/engine/api/server/server.go | 2 +- components/engine/server/server.go | 13 +++- components/engine/utils/filters/parse.go | 47 ++++++++------- components/engine/utils/filters/parse_test.go | 60 +++++++++++++++++++ 5 files changed, 101 insertions(+), 29 deletions(-) create mode 100644 components/engine/utils/filters/parse_test.go diff --git a/components/engine/api/client/commands.go b/components/engine/api/client/commands.go index d0ac621483..1dd6194958 100644 --- a/components/engine/api/client/commands.go +++ b/components/engine/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/components/engine/api/server/server.go b/components/engine/api/server/server.go index 0bed582260..43b2c62323 100644 --- a/components/engine/api/server/server.go +++ b/components/engine/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/components/engine/server/server.go b/components/engine/server/server.go index dd54ab67de..0495b0ad5d 100644 --- a/components/engine/server/server.go +++ b/components/engine/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/components/engine/utils/filters/parse.go b/components/engine/utils/filters/parse.go index a813be9ff9..1eb46830ae 100644 --- a/components/engine/utils/filters/parse.go +++ b/components/engine/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/components/engine/utils/filters/parse_test.go b/components/engine/utils/filters/parse_test.go new file mode 100644 index 0000000000..c24fe3949a --- /dev/null +++ b/components/engine/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 c3f842bed438fd13642c0be6b141b36715fd3b50 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) Upstream-commit: f322168563dee9d0361524d15c6a5069772b44f8 Component: engine --- components/engine/server/server.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/components/engine/server/server.go b/components/engine/server/server.go index 0495b0ad5d..967129b908 100644 --- a/components/engine/server/server.go +++ b/components/engine/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 e355fe478a767cad389e5afdae33ee7c8b54883a 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) Upstream-commit: 3ecfaa8f2da60bb4e6ce6c9ae0428bc7b647e34b Component: engine --- components/engine/api/client/commands.go | 2 +- .../engine/docs/sources/reference/commandline/cli.md | 8 ++++---- components/engine/server/server.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/components/engine/api/client/commands.go b/components/engine/api/client/commands.go index 1dd6194958..00dd761b16 100644 --- a/components/engine/api/client/commands.go +++ b/components/engine/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/components/engine/docs/sources/reference/commandline/cli.md b/components/engine/docs/sources/reference/commandline/cli.md index 0c3623f9e2..829f13b9a6 100644 --- a/components/engine/docs/sources/reference/commandline/cli.md +++ b/components/engine/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/components/engine/server/server.go b/components/engine/server/server.go index 967129b908..8c70842a47 100644 --- a/components/engine/server/server.go +++ b/components/engine/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 2a107417ae257324000b2fa27c5a38740b0e1f90 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) Upstream-commit: 89a15fa235f0aab6dd326a3737260c983f0fc22b Component: engine --- components/engine/api/client/commands.go | 16 +++++++++++++--- components/engine/utils/filters/parse.go | 17 +++++++++++++---- components/engine/utils/filters/parse_test.go | 5 ++++- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/components/engine/api/client/commands.go b/components/engine/api/client/commands.go index 00dd761b16..4e013b8fe8 100644 --- a/components/engine/api/client/commands.go +++ b/components/engine/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/components/engine/utils/filters/parse.go b/components/engine/utils/filters/parse.go index 1eb46830ae..c1d6936b94 100644 --- a/components/engine/utils/filters/parse.go +++ b/components/engine/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/components/engine/utils/filters/parse_test.go b/components/engine/utils/filters/parse_test.go index c24fe3949a..59c5a5d935 100644 --- a/components/engine/utils/filters/parse_test.go +++ b/components/engine/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 353629f40490b16ed7e68044c3e11ebf13e30df1 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) Upstream-commit: 3a4e3ca3277fef8f1d88858fa5a3e149a897f8ae Component: engine --- components/engine/utils/filters/parse.go | 30 ++++++++++--------- components/engine/utils/filters/parse_test.go | 15 ++++++++++ 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/components/engine/utils/filters/parse.go b/components/engine/utils/filters/parse.go index c1d6936b94..27c7132e8e 100644 --- a/components/engine/utils/filters/parse.go +++ b/components/engine/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/components/engine/utils/filters/parse_test.go b/components/engine/utils/filters/parse_test.go index 59c5a5d935..a248350223 100644 --- a/components/engine/utils/filters/parse_test.go +++ b/components/engine/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 9b403bb6e906f387ac18dea1db29b71a3fecd3bb 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) Upstream-commit: af6ab357e8db8888429a28014f629af19a2250b8 Component: engine --- components/engine/server/server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/server/server.go b/components/engine/server/server.go index 85f1125ba6..e7134d8f0c 100644 --- a/components/engine/server/server.go +++ b/components/engine/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 6e829f27f806ed6d0ac3aeb33a1a1837e5e4265d 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) Upstream-commit: 0633b12b286d763521124f6d144deade89a89bfc Component: engine --- components/engine/nat/nat.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/components/engine/nat/nat.go b/components/engine/nat/nat.go index 7aad775d70..31633dd544 100644 --- a/components/engine/nat/nat.go +++ b/components/engine/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 fe1f6345d0f27d5c80cae9a5467888c386943867 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) Upstream-commit: 996133b9babbe4c4dfb42ee0d5aea9566568af14 Component: engine --- components/engine/daemon/inspect.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/daemon/inspect.go b/components/engine/daemon/inspect.go index cf70d1579a..4da09d5449 100644 --- a/components/engine/daemon/inspect.go +++ b/components/engine/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 792ff163c75d4ecd9d78f1fd15f6bc34a296e819 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) Upstream-commit: 4e7254840bc60f31e501c52e25990c540015e83b Component: engine --- components/engine/registry/auth.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/components/engine/registry/auth.go b/components/engine/registry/auth.go index 4fdd51fda4..7384efbad6 100644 --- a/components/engine/registry/auth.go +++ b/components/engine/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 4038755eae5addfce61687ac04feeeff7fa9d71b 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) Upstream-commit: f41ced96af9dba56dd368e213e6e83fd02b85b03 Component: engine --- components/engine/daemon/volumes.go | 112 +++++++++++++++------------- 1 file changed, 59 insertions(+), 53 deletions(-) diff --git a/components/engine/daemon/volumes.go b/components/engine/daemon/volumes.go index c3b003d0ea..d9719369ac 100644 --- a/components/engine/daemon/volumes.go +++ b/components/engine/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 - ) - - 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 - } - } - return nil + return copyOwnership(source, destination) +} + +// 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 := os.Chown(destination, int(stat.Uid), int(stat.Gid)); err != nil { + return err + } + + return os.Chmod(destination, os.FileMode(stat.Mode)) } From fef574365b3be4b4d37a7b40e3ed9d48eedc7f7b 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) Upstream-commit: c024c9bd1ec13aade038f87266a03a1d1bc13441 Component: engine --- .../integration-cli/docker_cli_build_test.go | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index 0a698308bc..7f6271a380 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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 382f8a23adb2898de323149204e1b52a23bc15a9 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) Upstream-commit: 41f7cef2bd186d321fc4489691ba53ab41eb48e5 Component: engine --- .../execdriver/native/template/default_template.go | 1 + .../engine/integration-cli/docker_cli_run_test.go | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/components/engine/daemon/execdriver/native/template/default_template.go b/components/engine/daemon/execdriver/native/template/default_template.go index 21c888a034..e7d3143df9 100644 --- a/components/engine/daemon/execdriver/native/template/default_template.go +++ b/components/engine/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/components/engine/integration-cli/docker_cli_run_test.go b/components/engine/integration-cli/docker_cli_run_test.go index 209d730f93..545ad371ee 100644 --- a/components/engine/integration-cli/docker_cli_run_test.go +++ b/components/engine/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 55a81e78b4f877b8558c061bef8a8d53adc5bf44 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) Upstream-commit: 68476e277f953d1076c8e966691769d5a35e65b6 Component: engine --- components/engine/daemon/graphdriver/aufs/aufs.go | 9 ++++++--- components/engine/daemon/graphdriver/btrfs/btrfs.go | 6 +----- components/engine/daemon/graphdriver/driver.go | 7 +++++++ 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/components/engine/daemon/graphdriver/aufs/aufs.go b/components/engine/daemon/graphdriver/aufs/aufs.go index a8d728bbb8..3757ec56d0 100644 --- a/components/engine/daemon/graphdriver/aufs/aufs.go +++ b/components/engine/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/components/engine/daemon/graphdriver/btrfs/btrfs.go b/components/engine/daemon/graphdriver/btrfs/btrfs.go index 06c3fd0667..56299b18ee 100644 --- a/components/engine/daemon/graphdriver/btrfs/btrfs.go +++ b/components/engine/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/components/engine/daemon/graphdriver/driver.go b/components/engine/daemon/graphdriver/driver.go index 33965ccae6..8f9c0b6d2b 100644 --- a/components/engine/daemon/graphdriver/driver.go +++ b/components/engine/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 8ce68c5f964d0812e8d28f5c1d822ba14fdb2390 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) Upstream-commit: 3a77a73a856f50a5954f22c5a76395ca77e43d00 Component: engine --- components/engine/contrib/man/md/Dockerfile.5.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/components/engine/contrib/man/md/Dockerfile.5.md b/components/engine/contrib/man/md/Dockerfile.5.md index 06970eb871..a298083f84 100644 --- a/components/engine/contrib/man/md/Dockerfile.5.md +++ b/components/engine/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 006edb2caf3b4777c60da651840906d6330bbdc1 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) Upstream-commit: 6b8f0e394b3960bba1db982c62c002221419f3f5 Component: engine --- .../cgroups/systemd/apply_systemd.go | 40 +++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/components/engine/pkg/libcontainer/cgroups/systemd/apply_systemd.go b/components/engine/pkg/libcontainer/cgroups/systemd/apply_systemd.go index e57cf16b2a..622ad44dd2 100644 --- a/components/engine/pkg/libcontainer/cgroups/systemd/apply_systemd.go +++ b/components/engine/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 0c475a0cf33c01dd17befed8b5af9c89d056694d 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) Upstream-commit: 02255ddaa4948fd94450fd5d9d2ee9b147e88352 Component: engine --- components/engine/api/client/commands.go | 27 ++++++++++--------- .../reference/api/docker_remote_api_v1.12.md | 10 +++++++ components/engine/server/server.go | 2 +- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/components/engine/api/client/commands.go b/components/engine/api/client/commands.go index 4e013b8fe8..4f0915ba79 100644 --- a/components/engine/api/client/commands.go +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md index 9cc84f4ffc..23afa36bcf 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md +++ b/components/engine/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/components/engine/server/server.go b/components/engine/server/server.go index 8c70842a47..ff9d370308 100644 --- a/components/engine/server/server.go +++ b/components/engine/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 f20c102828d0cc629e02b4b37ca13306fa421c51 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) Upstream-commit: 613f74c1fbbdc5e476d28974d1dbe3727033d083 Component: engine --- .../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/components/engine/pkg/libcontainer/cgroups/cgroups.go b/components/engine/pkg/libcontainer/cgroups/cgroups.go index 905d0ca0b8..85607b548a 100644 --- a/components/engine/pkg/libcontainer/cgroups/cgroups.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/apply_raw.go b/components/engine/pkg/libcontainer/cgroups/fs/apply_raw.go index 291d1e84b5..8fa25997fb 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/apply_raw.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/freezer.go b/components/engine/pkg/libcontainer/cgroups/fs/freezer.go index a9a27ef5a9..ed57f8ba9e 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/freezer.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/systemd/apply_nosystemd.go b/components/engine/pkg/libcontainer/cgroups/systemd/apply_nosystemd.go index 0fff3e4c6b..c72bb11611 100644 --- a/components/engine/pkg/libcontainer/cgroups/systemd/apply_nosystemd.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/systemd/apply_systemd.go b/components/engine/pkg/libcontainer/cgroups/systemd/apply_systemd.go index 622ad44dd2..eb1425b847 100644 --- a/components/engine/pkg/libcontainer/cgroups/systemd/apply_systemd.go +++ b/components/engine/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 6fe0f0775288ed4a255cac6cd96ef4a14a24398f 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) Upstream-commit: 9fc8028c987ac560ceb3b29eb609d77a82abdacc Component: engine --- components/engine/api/client/commands.go | 9 +++++---- components/engine/contrib/completion/bash/docker | 2 +- .../engine/contrib/completion/fish/docker.fish | 2 +- .../engine/contrib/man/md/docker-search.1.md | 16 ++++++++-------- .../engine/docs/sources/docker-io/builds.md | 11 +++++------ .../engine/docs/sources/docker-io/repos.md | 2 +- .../engine/docs/sources/examples/mongodb.md | 2 +- .../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 ++++++---- .../engine/docs/sources/userguide/index.md | 2 +- 12 files changed, 36 insertions(+), 31 deletions(-) diff --git a/components/engine/api/client/commands.go b/components/engine/api/client/commands.go index 4f0915ba79..1ad5d7889e 100644 --- a/components/engine/api/client/commands.go +++ b/components/engine/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/components/engine/contrib/completion/bash/docker b/components/engine/contrib/completion/bash/docker index e6a191d32b..e2ddd2accf 100755 --- a/components/engine/contrib/completion/bash/docker +++ b/components/engine/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/components/engine/contrib/completion/fish/docker.fish b/components/engine/contrib/completion/fish/docker.fish index 7ea478d051..a7fd52e312 100644 --- a/components/engine/contrib/completion/fish/docker.fish +++ b/components/engine/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/components/engine/contrib/man/md/docker-search.1.md b/components/engine/contrib/man/md/docker-search.1.md index fb2c921f8a..945dd34e59 100644 --- a/components/engine/contrib/man/md/docker-search.1.md +++ b/components/engine/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/components/engine/docs/sources/docker-io/builds.md b/components/engine/docs/sources/docker-io/builds.md index dd18907cf6..c70de4006c 100644 --- a/components/engine/docs/sources/docker-io/builds.md +++ b/components/engine/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/components/engine/docs/sources/docker-io/repos.md b/components/engine/docs/sources/docker-io/repos.md index a9bdabd89b..11170182a4 100644 --- a/components/engine/docs/sources/docker-io/repos.md +++ b/components/engine/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/components/engine/docs/sources/examples/mongodb.md b/components/engine/docs/sources/examples/mongodb.md index 5b0b7d3e28..17d58e0dbc 100644 --- a/components/engine/docs/sources/examples/mongodb.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api.md b/components/engine/docs/sources/reference/api/docker_remote_api.md index 64837817e1..355102a707 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md index 23afa36bcf..91b273158b 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md +++ b/components/engine/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/components/engine/docs/sources/reference/commandline/cli.md b/components/engine/docs/sources/reference/commandline/cli.md index 829f13b9a6..faa9e2809d 100644 --- a/components/engine/docs/sources/reference/commandline/cli.md +++ b/components/engine/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/components/engine/docs/sources/userguide/dockerimages.md b/components/engine/docs/sources/userguide/dockerimages.md index 1a69ab9aa6..bd8b0d2c2e 100644 --- a/components/engine/docs/sources/userguide/dockerimages.md +++ b/components/engine/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/components/engine/docs/sources/userguide/index.md b/components/engine/docs/sources/userguide/index.md index 7f703b6854..7150540433 100644 --- a/components/engine/docs/sources/userguide/index.md +++ b/components/engine/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 ddc2df6d84cdbd0db158ff69a78d733e4a3d95c4 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) Upstream-commit: 5febba93babcf8c4b01862e88b6f6e11a1532bc8 Component: engine --- components/engine/contrib/man/md/docker.1.md | 6 +++--- components/engine/contrib/man/old-man/docker.1 | 2 +- components/engine/contrib/vagrant-docker/README.md | 8 ++++---- components/engine/docs/sources/articles/basics.md | 10 +++++----- components/engine/docs/sources/articles/https.md | 4 ++-- components/engine/docs/sources/installation/mac.md | 2 +- .../engine/docs/sources/installation/ubuntulinux.md | 4 ++-- .../sources/reference/api/docker_remote_api_v1.0.md | 2 +- .../sources/reference/api/docker_remote_api_v1.1.md | 2 +- .../sources/reference/api/docker_remote_api_v1.10.md | 2 +- .../sources/reference/api/docker_remote_api_v1.11.md | 2 +- .../sources/reference/api/docker_remote_api_v1.12.md | 2 +- .../sources/reference/api/docker_remote_api_v1.2.md | 4 ++-- .../sources/reference/api/docker_remote_api_v1.3.md | 4 ++-- .../sources/reference/api/docker_remote_api_v1.4.md | 4 ++-- .../sources/reference/api/docker_remote_api_v1.5.md | 4 ++-- .../sources/reference/api/docker_remote_api_v1.6.md | 2 +- .../sources/reference/api/docker_remote_api_v1.7.md | 2 +- .../sources/reference/api/docker_remote_api_v1.8.md | 2 +- .../sources/reference/api/docker_remote_api_v1.9.md | 2 +- .../engine/docs/sources/reference/commandline/cli.md | 4 ++-- .../engine/integration-cli/docker_cli_build_test.go | 10 +++++----- components/engine/integration/buildfile_test.go | 4 ++-- components/engine/utils/utils_test.go | 2 +- 24 files changed, 45 insertions(+), 45 deletions(-) diff --git a/components/engine/contrib/man/md/docker.1.md b/components/engine/contrib/man/md/docker.1.md index c73b083e61..f990e5162f 100644 --- a/components/engine/contrib/man/md/docker.1.md +++ b/components/engine/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/components/engine/contrib/man/old-man/docker.1 b/components/engine/contrib/man/old-man/docker.1 index 5a55865120..95f60891cb 100644 --- a/components/engine/contrib/man/old-man/docker.1 +++ b/components/engine/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/components/engine/contrib/vagrant-docker/README.md b/components/engine/contrib/vagrant-docker/README.md index d422492fe9..4ef9c28775 100644 --- a/components/engine/contrib/vagrant-docker/README.md +++ b/components/engine/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/components/engine/docs/sources/articles/basics.md b/components/engine/docs/sources/articles/basics.md index 1f21e3454b..cd4a9df652 100644 --- a/components/engine/docs/sources/articles/basics.md +++ b/components/engine/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/components/engine/docs/sources/articles/https.md b/components/engine/docs/sources/articles/https.md index c46cf6b88c..cc8c6a9761 100644 --- a/components/engine/docs/sources/articles/https.md +++ b/components/engine/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/components/engine/docs/sources/installation/mac.md b/components/engine/docs/sources/installation/mac.md index 135130ef8e..c450cd12d2 100644 --- a/components/engine/docs/sources/installation/mac.md +++ b/components/engine/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/components/engine/docs/sources/installation/ubuntulinux.md b/components/engine/docs/sources/installation/ubuntulinux.md index bd32802bba..78a0679e55 100644 --- a/components/engine/docs/sources/installation/ubuntulinux.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.0.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.0.md index d719ca27e8..ba5338c5f9 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.0.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.1.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.1.md index 21997e5488..b884ee69dc 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.1.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.md index 336edfbe5b..8676833af9 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md index 4133deaea6..b77b33b097 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md index 23afa36bcf..1bc1591d70 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.2.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.2.md index 17967eab3d..e231cff02f 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.2.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.3.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.3.md index 9f7bd22e32..71c70273fd 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.3.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.4.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.4.md index 2e7e94f7d4..253944fd9a 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.4.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.5.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.5.md index 08457bfd94..7dc5334f45 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.5.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.6.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.6.md index cd5b949c6e..021a357b79 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.6.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.7.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.7.md index 3ad2a729c5..02073e9b60 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.7.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.8.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.8.md index 597922e1f0..a691930c6b 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.8.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.9.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.9.md index 2030959f96..d67443fd26 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.9.md +++ b/components/engine/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/components/engine/docs/sources/reference/commandline/cli.md b/components/engine/docs/sources/reference/commandline/cli.md index 829f13b9a6..f9c7ad424b 100644 --- a/components/engine/docs/sources/reference/commandline/cli.md +++ b/components/engine/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/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index 7f6271a380..f000235843 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/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/components/engine/integration/buildfile_test.go b/components/engine/integration/buildfile_test.go index f91d5c2a69..8c02cf043c 100644 --- a/components/engine/integration/buildfile_test.go +++ b/components/engine/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/components/engine/utils/utils_test.go b/components/engine/utils/utils_test.go index 83164c68dd..63d722ed07 100644 --- a/components/engine/utils/utils_test.go +++ b/components/engine/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 8e81ab409aa63a32c8301dd90a716983126d0b21 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) Upstream-commit: d96eb47dee10a059436b4d4cf285db1d70468e26 Component: engine --- components/engine/contrib/man/md/docker-start.1.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/engine/contrib/man/md/docker-start.1.md b/components/engine/contrib/man/md/docker-start.1.md index 9e639bbb5e..2815f1b07f 100644 --- a/components/engine/contrib/man/md/docker-start.1.md +++ b/components/engine/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 7d5cb46a54ecf1c1e59a41948a96c00040fd282b 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) Upstream-commit: 1ef3ca83d8624aaaaed05cfce1f71282d70d84dd Component: engine --- .../engine/daemon/execdriver/native/driver.go | 7 ++-- components/engine/pkg/apparmor/setup.go | 34 +------------------ 2 files changed, 4 insertions(+), 37 deletions(-) diff --git a/components/engine/daemon/execdriver/native/driver.go b/components/engine/daemon/execdriver/native/driver.go index c84dbf7dc9..ab34481dce 100644 --- a/components/engine/daemon/execdriver/native/driver.go +++ b/components/engine/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/components/engine/pkg/apparmor/setup.go b/components/engine/pkg/apparmor/setup.go index ef6333a01a..8ed5437470 100644 --- a/components/engine/pkg/apparmor/setup.go +++ b/components/engine/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 44f1195c6c7c68bb10d482da815aa4c6560a28e9 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) Upstream-commit: 534521301c81bcc536446351cf4018f02d477d2a Component: engine --- .../engine/pkg/libcontainer/cgroups/fs/freezer.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/components/engine/pkg/libcontainer/cgroups/fs/freezer.go b/components/engine/pkg/libcontainer/cgroups/fs/freezer.go index ed57f8ba9e..432018fd38 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/freezer.go +++ b/components/engine/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 974c92338ef5dbe98c3a3bf2fb0e4f9f12f3932d 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) Upstream-commit: 7a60b9063c109892f60165adb71682988d39d3d8 Component: engine --- components/engine/daemon/graphdriver/aufs/aufs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/daemon/graphdriver/aufs/aufs.go b/components/engine/daemon/graphdriver/aufs/aufs.go index 3757ec56d0..79de4cda79 100644 --- a/components/engine/daemon/graphdriver/aufs/aufs.go +++ b/components/engine/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 cda4353daba5ef873c87b975f28c3ed61355fcb2 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) Upstream-commit: dc21dd2634791d272b8e39493815bb81eaba5605 Component: engine --- .../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/components/engine/pkg/libcontainer/cgroups/fs/apply_raw.go b/components/engine/pkg/libcontainer/cgroups/fs/apply_raw.go index 8fa25997fb..2231ab9a38 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/apply_raw.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/freezer.go b/components/engine/pkg/libcontainer/cgroups/fs/freezer.go index 432018fd38..5c9ba5b543 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/freezer.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/systemd/apply_systemd.go b/components/engine/pkg/libcontainer/cgroups/systemd/apply_systemd.go index eb1425b847..c486dbeb9c 100644 --- a/components/engine/pkg/libcontainer/cgroups/systemd/apply_systemd.go +++ b/components/engine/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 07bf3a87a81688037d8057293c7657fde6eb53cd 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 Upstream-commit: 2e14f0f70af2ce152b531f02c4fce363b6605e37 Component: engine --- components/engine/docs/sources/reference/builder.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/docs/sources/reference/builder.md b/components/engine/docs/sources/reference/builder.md index 4e92ae7f64..c8af26c5db 100644 --- a/components/engine/docs/sources/reference/builder.md +++ b/components/engine/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 a0957a53144564ecbd550504cb3acc98eefbf71c 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) Upstream-commit: eb3e2f4f4736eaaab260804ff8543fb77243a254 Component: engine --- components/engine/server/server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/server/server.go b/components/engine/server/server.go index 507f32688c..c1b4b4323b 100644 --- a/components/engine/server/server.go +++ b/components/engine/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 77114664a4d830c0f0ee38472874e8ec18095d4b 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) Upstream-commit: b054569cde788b2111ddbc4080b215dcda89f06e Component: engine --- components/engine/api/client/commands.go | 48 +++++++++++++++++++ components/engine/api/server/server.go | 32 +++++++++++++ components/engine/daemon/container.go | 25 ++++++++++ components/engine/daemon/daemon.go | 18 +++++++ components/engine/daemon/execdriver/driver.go | 2 + .../engine/daemon/execdriver/lxc/driver.go | 24 ++++++++++ .../execdriver/native/configuration/parse.go | 11 +++++ .../engine/daemon/execdriver/native/driver.go | 26 ++++++++++ components/engine/daemon/state.go | 24 ++++++++++ components/engine/server/server.go | 38 +++++++++++++++ 10 files changed, 248 insertions(+) diff --git a/components/engine/api/client/commands.go b/components/engine/api/client/commands.go index bf1b979f62..c55e6e59b3 100644 --- a/components/engine/api/client/commands.go +++ b/components/engine/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/components/engine/api/server/server.go b/components/engine/api/server/server.go index 43b2c62323..a2c02f57fd 100644 --- a/components/engine/api/server/server.go +++ b/components/engine/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/components/engine/daemon/container.go b/components/engine/daemon/container.go index 3bdaf849da..9492e53417 100644 --- a/components/engine/daemon/container.go +++ b/components/engine/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/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go index 50869baadf..ac9c99e617 100644 --- a/components/engine/daemon/daemon.go +++ b/components/engine/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/components/engine/daemon/execdriver/driver.go b/components/engine/daemon/execdriver/driver.go index b2af92d084..a5c5c814d7 100644 --- a/components/engine/daemon/execdriver/driver.go +++ b/components/engine/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/components/engine/daemon/execdriver/lxc/driver.go b/components/engine/daemon/execdriver/lxc/driver.go index e1b4763582..8f785e8a8f 100644 --- a/components/engine/daemon/execdriver/lxc/driver.go +++ b/components/engine/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/components/engine/daemon/execdriver/native/configuration/parse.go b/components/engine/daemon/execdriver/native/configuration/parse.go index f18a60f797..3dffb4ed53 100644 --- a/components/engine/daemon/execdriver/native/configuration/parse.go +++ b/components/engine/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/components/engine/daemon/execdriver/native/driver.go b/components/engine/daemon/execdriver/native/driver.go index ab34481dce..5889d5b287 100644 --- a/components/engine/daemon/execdriver/native/driver.go +++ b/components/engine/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/components/engine/daemon/state.go b/components/engine/daemon/state.go index c0ed9516e3..7ee8fc48c3 100644 --- a/components/engine/daemon/state.go +++ b/components/engine/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/components/engine/server/server.go b/components/engine/server/server.go index 507f32688c..0c5fe9eb05 100644 --- a/components/engine/server/server.go +++ b/components/engine/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 0fc07e0aef68c6483ac6771042a00f160d581ad6 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) Upstream-commit: 26246ebd5379a83b2ed656668bd985c652e98167 Component: engine --- components/engine/daemon/container.go | 10 +++--- components/engine/daemon/daemon.go | 6 ++-- .../execdriver/native/configuration/parse.go | 11 ------- .../engine/daemon/execdriver/native/driver.go | 30 ++++++++--------- components/engine/server/server.go | 32 ++++++++----------- 5 files changed, 34 insertions(+), 55 deletions(-) diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go index 9492e53417..bb02b37f5b 100644 --- a/components/engine/daemon/container.go +++ b/components/engine/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/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go index ac9c99e617..8879d019ec 100644 --- a/components/engine/daemon/daemon.go +++ b/components/engine/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/components/engine/daemon/execdriver/native/configuration/parse.go b/components/engine/daemon/execdriver/native/configuration/parse.go index 3dffb4ed53..f18a60f797 100644 --- a/components/engine/daemon/execdriver/native/configuration/parse.go +++ b/components/engine/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/components/engine/daemon/execdriver/native/driver.go b/components/engine/daemon/execdriver/native/driver.go index 5889d5b287..a57c466f2c 100644 --- a/components/engine/daemon/execdriver/native/driver.go +++ b/components/engine/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/components/engine/server/server.go b/components/engine/server/server.go index 0c5fe9eb05..9ca3f133b8 100644 --- a/components/engine/server/server.go +++ b/components/engine/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 d17e332286c0f0bca4a9c09059a6048be4dcbc6f 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) Upstream-commit: 8d63b610c39eb34b06f275b507c482460015531e Component: engine --- .../pkg/libcontainer/cgroups/fs/cpuacct.go | 44 +++++++++++-------- .../pkg/libcontainer/cgroups/fs/memory.go | 5 +++ .../libcontainer/cgroups/fs/memory_test.go | 4 +- .../engine/pkg/libcontainer/cgroups/stats.go | 6 +++ 4 files changed, 39 insertions(+), 20 deletions(-) diff --git a/components/engine/pkg/libcontainer/cgroups/fs/cpuacct.go b/components/engine/pkg/libcontainer/cgroups/fs/cpuacct.go index dc17fdf835..5ea01dc94a 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/cpuacct.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/memory.go b/components/engine/pkg/libcontainer/cgroups/fs/memory.go index f202b16009..202ddc8ede 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/memory.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/fs/memory_test.go b/components/engine/pkg/libcontainer/cgroups/fs/memory_test.go index e7d2018712..29aea1f219 100644 --- a/components/engine/pkg/libcontainer/cgroups/fs/memory_test.go +++ b/components/engine/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/components/engine/pkg/libcontainer/cgroups/stats.go b/components/engine/pkg/libcontainer/cgroups/stats.go index eddf0ee0b1..20a5f00a37 100644 --- a/components/engine/pkg/libcontainer/cgroups/stats.go +++ b/components/engine/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 5f73b1bc4e134e839ae83a85e22200e7175c2365 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) Upstream-commit: 91757eaeb3523679b30afee61c1dede33c8664c9 Component: engine --- components/engine/api/client/commands.go | 114 +++++++++++++---------- components/engine/api/client/utils.go | 11 ++- components/engine/api/container.go | 18 ---- 3 files changed, 73 insertions(+), 70 deletions(-) delete mode 100644 components/engine/api/container.go diff --git a/components/engine/api/client/commands.go b/components/engine/api/client/commands.go index bf1b979f62..d7eb4884bb 100644 --- a/components/engine/api/client/commands.go +++ b/components/engine/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/components/engine/api/client/utils.go b/components/engine/api/client/utils.go index 8f303dcd98..2620683708 100644 --- a/components/engine/api/client/utils.go +++ b/components/engine/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/components/engine/api/container.go b/components/engine/api/container.go deleted file mode 100644 index 4cc73b2252..0000000000 --- a/components/engine/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 e9b3abdfc5be00b7d5b149a39b38937f107d03cb 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) Upstream-commit: 8aff01c0b447fa4d68f053c44e8baf7b24247164 Component: engine --- .../engine/daemon/execdriver/native/driver.go | 16 ++++++++-------- .../{nsinit => namespaces}/create.go | 2 +- .../libcontainer/{nsinit => namespaces}/exec.go | 2 +- .../{nsinit => namespaces}/execin.go | 2 +- .../libcontainer/{nsinit => namespaces}/init.go | 2 +- .../libcontainer/{nsinit => namespaces}/pid.go | 2 +- .../{nsinit => namespaces}/std_term.go | 2 +- .../{nsinit => namespaces}/sync_pipe.go | 2 +- .../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 components/engine/pkg/libcontainer/{nsinit => namespaces}/create.go (92%) rename components/engine/pkg/libcontainer/{nsinit => namespaces}/exec.go (99%) rename components/engine/pkg/libcontainer/{nsinit => namespaces}/execin.go (99%) rename components/engine/pkg/libcontainer/{nsinit => namespaces}/init.go (99%) rename components/engine/pkg/libcontainer/{nsinit => namespaces}/pid.go (97%) rename components/engine/pkg/libcontainer/{nsinit => namespaces}/std_term.go (97%) rename components/engine/pkg/libcontainer/{nsinit => namespaces}/sync_pipe.go (98%) rename components/engine/pkg/libcontainer/{nsinit => namespaces}/term.go (95%) rename components/engine/pkg/libcontainer/{nsinit => namespaces}/tty_term.go (98%) rename components/engine/pkg/libcontainer/{nsinit => namespaces}/unsupported.go (97%) rename components/engine/pkg/libcontainer/nsinit/{nsinit => }/main.go (83%) diff --git a/components/engine/daemon/execdriver/native/driver.go b/components/engine/daemon/execdriver/native/driver.go index a57c466f2c..41121a70f3 100644 --- a/components/engine/daemon/execdriver/native/driver.go +++ b/components/engine/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/components/engine/pkg/libcontainer/nsinit/create.go b/components/engine/pkg/libcontainer/namespaces/create.go similarity index 92% rename from components/engine/pkg/libcontainer/nsinit/create.go rename to components/engine/pkg/libcontainer/namespaces/create.go index d5cba464d2..60b2a2db02 100644 --- a/components/engine/pkg/libcontainer/nsinit/create.go +++ b/components/engine/pkg/libcontainer/namespaces/create.go @@ -1,4 +1,4 @@ -package nsinit +package namespaces import ( "os" diff --git a/components/engine/pkg/libcontainer/nsinit/exec.go b/components/engine/pkg/libcontainer/namespaces/exec.go similarity index 99% rename from components/engine/pkg/libcontainer/nsinit/exec.go rename to components/engine/pkg/libcontainer/namespaces/exec.go index f266303f8c..288205ea60 100644 --- a/components/engine/pkg/libcontainer/nsinit/exec.go +++ b/components/engine/pkg/libcontainer/namespaces/exec.go @@ -1,6 +1,6 @@ // +build linux -package nsinit +package namespaces import ( "os" diff --git a/components/engine/pkg/libcontainer/nsinit/execin.go b/components/engine/pkg/libcontainer/namespaces/execin.go similarity index 99% rename from components/engine/pkg/libcontainer/nsinit/execin.go rename to components/engine/pkg/libcontainer/namespaces/execin.go index 40b95093dd..09bf40582a 100644 --- a/components/engine/pkg/libcontainer/nsinit/execin.go +++ b/components/engine/pkg/libcontainer/namespaces/execin.go @@ -1,6 +1,6 @@ // +build linux -package nsinit +package namespaces import ( "fmt" diff --git a/components/engine/pkg/libcontainer/nsinit/init.go b/components/engine/pkg/libcontainer/namespaces/init.go similarity index 99% rename from components/engine/pkg/libcontainer/nsinit/init.go rename to components/engine/pkg/libcontainer/namespaces/init.go index dc983161fa..b53c56668d 100644 --- a/components/engine/pkg/libcontainer/nsinit/init.go +++ b/components/engine/pkg/libcontainer/namespaces/init.go @@ -1,6 +1,6 @@ // +build linux -package nsinit +package namespaces import ( "fmt" diff --git a/components/engine/pkg/libcontainer/nsinit/pid.go b/components/engine/pkg/libcontainer/namespaces/pid.go similarity index 97% rename from components/engine/pkg/libcontainer/nsinit/pid.go rename to components/engine/pkg/libcontainer/namespaces/pid.go index bba2f10e1b..8d97ec1463 100644 --- a/components/engine/pkg/libcontainer/nsinit/pid.go +++ b/components/engine/pkg/libcontainer/namespaces/pid.go @@ -1,4 +1,4 @@ -package nsinit +package namespaces import ( "fmt" diff --git a/components/engine/pkg/libcontainer/nsinit/std_term.go b/components/engine/pkg/libcontainer/namespaces/std_term.go similarity index 97% rename from components/engine/pkg/libcontainer/nsinit/std_term.go rename to components/engine/pkg/libcontainer/namespaces/std_term.go index 2b8201a71b..324336af28 100644 --- a/components/engine/pkg/libcontainer/nsinit/std_term.go +++ b/components/engine/pkg/libcontainer/namespaces/std_term.go @@ -1,4 +1,4 @@ -package nsinit +package namespaces import ( "io" diff --git a/components/engine/pkg/libcontainer/nsinit/sync_pipe.go b/components/engine/pkg/libcontainer/namespaces/sync_pipe.go similarity index 98% rename from components/engine/pkg/libcontainer/nsinit/sync_pipe.go rename to components/engine/pkg/libcontainer/namespaces/sync_pipe.go index d0bfdda865..e12ed447fa 100644 --- a/components/engine/pkg/libcontainer/nsinit/sync_pipe.go +++ b/components/engine/pkg/libcontainer/namespaces/sync_pipe.go @@ -1,4 +1,4 @@ -package nsinit +package namespaces import ( "encoding/json" diff --git a/components/engine/pkg/libcontainer/nsinit/term.go b/components/engine/pkg/libcontainer/namespaces/term.go similarity index 95% rename from components/engine/pkg/libcontainer/nsinit/term.go rename to components/engine/pkg/libcontainer/namespaces/term.go index 5fc801ab53..2a50bf8554 100644 --- a/components/engine/pkg/libcontainer/nsinit/term.go +++ b/components/engine/pkg/libcontainer/namespaces/term.go @@ -1,4 +1,4 @@ -package nsinit +package namespaces import ( "io" diff --git a/components/engine/pkg/libcontainer/nsinit/tty_term.go b/components/engine/pkg/libcontainer/namespaces/tty_term.go similarity index 98% rename from components/engine/pkg/libcontainer/nsinit/tty_term.go rename to components/engine/pkg/libcontainer/namespaces/tty_term.go index fc6e1ab499..272cf2cd65 100644 --- a/components/engine/pkg/libcontainer/nsinit/tty_term.go +++ b/components/engine/pkg/libcontainer/namespaces/tty_term.go @@ -1,4 +1,4 @@ -package nsinit +package namespaces import ( "io" diff --git a/components/engine/pkg/libcontainer/nsinit/unsupported.go b/components/engine/pkg/libcontainer/namespaces/unsupported.go similarity index 97% rename from components/engine/pkg/libcontainer/nsinit/unsupported.go rename to components/engine/pkg/libcontainer/namespaces/unsupported.go index 51509f79a2..b459b4d2f5 100644 --- a/components/engine/pkg/libcontainer/nsinit/unsupported.go +++ b/components/engine/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/components/engine/pkg/libcontainer/nsinit/nsinit/main.go b/components/engine/pkg/libcontainer/nsinit/main.go similarity index 83% rename from components/engine/pkg/libcontainer/nsinit/nsinit/main.go rename to components/engine/pkg/libcontainer/nsinit/main.go index 5d968375ed..6659a1310e 100644 --- a/components/engine/pkg/libcontainer/nsinit/nsinit/main.go +++ b/components/engine/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 a1eeb956821680369a5ee633a9d49fc3509a55fa 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) Upstream-commit: 2819677c215bd409b4ce4da51f0ddfb303760796 Component: engine --- components/engine/docs/mkdocs.yml | 16 ++--- components/engine/docs/s3_website.json | 3 +- .../engine/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 +++--- .../engine/docs/sources/docker-io/accounts.md | 32 ---------- .../engine/docs/sources/examples/mongodb.md | 15 +++-- .../docs/sources/examples/nodejs_web_app.md | 6 +- .../sources/examples/postgresql_service.md | 2 +- .../sources/examples/running_riak_service.md | 4 +- components/engine/docs/sources/faq.md | 2 +- components/engine/docs/sources/index.md | 2 +- .../introduction/understanding-docker.md | 18 +++--- .../engine/docs/sources/reference/api.md | 2 +- .../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 ++--- .../engine/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 +- .../engine/docs/sources/userguide/index.md | 26 ++++----- .../docs/sources/userguide/usingdocker.md | 2 +- 40 files changed, 208 insertions(+), 202 deletions(-) create mode 100644 components/engine/docs/sources/docker-hub/accounts.md rename components/engine/docs/sources/{docker-io => docker-hub}/builds.md (78%) rename components/engine/docs/sources/{docker-io => docker-hub}/home.md (54%) rename components/engine/docs/sources/{docker-io => docker-hub}/index.md (87%) rename components/engine/docs/sources/{docker-io => docker-hub}/repos.md (83%) delete mode 100644 components/engine/docs/sources/docker-io/accounts.md rename components/engine/docs/sources/userguide/{dockerio.md => dockerhub.md} (72%) diff --git a/components/engine/docs/mkdocs.yml b/components/engine/docs/mkdocs.yml index 2835cd9dde..03483abd08 100755 --- a/components/engine/docs/mkdocs.yml +++ b/components/engine/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/components/engine/docs/s3_website.json b/components/engine/docs/s3_website.json index eab6ae820c..1e6aeea753 100644 --- a/components/engine/docs/s3_website.json +++ b/components/engine/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/components/engine/docs/sources/articles/basics.md b/components/engine/docs/sources/articles/basics.md index cd4a9df652..e931569652 100644 --- a/components/engine/docs/sources/articles/basics.md +++ b/components/engine/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/components/engine/docs/sources/docker-hub/accounts.md b/components/engine/docs/sources/docker-hub/accounts.md new file mode 100644 index 0000000000..43df62a9bb --- /dev/null +++ b/components/engine/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/components/engine/docs/sources/docker-io/builds.md b/components/engine/docs/sources/docker-hub/builds.md similarity index 78% rename from components/engine/docs/sources/docker-io/builds.md rename to components/engine/docs/sources/docker-hub/builds.md index c70de4006c..09876479f9 100644 --- a/components/engine/docs/sources/docker-io/builds.md +++ b/components/engine/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/components/engine/docs/sources/docker-io/home.md b/components/engine/docs/sources/docker-hub/home.md similarity index 54% rename from components/engine/docs/sources/docker-io/home.md rename to components/engine/docs/sources/docker-hub/home.md index d29de76fbf..15baf7b83a 100644 --- a/components/engine/docs/sources/docker-io/home.md +++ b/components/engine/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/components/engine/docs/sources/docker-io/index.md b/components/engine/docs/sources/docker-hub/index.md similarity index 87% rename from components/engine/docs/sources/docker-io/index.md rename to components/engine/docs/sources/docker-hub/index.md index dc83f0b281..250f2b3a1b 100644 --- a/components/engine/docs/sources/docker-io/index.md +++ b/components/engine/docs/sources/docker-hub/index.md @@ -1,4 +1,4 @@ -# Docker.io +# Docker Hub ## Contents: diff --git a/components/engine/docs/sources/docker-io/repos.md b/components/engine/docs/sources/docker-hub/repos.md similarity index 83% rename from components/engine/docs/sources/docker-io/repos.md rename to components/engine/docs/sources/docker-hub/repos.md index 11170182a4..5f8cb5b0ee 100644 --- a/components/engine/docs/sources/docker-io/repos.md +++ b/components/engine/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/components/engine/docs/sources/docker-io/accounts.md b/components/engine/docs/sources/docker-io/accounts.md deleted file mode 100644 index cfbcd9512c..0000000000 --- a/components/engine/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/components/engine/docs/sources/examples/mongodb.md b/components/engine/docs/sources/examples/mongodb.md index 17d58e0dbc..602f55ca88 100644 --- a/components/engine/docs/sources/examples/mongodb.md +++ b/components/engine/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/components/engine/docs/sources/examples/nodejs_web_app.md b/components/engine/docs/sources/examples/nodejs_web_app.md index 99946e99e0..cf00e88bed 100644 --- a/components/engine/docs/sources/examples/nodejs_web_app.md +++ b/components/engine/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/components/engine/docs/sources/examples/postgresql_service.md b/components/engine/docs/sources/examples/postgresql_service.md index b931fd8ba4..b9fae49d99 100644 --- a/components/engine/docs/sources/examples/postgresql_service.md +++ b/components/engine/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/components/engine/docs/sources/examples/running_riak_service.md b/components/engine/docs/sources/examples/running_riak_service.md index 098cc9094b..5909b7e2b0 100644 --- a/components/engine/docs/sources/examples/running_riak_service.md +++ b/components/engine/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/components/engine/docs/sources/faq.md b/components/engine/docs/sources/faq.md index 8dbdfd184f..fa7deb4e24 100644 --- a/components/engine/docs/sources/faq.md +++ b/components/engine/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/components/engine/docs/sources/index.md b/components/engine/docs/sources/index.md index f7295d9c10..4a66dd1dd8 100644 --- a/components/engine/docs/sources/index.md +++ b/components/engine/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/components/engine/docs/sources/introduction/understanding-docker.md b/components/engine/docs/sources/introduction/understanding-docker.md index e9041420af..e9f5a1bfe8 100644 --- a/components/engine/docs/sources/introduction/understanding-docker.md +++ b/components/engine/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/components/engine/docs/sources/reference/api.md b/components/engine/docs/sources/reference/api.md index 9f185a0e37..720d40b2e8 100644 --- a/components/engine/docs/sources/reference/api.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker-io_api.md b/components/engine/docs/sources/reference/api/docker-io_api.md index 66cf311b41..d5be332d35 100644 --- a/components/engine/docs/sources/reference/api/docker-io_api.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.0.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.0.md index ba5338c5f9..2f17b2a74d 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.0.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.1.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.1.md index b884ee69dc..e777901c6c 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.1.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.md index 8676833af9..35d6b039dd 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md index b77b33b097..476ef61d95 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md index d941a1d45f..e4316bfb5e 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.2.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.2.md index e231cff02f..cecab5bb4e 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.2.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.3.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.3.md index 71c70273fd..1d60b4300d 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.3.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.4.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.4.md index 253944fd9a..f7d6e82c19 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.4.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.5.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.5.md index 7dc5334f45..53d970accd 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.5.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.6.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.6.md index 021a357b79..9b7cded33f 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.6.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.7.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.7.md index 02073e9b60..3432e9bb21 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.7.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.8.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.8.md index a691930c6b..184e107cdc 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.8.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.9.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.9.md index d67443fd26..fc9f9b8d5b 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.9.md +++ b/components/engine/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/components/engine/docs/sources/reference/commandline/cli.md b/components/engine/docs/sources/reference/commandline/cli.md index 16190ab1de..308e7d35df 100644 --- a/components/engine/docs/sources/reference/commandline/cli.md +++ b/components/engine/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/components/engine/docs/sources/terms/registry.md b/components/engine/docs/sources/terms/registry.md index 08f8e8f69d..8a7e6237ec 100644 --- a/components/engine/docs/sources/terms/registry.md +++ b/components/engine/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/components/engine/docs/sources/userguide/dockerio.md b/components/engine/docs/sources/userguide/dockerhub.md similarity index 72% rename from components/engine/docs/sources/userguide/dockerio.md rename to components/engine/docs/sources/userguide/dockerhub.md index e5c6c6dace..7e7acbef16 100644 --- a/components/engine/docs/sources/userguide/dockerio.md +++ b/components/engine/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/components/engine/docs/sources/userguide/dockerimages.md b/components/engine/docs/sources/userguide/dockerimages.md index bd8b0d2c2e..19881c258b 100644 --- a/components/engine/docs/sources/userguide/dockerimages.md +++ b/components/engine/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/components/engine/docs/sources/userguide/dockerizing.md b/components/engine/docs/sources/userguide/dockerizing.md index 79a2066c62..be3ccd4449 100644 --- a/components/engine/docs/sources/userguide/dockerizing.md +++ b/components/engine/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/components/engine/docs/sources/userguide/dockerrepos.md b/components/engine/docs/sources/userguide/dockerrepos.md index d18ec13ccd..5ebad96986 100644 --- a/components/engine/docs/sources/userguide/dockerrepos.md +++ b/components/engine/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/components/engine/docs/sources/userguide/dockervolumes.md b/components/engine/docs/sources/userguide/dockervolumes.md index 34cfe05b47..f3a4612350 100644 --- a/components/engine/docs/sources/userguide/dockervolumes.md +++ b/components/engine/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/components/engine/docs/sources/userguide/index.md b/components/engine/docs/sources/userguide/index.md index 7150540433..54400cef79 100644 --- a/components/engine/docs/sources/userguide/index.md +++ b/components/engine/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/components/engine/docs/sources/userguide/usingdocker.md b/components/engine/docs/sources/userguide/usingdocker.md index 2eaf707d1a..89401b19b4 100644 --- a/components/engine/docs/sources/userguide/usingdocker.md +++ b/components/engine/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 e29c2c6bded80ce876b2c2842cc3cf32f05647b2 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) Upstream-commit: 7a145b022a3b50b0d04cb0471d517f7c6c5efc67 Component: engine --- components/engine/api/server/server.go | 27 ---------- components/engine/daemon/container.go | 36 ------------- .../reference/api/docker_remote_api.md | 3 ++ .../reference/api/docker_remote_api_v1.11.md | 25 --------- components/engine/integration/server_test.go | 22 -------- components/engine/server/server.go | 51 ------------------- 6 files changed, 3 insertions(+), 161 deletions(-) diff --git a/components/engine/api/server/server.go b/components/engine/api/server/server.go index a2c02f57fd..61407b2648 100644 --- a/components/engine/api/server/server.go +++ b/components/engine/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/components/engine/daemon/container.go b/components/engine/daemon/container.go index bb02b37f5b..b4a33e3e18 100644 --- a/components/engine/daemon/container.go +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api.md b/components/engine/docs/sources/reference/api/docker_remote_api.md index 355102a707..9db9a9757d 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md index b77b33b097..e72545b6f9 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md +++ b/components/engine/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/components/engine/integration/server_test.go b/components/engine/integration/server_test.go index 3752c9b7e6..151490cdc6 100644 --- a/components/engine/integration/server_test.go +++ b/components/engine/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/components/engine/server/server.go b/components/engine/server/server.go index b439c0f2d8..93c6d39baf 100644 --- a/components/engine/server/server.go +++ b/components/engine/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 f1f202bb4068100869d47b755eb1fe1f281d22de 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) Upstream-commit: 0a725ea28259f8a0f9a1de5730fd99675b942dde Component: engine --- .../pkg/libcontainer/namespaces/execin.go | 116 ++++---------- .../pkg/libcontainer/namespaces/nsenter.go | 142 ++++++++++++++++++ .../engine/pkg/libcontainer/nsinit/main.go | 63 +++++++- 3 files changed, 224 insertions(+), 97 deletions(-) create mode 100644 components/engine/pkg/libcontainer/namespaces/nsenter.go diff --git a/components/engine/pkg/libcontainer/namespaces/execin.go b/components/engine/pkg/libcontainer/namespaces/execin.go index 09bf40582a..699c67dbc7 100644 --- a/components/engine/pkg/libcontainer/namespaces/execin.go +++ b/components/engine/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/components/engine/pkg/libcontainer/namespaces/nsenter.go b/components/engine/pkg/libcontainer/namespaces/nsenter.go new file mode 100644 index 0000000000..c5dd2e7953 --- /dev/null +++ b/components/engine/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/components/engine/pkg/libcontainer/nsinit/main.go b/components/engine/pkg/libcontainer/nsinit/main.go index 6659a1310e..bddc1992fd 100644 --- a/components/engine/pkg/libcontainer/nsinit/main.go +++ b/components/engine/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 a3fd3c7cb627be79b05847827173acca5aace02b 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) Upstream-commit: 8497d1274b046804999699ccb66b11a3249906a1 Component: engine --- .../engine/pkg/libcontainer/namespaces/execin.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/components/engine/pkg/libcontainer/namespaces/execin.go b/components/engine/pkg/libcontainer/namespaces/execin.go index 699c67dbc7..4d5671e778 100644 --- a/components/engine/pkg/libcontainer/namespaces/execin.go +++ b/components/engine/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 f1a3b9e98b1d8a6bcac63150873f616824165188 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) Upstream-commit: 7eb508633db93213404292bd4fd21b6855f45bea Component: engine --- components/engine/pkg/libcontainer/namespaces/nsenter.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/engine/pkg/libcontainer/namespaces/nsenter.go b/components/engine/pkg/libcontainer/namespaces/nsenter.go index c5dd2e7953..d5c2e761b7 100644 --- a/components/engine/pkg/libcontainer/namespaces/nsenter.go +++ b/components/engine/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 d0a1649e4a4f0bd9f2918933b2c4318b2752e96c 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) Upstream-commit: 0d1906feea511cbfd1695adbb0b39dfdfae2caeb Component: engine --- .../engine/docs/sources/reference/api/docker_remote_api.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/engine/docs/sources/reference/api/docker_remote_api.md b/components/engine/docs/sources/reference/api/docker_remote_api.md index 9db9a9757d..9d4069f70a 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api.md +++ b/components/engine/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 82966c07984dbe037d9a739dd2586583efdba1e0 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) Upstream-commit: 13f07b636f014331a5d5dc0eac40d6ac9e5c9263 Component: engine --- components/engine/pkg/units/size.go | 36 ++++++++++++++++++++++++ components/engine/pkg/units/size_test.go | 35 +++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/components/engine/pkg/units/size.go b/components/engine/pkg/units/size.go index 99c8800965..480ec2f141 100644 --- a/components/engine/pkg/units/size.go +++ b/components/engine/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/components/engine/pkg/units/size_test.go b/components/engine/pkg/units/size_test.go index 958a4ca13d..5240bbd9f0 100644 --- a/components/engine/pkg/units/size_test.go +++ b/components/engine/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 d381e276cf32bc6304d130a9205d319a2167cbe9 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) Upstream-commit: 948e54ac455f88c79524dcf117df80f2d4c3f96c Component: engine --- .../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/components/engine/daemon/graphdriver/devmapper/deviceset.go b/components/engine/daemon/graphdriver/devmapper/deviceset.go index 4de7858c1f..f36faca900 100644 --- a/components/engine/daemon/graphdriver/devmapper/deviceset.go +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/devmapper.go b/components/engine/daemon/graphdriver/devmapper/devmapper.go index d78ae1ef8b..a6602c276e 100644 --- a/components/engine/daemon/graphdriver/devmapper/devmapper.go +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/devmapper_wrapper.go b/components/engine/daemon/graphdriver/devmapper/devmapper_wrapper.go index bf558affc8..9f1b5a6054 100644 --- a/components/engine/daemon/graphdriver/devmapper/devmapper_wrapper.go +++ b/components/engine/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 83cd70635324d893a90eb27d3f1a05dabc75408b 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) Upstream-commit: 822ea97ffcf10645720bb93108a60f8b9ce9931d Component: engine --- components/engine/daemon/daemon.go | 4 ++-- components/engine/daemon/graphdriver/aufs/aufs.go | 2 +- .../engine/daemon/graphdriver/aufs/aufs_test.go | 2 +- .../engine/daemon/graphdriver/btrfs/btrfs.go | 2 +- .../engine/daemon/graphdriver/devmapper/driver.go | 2 +- components/engine/daemon/graphdriver/driver.go | 14 +++++++------- .../daemon/graphdriver/graphtest/graphtest.go | 2 +- components/engine/daemon/graphdriver/vfs/driver.go | 2 +- components/engine/daemonconfig/config.go | 5 +++++ components/engine/docker/docker.go | 3 +++ .../docs/sources/reference/commandline/cli.md | 1 + components/engine/graph/tags_unit_test.go | 2 +- components/engine/integration/graph_test.go | 2 +- 13 files changed, 26 insertions(+), 17 deletions(-) diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go index 8879d019ec..2c2b046946 100644 --- a/components/engine/daemon/daemon.go +++ b/components/engine/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/components/engine/daemon/graphdriver/aufs/aufs.go b/components/engine/daemon/graphdriver/aufs/aufs.go index 79de4cda79..43c3128271 100644 --- a/components/engine/daemon/graphdriver/aufs/aufs.go +++ b/components/engine/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/components/engine/daemon/graphdriver/aufs/aufs_test.go b/components/engine/daemon/graphdriver/aufs/aufs_test.go index dab5aecc41..b3bad410a5 100644 --- a/components/engine/daemon/graphdriver/aufs/aufs_test.go +++ b/components/engine/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/components/engine/daemon/graphdriver/btrfs/btrfs.go b/components/engine/daemon/graphdriver/btrfs/btrfs.go index 56299b18ee..ba3ecba761 100644 --- a/components/engine/daemon/graphdriver/btrfs/btrfs.go +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/driver.go b/components/engine/daemon/graphdriver/devmapper/driver.go index 609971cda1..50bda1cd2e 100644 --- a/components/engine/daemon/graphdriver/devmapper/driver.go +++ b/components/engine/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/components/engine/daemon/graphdriver/driver.go b/components/engine/daemon/graphdriver/driver.go index 8f9c0b6d2b..93d4ed2535 100644 --- a/components/engine/daemon/graphdriver/driver.go +++ b/components/engine/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/components/engine/daemon/graphdriver/graphtest/graphtest.go b/components/engine/daemon/graphdriver/graphtest/graphtest.go index 56ea8f7d42..d53878c45a 100644 --- a/components/engine/daemon/graphdriver/graphtest/graphtest.go +++ b/components/engine/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/components/engine/daemon/graphdriver/vfs/driver.go b/components/engine/daemon/graphdriver/vfs/driver.go index 7473aa659d..992af0e149 100644 --- a/components/engine/daemon/graphdriver/vfs/driver.go +++ b/components/engine/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/components/engine/daemonconfig/config.go b/components/engine/daemonconfig/config.go index 619bfe582f..9f77d84a58 100644 --- a/components/engine/daemonconfig/config.go +++ b/components/engine/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/components/engine/docker/docker.go b/components/engine/docker/docker.go index 4215ed3a95..56bcb04e41 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/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/components/engine/docs/sources/reference/commandline/cli.md b/components/engine/docs/sources/reference/commandline/cli.md index 16190ab1de..fd5d140a00 100644 --- a/components/engine/docs/sources/reference/commandline/cli.md +++ b/components/engine/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/components/engine/graph/tags_unit_test.go b/components/engine/graph/tags_unit_test.go index bc438131ca..42e097724d 100644 --- a/components/engine/graph/tags_unit_test.go +++ b/components/engine/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/components/engine/integration/graph_test.go b/components/engine/integration/graph_test.go index c29055edfc..dc056f7e1c 100644 --- a/components/engine/integration/graph_test.go +++ b/components/engine/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 cb7dedffd7ea6f28c5cc2e56683dc792517e3309 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) Upstream-commit: 7f5ba068f438ee159bfca7396de4987bcae45809 Component: engine --- .../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 components/engine/daemon/graphdriver/devmapper/README.md diff --git a/components/engine/daemon/graphdriver/devmapper/README.md b/components/engine/daemon/graphdriver/devmapper/README.md new file mode 100644 index 0000000000..f2c855c893 --- /dev/null +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/deviceset.go b/components/engine/daemon/graphdriver/devmapper/deviceset.go index f36faca900..430f5c2e60 100644 --- a/components/engine/daemon/graphdriver/devmapper/deviceset.go +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/driver.go b/components/engine/daemon/graphdriver/devmapper/driver.go index 50bda1cd2e..5bfd8ee658 100644 --- a/components/engine/daemon/graphdriver/devmapper/driver.go +++ b/components/engine/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 28ddc638943609e3d5bc86a0021673a4c752c37e 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) Upstream-commit: 10083f414017636065aa50610f07784738df8e7a Component: engine --- .../daemon/graphdriver/devmapper/deviceset.go | 9 +++- .../daemon/graphdriver/devmapper/mount.go | 47 +++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/components/engine/daemon/graphdriver/devmapper/deviceset.go b/components/engine/daemon/graphdriver/devmapper/deviceset.go index 430f5c2e60..5e055f6dba 100644 --- a/components/engine/daemon/graphdriver/devmapper/deviceset.go +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/mount.go b/components/engine/daemon/graphdriver/devmapper/mount.go index 6de9e46c8c..4ec0698c6c 100644 --- a/components/engine/daemon/graphdriver/devmapper/mount.go +++ b/components/engine/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 42f2781e59965b16c0d55be5940a28326c22a50c 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) Upstream-commit: 807bc2cd049d97f31eab54ce3d5719d63240e3e7 Component: engine --- .../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/components/engine/daemon/graphdriver/devmapper/README.md b/components/engine/daemon/graphdriver/devmapper/README.md index f2c855c893..4d32a14355 100644 --- a/components/engine/daemon/graphdriver/devmapper/README.md +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/deviceset.go b/components/engine/daemon/graphdriver/devmapper/deviceset.go index 5e055f6dba..13f68f61e3 100644 --- a/components/engine/daemon/graphdriver/devmapper/deviceset.go +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/mount.go b/components/engine/daemon/graphdriver/devmapper/mount.go index 4ec0698c6c..c9ff216d5d 100644 --- a/components/engine/daemon/graphdriver/devmapper/mount.go +++ b/components/engine/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 af11ee865ff474ed84e3fb11f047969ff7595922 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) Upstream-commit: a226168a8b877d632cb87c95dd0288f6092b9d8f Component: engine --- .../daemon/graphdriver/devmapper/README.md | 30 ++++++ .../daemon/graphdriver/devmapper/deviceset.go | 96 +++++++++++++------ 2 files changed, 97 insertions(+), 29 deletions(-) diff --git a/components/engine/daemon/graphdriver/devmapper/README.md b/components/engine/daemon/graphdriver/devmapper/README.md index 4d32a14355..53b7174dba 100644 --- a/components/engine/daemon/graphdriver/devmapper/README.md +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/deviceset.go b/components/engine/daemon/graphdriver/devmapper/deviceset.go index 13f68f61e3..497e1102c2 100644 --- a/components/engine/daemon/graphdriver/devmapper/deviceset.go +++ b/components/engine/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 3ee07ce41f0500c00b6fe9583f71b838342a42f4 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) Upstream-commit: 0434a2ce64c0ce07e97e9a516cef226be67d5f5b Component: engine --- .../daemon/graphdriver/devmapper/README.md | 16 +++++++++++ .../daemon/graphdriver/devmapper/deviceset.go | 28 +++++++++++++++---- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/components/engine/daemon/graphdriver/devmapper/README.md b/components/engine/daemon/graphdriver/devmapper/README.md index 53b7174dba..ccff3f021b 100644 --- a/components/engine/daemon/graphdriver/devmapper/README.md +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/deviceset.go b/components/engine/daemon/graphdriver/devmapper/deviceset.go index 497e1102c2..a930bf2b96 100644 --- a/components/engine/daemon/graphdriver/devmapper/deviceset.go +++ b/components/engine/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 81d8873d1eb203955a25e45aae28a4fa0b4ae5ee 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) Upstream-commit: 8c646ebf16bd0e2618cbca430bc129ee7416a21c Component: engine --- .../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/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.md index 8676833af9..9c39611b34 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md index e72545b6f9..f639557e13 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md index d941a1d45f..08a06a45d4 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md +++ b/components/engine/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 15bbcd7308b7287c6e8f993bc9c918ee17a52ef2 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) Upstream-commit: 04620e01547d6526023b49494919f3eb3fcf900a Component: engine --- .../docs/sources/examples/running_ssh_service.Dockerfile | 7 +++---- .../engine/docs/sources/examples/running_ssh_service.md | 5 ++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/components/engine/docs/sources/examples/running_ssh_service.Dockerfile b/components/engine/docs/sources/examples/running_ssh_service.Dockerfile index dd2acb7a4b..978e610422 100644 --- a/components/engine/docs/sources/examples/running_ssh_service.Dockerfile +++ b/components/engine/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/components/engine/docs/sources/examples/running_ssh_service.md b/components/engine/docs/sources/examples/running_ssh_service.md index 5be2c7053e..27439f998f 100644 --- a/components/engine/docs/sources/examples/running_ssh_service.md +++ b/components/engine/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 cdfc620fa199bd39226d4a5dea6e0483eced8357 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) Upstream-commit: d08c965e2c6ed223e10773ac77f95b7b6e273e0a Component: engine --- components/engine/docs/sources/reference/commandline/cli.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/engine/docs/sources/reference/commandline/cli.md b/components/engine/docs/sources/reference/commandline/cli.md index 829f13b9a6..3e61c94adc 100644 --- a/components/engine/docs/sources/reference/commandline/cli.md +++ b/components/engine/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 51ac70314e175c776318fc05f714ea7647c7ee5f 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) Upstream-commit: a12453186323372f4d4e23980d3f1caa2de91e5a Component: engine --- components/engine/registry/registry.go | 29 ++++++++- components/engine/registry/registry_test.go | 71 ++++++++++++++++++++- 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/components/engine/registry/registry.go b/components/engine/registry/registry.go index 7bcf066019..8d1a9f2287 100644 --- a/components/engine/registry/registry.go +++ b/components/engine/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/components/engine/registry/registry_test.go b/components/engine/registry/registry_test.go index e207359e61..2857ab4a49 100644 --- a/components/engine/registry/registry_test.go +++ b/components/engine/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 9760ac9e6fd399646b9040f39cb2f5d6132191f6 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) Upstream-commit: 1a3d43c23ec7bbb1aa206581acd0497c47e29a2f Component: engine --- .../engine/pkg/libcontainer/nsinit/exec.go | 74 ++++++ .../engine/pkg/libcontainer/nsinit/init.go | 43 ++++ .../engine/pkg/libcontainer/nsinit/main.go | 231 +++--------------- .../engine/pkg/libcontainer/nsinit/spec.go | 38 +++ .../engine/pkg/libcontainer/nsinit/stats.go | 42 ++++ .../engine/pkg/libcontainer/nsinit/utils.go | 52 ++++ 6 files changed, 277 insertions(+), 203 deletions(-) create mode 100644 components/engine/pkg/libcontainer/nsinit/exec.go create mode 100644 components/engine/pkg/libcontainer/nsinit/init.go create mode 100644 components/engine/pkg/libcontainer/nsinit/spec.go create mode 100644 components/engine/pkg/libcontainer/nsinit/stats.go create mode 100644 components/engine/pkg/libcontainer/nsinit/utils.go diff --git a/components/engine/pkg/libcontainer/nsinit/exec.go b/components/engine/pkg/libcontainer/nsinit/exec.go new file mode 100644 index 0000000000..ef33d3a5a3 --- /dev/null +++ b/components/engine/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/components/engine/pkg/libcontainer/nsinit/init.go b/components/engine/pkg/libcontainer/nsinit/init.go new file mode 100644 index 0000000000..561b93bb71 --- /dev/null +++ b/components/engine/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/components/engine/pkg/libcontainer/nsinit/main.go b/components/engine/pkg/libcontainer/nsinit/main.go index bddc1992fd..29b80bad49 100644 --- a/components/engine/pkg/libcontainer/nsinit/main.go +++ b/components/engine/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/components/engine/pkg/libcontainer/nsinit/spec.go b/components/engine/pkg/libcontainer/nsinit/spec.go new file mode 100644 index 0000000000..92a2f64cdc --- /dev/null +++ b/components/engine/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/components/engine/pkg/libcontainer/nsinit/stats.go b/components/engine/pkg/libcontainer/nsinit/stats.go new file mode 100644 index 0000000000..0e930823b1 --- /dev/null +++ b/components/engine/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/components/engine/pkg/libcontainer/nsinit/utils.go b/components/engine/pkg/libcontainer/nsinit/utils.go new file mode 100644 index 0000000000..fc311a351d --- /dev/null +++ b/components/engine/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 19c2bab6db2f173091801d3ec3fb15b9a67cb0dc 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) Upstream-commit: f3621531579495e7de911004d47e077d421fcfb8 Component: engine --- .../engine/pkg/libcontainer/nsinit/exec.go | 12 +++--- .../engine/pkg/libcontainer/nsinit/init.go | 5 +++ .../engine/pkg/libcontainer/nsinit/main.go | 17 +++----- .../engine/pkg/libcontainer/nsinit/nsenter.go | 40 +++++++++++++++++++ .../engine/pkg/libcontainer/nsinit/spec.go | 14 ++++--- .../engine/pkg/libcontainer/nsinit/stats.go | 14 ++++--- .../engine/pkg/libcontainer/nsinit/utils.go | 10 +++++ 7 files changed, 85 insertions(+), 27 deletions(-) create mode 100644 components/engine/pkg/libcontainer/nsinit/nsenter.go diff --git a/components/engine/pkg/libcontainer/nsinit/exec.go b/components/engine/pkg/libcontainer/nsinit/exec.go index ef33d3a5a3..d4ce1ca8c4 100644 --- a/components/engine/pkg/libcontainer/nsinit/exec.go +++ b/components/engine/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/components/engine/pkg/libcontainer/nsinit/init.go b/components/engine/pkg/libcontainer/nsinit/init.go index 561b93bb71..20096f0218 100644 --- a/components/engine/pkg/libcontainer/nsinit/init.go +++ b/components/engine/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/components/engine/pkg/libcontainer/nsinit/main.go b/components/engine/pkg/libcontainer/nsinit/main.go index 29b80bad49..20132de0e0 100644 --- a/components/engine/pkg/libcontainer/nsinit/main.go +++ b/components/engine/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/components/engine/pkg/libcontainer/nsinit/nsenter.go b/components/engine/pkg/libcontainer/nsinit/nsenter.go new file mode 100644 index 0000000000..54644282d4 --- /dev/null +++ b/components/engine/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/components/engine/pkg/libcontainer/nsinit/spec.go b/components/engine/pkg/libcontainer/nsinit/spec.go index 92a2f64cdc..2eb4da9fc5 100644 --- a/components/engine/pkg/libcontainer/nsinit/spec.go +++ b/components/engine/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/components/engine/pkg/libcontainer/nsinit/stats.go b/components/engine/pkg/libcontainer/nsinit/stats.go index 0e930823b1..023b40a822 100644 --- a/components/engine/pkg/libcontainer/nsinit/stats.go +++ b/components/engine/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/components/engine/pkg/libcontainer/nsinit/utils.go b/components/engine/pkg/libcontainer/nsinit/utils.go index fc311a351d..9926e2721f 100644 --- a/components/engine/pkg/libcontainer/nsinit/utils.go +++ b/components/engine/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 1492ef2c0625ae34211e57334369bcd361526ade 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) Upstream-commit: fad14eda3af8b4ffa59f74dc5c6583d22bcc9471 Component: engine --- components/engine/docs/mkdocs.yml | 16 ++--- components/engine/docs/s3_website.json | 3 +- .../engine/docs/sources/articles/basics.md | 4 +- .../docs/sources/docker-hub/accounts.md | 31 ---------- .../engine/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 +++--- .../engine/docs/sources/examples/mongodb.md | 15 ++--- .../docs/sources/examples/nodejs_web_app.md | 6 +- .../sources/examples/postgresql_service.md | 2 +- .../sources/examples/running_riak_service.md | 4 +- components/engine/docs/sources/faq.md | 2 +- components/engine/docs/sources/index.md | 2 +- .../introduction/understanding-docker.md | 18 +++--- .../engine/docs/sources/reference/api.md | 2 +- .../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 ++--- .../engine/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 +- .../engine/docs/sources/userguide/index.md | 26 ++++----- .../docs/sources/userguide/usingdocker.md | 2 +- 40 files changed, 202 insertions(+), 208 deletions(-) delete mode 100644 components/engine/docs/sources/docker-hub/accounts.md create mode 100644 components/engine/docs/sources/docker-io/accounts.md rename components/engine/docs/sources/{docker-hub => docker-io}/builds.md (78%) rename components/engine/docs/sources/{docker-hub => docker-io}/home.md (54%) rename components/engine/docs/sources/{docker-hub => docker-io}/index.md (87%) rename components/engine/docs/sources/{docker-hub => docker-io}/repos.md (83%) rename components/engine/docs/sources/userguide/{dockerhub.md => dockerio.md} (72%) diff --git a/components/engine/docs/mkdocs.yml b/components/engine/docs/mkdocs.yml index 03483abd08..2835cd9dde 100755 --- a/components/engine/docs/mkdocs.yml +++ b/components/engine/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/components/engine/docs/s3_website.json b/components/engine/docs/s3_website.json index 1e6aeea753..eab6ae820c 100644 --- a/components/engine/docs/s3_website.json +++ b/components/engine/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/components/engine/docs/sources/articles/basics.md b/components/engine/docs/sources/articles/basics.md index e931569652..cd4a9df652 100644 --- a/components/engine/docs/sources/articles/basics.md +++ b/components/engine/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/components/engine/docs/sources/docker-hub/accounts.md b/components/engine/docs/sources/docker-hub/accounts.md deleted file mode 100644 index 43df62a9bb..0000000000 --- a/components/engine/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/components/engine/docs/sources/docker-io/accounts.md b/components/engine/docs/sources/docker-io/accounts.md new file mode 100644 index 0000000000..cfbcd9512c --- /dev/null +++ b/components/engine/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/components/engine/docs/sources/docker-hub/builds.md b/components/engine/docs/sources/docker-io/builds.md similarity index 78% rename from components/engine/docs/sources/docker-hub/builds.md rename to components/engine/docs/sources/docker-io/builds.md index 09876479f9..c70de4006c 100644 --- a/components/engine/docs/sources/docker-hub/builds.md +++ b/components/engine/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/components/engine/docs/sources/docker-hub/home.md b/components/engine/docs/sources/docker-io/home.md similarity index 54% rename from components/engine/docs/sources/docker-hub/home.md rename to components/engine/docs/sources/docker-io/home.md index 15baf7b83a..d29de76fbf 100644 --- a/components/engine/docs/sources/docker-hub/home.md +++ b/components/engine/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/components/engine/docs/sources/docker-hub/index.md b/components/engine/docs/sources/docker-io/index.md similarity index 87% rename from components/engine/docs/sources/docker-hub/index.md rename to components/engine/docs/sources/docker-io/index.md index 250f2b3a1b..dc83f0b281 100644 --- a/components/engine/docs/sources/docker-hub/index.md +++ b/components/engine/docs/sources/docker-io/index.md @@ -1,4 +1,4 @@ -# Docker Hub +# Docker.io ## Contents: diff --git a/components/engine/docs/sources/docker-hub/repos.md b/components/engine/docs/sources/docker-io/repos.md similarity index 83% rename from components/engine/docs/sources/docker-hub/repos.md rename to components/engine/docs/sources/docker-io/repos.md index 5f8cb5b0ee..11170182a4 100644 --- a/components/engine/docs/sources/docker-hub/repos.md +++ b/components/engine/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/components/engine/docs/sources/examples/mongodb.md b/components/engine/docs/sources/examples/mongodb.md index 602f55ca88..17d58e0dbc 100644 --- a/components/engine/docs/sources/examples/mongodb.md +++ b/components/engine/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/components/engine/docs/sources/examples/nodejs_web_app.md b/components/engine/docs/sources/examples/nodejs_web_app.md index cf00e88bed..99946e99e0 100644 --- a/components/engine/docs/sources/examples/nodejs_web_app.md +++ b/components/engine/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/components/engine/docs/sources/examples/postgresql_service.md b/components/engine/docs/sources/examples/postgresql_service.md index b9fae49d99..b931fd8ba4 100644 --- a/components/engine/docs/sources/examples/postgresql_service.md +++ b/components/engine/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/components/engine/docs/sources/examples/running_riak_service.md b/components/engine/docs/sources/examples/running_riak_service.md index 5909b7e2b0..098cc9094b 100644 --- a/components/engine/docs/sources/examples/running_riak_service.md +++ b/components/engine/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/components/engine/docs/sources/faq.md b/components/engine/docs/sources/faq.md index fa7deb4e24..8dbdfd184f 100644 --- a/components/engine/docs/sources/faq.md +++ b/components/engine/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/components/engine/docs/sources/index.md b/components/engine/docs/sources/index.md index 4a66dd1dd8..f7295d9c10 100644 --- a/components/engine/docs/sources/index.md +++ b/components/engine/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/components/engine/docs/sources/introduction/understanding-docker.md b/components/engine/docs/sources/introduction/understanding-docker.md index e9f5a1bfe8..e9041420af 100644 --- a/components/engine/docs/sources/introduction/understanding-docker.md +++ b/components/engine/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/components/engine/docs/sources/reference/api.md b/components/engine/docs/sources/reference/api.md index 720d40b2e8..9f185a0e37 100644 --- a/components/engine/docs/sources/reference/api.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker-io_api.md b/components/engine/docs/sources/reference/api/docker-io_api.md index d5be332d35..66cf311b41 100644 --- a/components/engine/docs/sources/reference/api/docker-io_api.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.0.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.0.md index 2f17b2a74d..ba5338c5f9 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.0.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.1.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.1.md index e777901c6c..b884ee69dc 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.1.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.md index 0292c1ab2f..9c39611b34 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md index 51c0def92b..f639557e13 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.11.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md index c689ebdd53..08a06a45d4 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.12.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.2.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.2.md index cecab5bb4e..e231cff02f 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.2.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.3.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.3.md index 1d60b4300d..71c70273fd 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.3.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.4.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.4.md index f7d6e82c19..253944fd9a 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.4.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.5.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.5.md index 53d970accd..7dc5334f45 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.5.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.6.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.6.md index 9b7cded33f..021a357b79 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.6.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.7.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.7.md index 3432e9bb21..02073e9b60 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.7.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.8.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.8.md index 184e107cdc..a691930c6b 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.8.md +++ b/components/engine/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/components/engine/docs/sources/reference/api/docker_remote_api_v1.9.md b/components/engine/docs/sources/reference/api/docker_remote_api_v1.9.md index fc9f9b8d5b..d67443fd26 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.9.md +++ b/components/engine/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/components/engine/docs/sources/reference/commandline/cli.md b/components/engine/docs/sources/reference/commandline/cli.md index 308e7d35df..16190ab1de 100644 --- a/components/engine/docs/sources/reference/commandline/cli.md +++ b/components/engine/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/components/engine/docs/sources/terms/registry.md b/components/engine/docs/sources/terms/registry.md index 8a7e6237ec..08f8e8f69d 100644 --- a/components/engine/docs/sources/terms/registry.md +++ b/components/engine/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/components/engine/docs/sources/userguide/dockerimages.md b/components/engine/docs/sources/userguide/dockerimages.md index 19881c258b..bd8b0d2c2e 100644 --- a/components/engine/docs/sources/userguide/dockerimages.md +++ b/components/engine/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/components/engine/docs/sources/userguide/dockerhub.md b/components/engine/docs/sources/userguide/dockerio.md similarity index 72% rename from components/engine/docs/sources/userguide/dockerhub.md rename to components/engine/docs/sources/userguide/dockerio.md index 7e7acbef16..e5c6c6dace 100644 --- a/components/engine/docs/sources/userguide/dockerhub.md +++ b/components/engine/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/components/engine/docs/sources/userguide/dockerizing.md b/components/engine/docs/sources/userguide/dockerizing.md index be3ccd4449..79a2066c62 100644 --- a/components/engine/docs/sources/userguide/dockerizing.md +++ b/components/engine/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/components/engine/docs/sources/userguide/dockerrepos.md b/components/engine/docs/sources/userguide/dockerrepos.md index 5ebad96986..d18ec13ccd 100644 --- a/components/engine/docs/sources/userguide/dockerrepos.md +++ b/components/engine/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/components/engine/docs/sources/userguide/dockervolumes.md b/components/engine/docs/sources/userguide/dockervolumes.md index f3a4612350..34cfe05b47 100644 --- a/components/engine/docs/sources/userguide/dockervolumes.md +++ b/components/engine/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/components/engine/docs/sources/userguide/index.md b/components/engine/docs/sources/userguide/index.md index 54400cef79..7150540433 100644 --- a/components/engine/docs/sources/userguide/index.md +++ b/components/engine/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/components/engine/docs/sources/userguide/usingdocker.md b/components/engine/docs/sources/userguide/usingdocker.md index 89401b19b4..2eaf707d1a 100644 --- a/components/engine/docs/sources/userguide/usingdocker.md +++ b/components/engine/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 ccac994016d4812789d134fec5f5aeb9c200968a 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) Upstream-commit: 09b19752e028f91644e3e2801f0fd616c4adc683 Component: engine --- components/engine/docs/sources/index.md | 77 ++++++++++++------------- 1 file changed, 36 insertions(+), 41 deletions(-) diff --git a/components/engine/docs/sources/index.md b/components/engine/docs/sources/index.md index f7295d9c10..93021c2d83 100644 --- a/components/engine/docs/sources/index.md +++ b/components/engine/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 cefcc5a5b71a83c25f4680395c00fe6978425f5b 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) Upstream-commit: abcb724ed90a1e68234bd9c1c0d9263557b67026 Component: engine --- components/engine/docs/sources/index.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/components/engine/docs/sources/index.md b/components/engine/docs/sources/index.md index 93021c2d83..5a734895d6 100644 --- a/components/engine/docs/sources/index.md +++ b/components/engine/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 9fb60c3559fe22e4a9837c03bff0c5095536dcc0 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) Upstream-commit: b1ac791d8426cd7b6fb5a19f5e918b26c83d83f6 Component: engine --- .../daemon/graphdriver/devmapper/README.md | 2 +- .../daemon/graphdriver/devmapper/deviceset.go | 23 ++----------------- 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/components/engine/daemon/graphdriver/devmapper/README.md b/components/engine/daemon/graphdriver/devmapper/README.md index ccff3f021b..c8ab1d1ee1 100644 --- a/components/engine/daemon/graphdriver/devmapper/README.md +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/deviceset.go b/components/engine/daemon/graphdriver/devmapper/deviceset.go index a930bf2b96..48323f6610 100644 --- a/components/engine/daemon/graphdriver/devmapper/deviceset.go +++ b/components/engine/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 12d535e2c7cfe733011c343276cd00c7362575f8 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) Upstream-commit: 3609b051b88565c0fe0615fd47ddb48eed549d27 Component: engine --- components/engine/daemon/daemon.go | 24 ------------------- .../engine/daemon/graphdriver/aufs/aufs.go | 8 ++++++- .../engine/daemon/graphdriver/btrfs/btrfs.go | 14 +++++++++-- .../daemon/graphdriver/devmapper/driver.go | 15 +++++++++++- .../engine/daemon/graphdriver/driver.go | 19 ++++++++++++++- 5 files changed, 51 insertions(+), 29 deletions(-) diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go index 2c2b046946..445d5c5b6c 100644 --- a/components/engine/daemon/daemon.go +++ b/components/engine/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/components/engine/daemon/graphdriver/aufs/aufs.go b/components/engine/daemon/graphdriver/aufs/aufs.go index 43c3128271..97e9b9748a 100644 --- a/components/engine/daemon/graphdriver/aufs/aufs.go +++ b/components/engine/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/components/engine/daemon/graphdriver/btrfs/btrfs.go b/components/engine/daemon/graphdriver/btrfs/btrfs.go index ba3ecba761..f561244c51 100644 --- a/components/engine/daemon/graphdriver/btrfs/btrfs.go +++ b/components/engine/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/components/engine/daemon/graphdriver/devmapper/driver.go b/components/engine/daemon/graphdriver/devmapper/driver.go index 5bfd8ee658..cf82ad62ed 100644 --- a/components/engine/daemon/graphdriver/devmapper/driver.go +++ b/components/engine/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/components/engine/daemon/graphdriver/driver.go b/components/engine/daemon/graphdriver/driver.go index 93d4ed2535..4536489706 100644 --- a/components/engine/daemon/graphdriver/driver.go +++ b/components/engine/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 fbe9e09f0183d467a9b188f05ae9b111c3ed18ff 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) Upstream-commit: 762c52850177d0eb9148e5f3a0df90c79043246b Component: engine --- components/engine/docs/sources/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/docs/sources/index.md b/components/engine/docs/sources/index.md index 5a734895d6..dc18fcdad8 100644 --- a/components/engine/docs/sources/index.md +++ b/components/engine/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 4a0a27b9ff00e9c534e7d5b1ae055d9bbeff68cc 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) Upstream-commit: 3986bc659be9ff83d521f82e3353b1f73b896cbc Component: engine --- components/engine/hack/make.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/hack/make.sh b/components/engine/hack/make.sh index e7dc09cfbe..cc5582a8df 100755 --- a/components/engine/hack/make.sh +++ b/components/engine/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 1d13810851a755f05ef05d1d93fd24fb6dd6b738 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) Upstream-commit: 34bd2d622910444ae0d30bd0ac32005e224074c1 Component: engine --- components/engine/daemon/daemon.go | 31 ++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/components/engine/daemon/daemon.go b/components/engine/daemon/daemon.go index 445d5c5b6c..b990b0df60 100644 --- a/components/engine/daemon/daemon.go +++ b/components/engine/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 5bfe5a532a028f59b7fe695d5602e989334da8ff 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) Upstream-commit: 30ba7546cb5a1ff7e4915c5a25dd8d72b3bf735b Component: engine --- components/engine/daemon/execdriver/native/driver.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/daemon/execdriver/native/driver.go b/components/engine/daemon/execdriver/native/driver.go index 41121a70f3..d3805b493c 100644 --- a/components/engine/daemon/execdriver/native/driver.go +++ b/components/engine/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 6a34dc5f382690bc364404de294062506533152d 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) Upstream-commit: 14680bf724161785d164fd99de36e3eb03a823ef Component: engine --- components/engine/CHANGELOG.md | 12 ++++++++++++ components/engine/VERSION | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/components/engine/CHANGELOG.md b/components/engine/CHANGELOG.md index 14329ab96c..07f11d90f0 100644 --- a/components/engine/CHANGELOG.md +++ b/components/engine/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/components/engine/VERSION b/components/engine/VERSION index 72ab694d37..ac454c6a1f 100644 --- a/components/engine/VERSION +++ b/components/engine/VERSION @@ -1 +1 @@ -0.11.1-dev +0.12.0