mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge branch 'master' into builder_server-3
Conflicts: utils/utils.go
This commit is contained in:
commit
78f86ea502
24 changed files with 309 additions and 44 deletions
12
FIXME
12
FIXME
|
@ -16,3 +16,15 @@ to put them - so we put them here :)
|
||||||
* Unify build commands and regular commands
|
* Unify build commands and regular commands
|
||||||
* Move source code into src/ subdir for clarity
|
* Move source code into src/ subdir for clarity
|
||||||
* Clean up the Makefile, it's a mess
|
* Clean up the Makefile, it's a mess
|
||||||
|
- docker buidl: show short IDs
|
||||||
|
- docker build: on non-existent local path for ADD, don't show full absolute path on the host
|
||||||
|
- mount into /dockerinit rather than /sbin/init
|
||||||
|
- docker tag foo REPO:TAG
|
||||||
|
- use size header for progress bar in pull
|
||||||
|
- Clean up context upload in build!!!
|
||||||
|
- Parallel pull
|
||||||
|
- Ensure /proc/sys/net/ipv4/ip_forward is 1
|
||||||
|
- Force DNS to public!
|
||||||
|
- Always generate a resolv.conf per container, to avoid changing resolv.conf under thne container's feet
|
||||||
|
- Save metadata with import/export
|
||||||
|
- Upgrade dockerd without stopping containers
|
||||||
|
|
16
Vagrantfile
vendored
16
Vagrantfile
vendored
|
@ -5,11 +5,13 @@ BOX_NAME = ENV['BOX_NAME'] || "ubuntu"
|
||||||
BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64.box"
|
BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64.box"
|
||||||
AWS_REGION = ENV['AWS_REGION'] || "us-east-1"
|
AWS_REGION = ENV['AWS_REGION'] || "us-east-1"
|
||||||
AWS_AMI = ENV['AWS_AMI'] || "ami-d0f89fb9"
|
AWS_AMI = ENV['AWS_AMI'] || "ami-d0f89fb9"
|
||||||
|
FORWARD_DOCKER_PORTS = ENV['FORWARD_DOCKER_PORTS']
|
||||||
|
|
||||||
Vagrant::Config.run do |config|
|
Vagrant::Config.run do |config|
|
||||||
# Setup virtual machine box. This VM configuration code is always executed.
|
# Setup virtual machine box. This VM configuration code is always executed.
|
||||||
config.vm.box = BOX_NAME
|
config.vm.box = BOX_NAME
|
||||||
config.vm.box_url = BOX_URI
|
config.vm.box_url = BOX_URI
|
||||||
|
config.vm.forward_port 4243, 4243
|
||||||
|
|
||||||
# Provision docker and new kernel if deployment was not done
|
# Provision docker and new kernel if deployment was not done
|
||||||
if Dir.glob("#{File.dirname(__FILE__)}/.vagrant/machines/default/*/id").empty?
|
if Dir.glob("#{File.dirname(__FILE__)}/.vagrant/machines/default/*/id").empty?
|
||||||
|
@ -70,3 +72,17 @@ Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
|
||||||
config.vm.box_url = BOX_URI
|
config.vm.box_url = BOX_URI
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if !FORWARD_DOCKER_PORTS.nil?
|
||||||
|
Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
|
||||||
|
(49000..49900).each do |port|
|
||||||
|
config.vm.forward_port port, port
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
|
||||||
|
(49000..49900).each do |port|
|
||||||
|
config.vm.network :forwarded_port, :host => port, :guest => port
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
13
api.go
13
api.go
|
@ -438,17 +438,23 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
|
||||||
|
|
||||||
func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
config := &Config{}
|
config := &Config{}
|
||||||
|
out := &APIRun{}
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(config); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(config); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(config.Dns) == 0 && len(srv.runtime.Dns) == 0 && utils.CheckLocalDns() {
|
||||||
|
out.Warnings = append(out.Warnings, fmt.Sprintf("Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns))
|
||||||
|
config.Dns = defaultDns
|
||||||
|
}
|
||||||
|
|
||||||
id, err := srv.ContainerCreate(config)
|
id, err := srv.ContainerCreate(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
out.ID = id
|
||||||
|
|
||||||
out := &APIRun{
|
|
||||||
ID: id,
|
|
||||||
}
|
|
||||||
if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
|
if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
|
||||||
log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.")
|
log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.")
|
||||||
out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.")
|
out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.")
|
||||||
|
@ -457,6 +463,7 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r
|
||||||
log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.")
|
log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.")
|
||||||
out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.")
|
out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.")
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := json.Marshal(out)
|
b, err := json.Marshal(out)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -11,6 +11,8 @@ type APIImages struct {
|
||||||
Tag string `json:",omitempty"`
|
Tag string `json:",omitempty"`
|
||||||
ID string `json:"Id"`
|
ID string `json:"Id"`
|
||||||
Created int64
|
Created int64
|
||||||
|
Size int64
|
||||||
|
VirtualSize int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type APIInfo struct {
|
type APIInfo struct {
|
||||||
|
@ -35,6 +37,8 @@ type APIContainers struct {
|
||||||
Created int64
|
Created int64
|
||||||
Status string
|
Status string
|
||||||
Ports string
|
Ports string
|
||||||
|
SizeRw int64
|
||||||
|
SizeRootFs int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type APISearch struct {
|
type APISearch struct {
|
||||||
|
|
16
builder.go
16
builder.go
|
@ -2,11 +2,14 @@ package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/dotcloud/docker/utils"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var defaultDns = []string{"8.8.8.8", "8.8.4.4"}
|
||||||
|
|
||||||
type Builder struct {
|
type Builder struct {
|
||||||
runtime *Runtime
|
runtime *Runtime
|
||||||
repositories *TagStore
|
repositories *TagStore
|
||||||
|
@ -66,15 +69,26 @@ func (builder *Builder) Create(config *Config) (*Container, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(config.Dns) == 0 && len(builder.runtime.Dns) == 0 && utils.CheckLocalDns() {
|
||||||
|
//"WARNING: Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns
|
||||||
|
builder.runtime.Dns = defaultDns
|
||||||
|
}
|
||||||
|
|
||||||
// If custom dns exists, then create a resolv.conf for the container
|
// If custom dns exists, then create a resolv.conf for the container
|
||||||
|
if len(config.Dns) > 0 || len(builder.runtime.Dns) > 0 {
|
||||||
|
var dns []string
|
||||||
if len(config.Dns) > 0 {
|
if len(config.Dns) > 0 {
|
||||||
|
dns = config.Dns
|
||||||
|
} else {
|
||||||
|
dns = builder.runtime.Dns
|
||||||
|
}
|
||||||
container.ResolvConfPath = path.Join(container.root, "resolv.conf")
|
container.ResolvConfPath = path.Join(container.root, "resolv.conf")
|
||||||
f, err := os.Create(container.ResolvConfPath)
|
f, err := os.Create(container.ResolvConfPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
for _, dns := range config.Dns {
|
for _, dns := range dns {
|
||||||
if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil {
|
if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
30
commands.go
30
commands.go
|
@ -20,6 +20,7 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
@ -736,6 +737,15 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
nameParts := strings.SplitN(name, "/", 2)
|
||||||
|
validNamespace := regexp.MustCompile(`^([a-z0-9_]{4,30})$`)
|
||||||
|
if !validNamespace.MatchString(nameParts[0]) {
|
||||||
|
return fmt.Errorf("Invalid namespace name (%s), only [a-z0-9_] are allowed, size between 4 and 30", nameParts[0])
|
||||||
|
}
|
||||||
|
validRepo := regexp.MustCompile(`^([a-zA-Z0-9-_.]+)$`)
|
||||||
|
if !validRepo.MatchString(nameParts[1]) {
|
||||||
|
return fmt.Errorf("Invalid repository name (%s), only [a-zA-Z0-9-_.] are allowed", nameParts[1])
|
||||||
|
}
|
||||||
|
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
v.Set("registry", *registry)
|
v.Set("registry", *registry)
|
||||||
|
@ -820,7 +830,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
||||||
|
|
||||||
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
|
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
|
||||||
if !*quiet {
|
if !*quiet {
|
||||||
fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED")
|
fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED\tSIZE")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, out := range outs {
|
for _, out := range outs {
|
||||||
|
@ -838,7 +848,12 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(w, "%s\t", utils.TruncateID(out.ID))
|
fmt.Fprintf(w, "%s\t", utils.TruncateID(out.ID))
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "%s ago\n", utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
|
fmt.Fprintf(w, "%s ago\t", utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
|
||||||
|
if out.VirtualSize > 0 {
|
||||||
|
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.Size), utils.HumanSize(out.VirtualSize))
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(w, "%s\n", utils.HumanSize(out.Size))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if *noTrunc {
|
if *noTrunc {
|
||||||
fmt.Fprintln(w, out.ID)
|
fmt.Fprintln(w, out.ID)
|
||||||
|
@ -897,15 +912,20 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
||||||
}
|
}
|
||||||
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
|
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
|
||||||
if !*quiet {
|
if !*quiet {
|
||||||
fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS")
|
fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tSIZE")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, out := range outs {
|
for _, out := range outs {
|
||||||
if !*quiet {
|
if !*quiet {
|
||||||
if *noTrunc {
|
if *noTrunc {
|
||||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\n", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports)
|
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\n", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports)
|
fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports)
|
||||||
|
}
|
||||||
|
if out.SizeRootFs > 0 {
|
||||||
|
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.SizeRw), utils.HumanSize(out.SizeRootFs))
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(w, "%s\n", utils.HumanSize(out.SizeRw))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if *noTrunc {
|
if *noTrunc {
|
||||||
|
|
24
container.go
24
container.go
|
@ -13,6 +13,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -919,3 +920,26 @@ func validateID(id string) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSize, return real size, virtual size
|
||||||
|
func (container *Container) GetSize() (int64, int64) {
|
||||||
|
var sizeRw, sizeRootfs int64
|
||||||
|
|
||||||
|
filepath.Walk(container.rwPath(), func(path string, fileInfo os.FileInfo, err error) error {
|
||||||
|
if fileInfo != nil {
|
||||||
|
sizeRw += fileInfo.Size()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := os.Stat(container.RootfsPath())
|
||||||
|
if err == nil {
|
||||||
|
filepath.Walk(container.RootfsPath(), func(path string, fileInfo os.FileInfo, err error) error {
|
||||||
|
if fileInfo != nil {
|
||||||
|
sizeRootfs += fileInfo.Size()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return sizeRw, sizeRootfs
|
||||||
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ func main() {
|
||||||
pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
|
pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
|
||||||
flHost := flag.String("H", fmt.Sprintf("%s:%d", host, port), "Host:port to bind/connect to")
|
flHost := flag.String("H", fmt.Sprintf("%s:%d", host, port), "Host:port to bind/connect to")
|
||||||
flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
|
flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
|
||||||
|
flDns := flag.String("dns", "", "Set custom dns servers")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if *bridgeName != "" {
|
if *bridgeName != "" {
|
||||||
docker.NetworkBridgeIface = *bridgeName
|
docker.NetworkBridgeIface = *bridgeName
|
||||||
|
@ -66,7 +67,7 @@ func main() {
|
||||||
flag.Usage()
|
flag.Usage()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := daemon(*pidfile, host, port, *flAutoRestart, *flEnableCors); err != nil {
|
if err := daemon(*pidfile, host, port, *flAutoRestart, *flEnableCors, *flDns); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
}
|
}
|
||||||
|
@ -105,7 +106,7 @@ func removePidFile(pidfile string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func daemon(pidfile, addr string, port int, autoRestart, enableCors bool) error {
|
func daemon(pidfile, addr string, port int, autoRestart, enableCors bool, flDns string) error {
|
||||||
if addr != "127.0.0.1" {
|
if addr != "127.0.0.1" {
|
||||||
log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
|
log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
|
||||||
}
|
}
|
||||||
|
@ -122,8 +123,11 @@ func daemon(pidfile, addr string, port int, autoRestart, enableCors bool) error
|
||||||
removePidFile(pidfile)
|
removePidFile(pidfile)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}()
|
}()
|
||||||
|
var dns []string
|
||||||
server, err := docker.NewServer(autoRestart, enableCors)
|
if flDns != "" {
|
||||||
|
dns = []string{flDns}
|
||||||
|
}
|
||||||
|
server, err := docker.NewServer(autoRestart, enableCors, dns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,28 +47,40 @@ List containers
|
||||||
"Image": "base:latest",
|
"Image": "base:latest",
|
||||||
"Command": "echo 1",
|
"Command": "echo 1",
|
||||||
"Created": 1367854155,
|
"Created": 1367854155,
|
||||||
"Status": "Exit 0"
|
"Status": "Exit 0",
|
||||||
|
"Ports":"",
|
||||||
|
"SizeRw":12288,
|
||||||
|
"SizeRootFs":0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Id": "9cd87474be90",
|
"Id": "9cd87474be90",
|
||||||
"Image": "base:latest",
|
"Image": "base:latest",
|
||||||
"Command": "echo 222222",
|
"Command": "echo 222222",
|
||||||
"Created": 1367854155,
|
"Created": 1367854155,
|
||||||
"Status": "Exit 0"
|
"Status": "Exit 0",
|
||||||
|
"Ports":"",
|
||||||
|
"SizeRw":12288,
|
||||||
|
"SizeRootFs":0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Id": "3176a2479c92",
|
"Id": "3176a2479c92",
|
||||||
"Image": "base:latest",
|
"Image": "base:latest",
|
||||||
"Command": "echo 3333333333333333",
|
"Command": "echo 3333333333333333",
|
||||||
"Created": 1367854154,
|
"Created": 1367854154,
|
||||||
"Status": "Exit 0"
|
"Status": "Exit 0",
|
||||||
|
"Ports":"",
|
||||||
|
"SizeRw":12288,
|
||||||
|
"SizeRootFs":0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Id": "4cb07b47f9fb",
|
"Id": "4cb07b47f9fb",
|
||||||
"Image": "base:latest",
|
"Image": "base:latest",
|
||||||
"Command": "echo 444444444444444444444444444444444",
|
"Command": "echo 444444444444444444444444444444444",
|
||||||
"Created": 1367854152,
|
"Created": 1367854152,
|
||||||
"Status": "Exit 0"
|
"Status": "Exit 0",
|
||||||
|
"Ports":"",
|
||||||
|
"SizeRw":12288,
|
||||||
|
"SizeRootFs":0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -488,13 +500,17 @@ List Images
|
||||||
"Repository":"base",
|
"Repository":"base",
|
||||||
"Tag":"ubuntu-12.10",
|
"Tag":"ubuntu-12.10",
|
||||||
"Id":"b750fe79269d",
|
"Id":"b750fe79269d",
|
||||||
"Created":1364102658
|
"Created":1364102658,
|
||||||
|
"Size":24653,
|
||||||
|
"VirtualSize":180116135
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Repository":"base",
|
"Repository":"base",
|
||||||
"Tag":"ubuntu-quantal",
|
"Tag":"ubuntu-quantal",
|
||||||
"Id":"b750fe79269d",
|
"Id":"b750fe79269d",
|
||||||
"Created":1364102658
|
"Created":1364102658,
|
||||||
|
"Size":24653,
|
||||||
|
"VirtualSize":180116135
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -643,7 +659,8 @@ Inspect an image
|
||||||
"Image":"base",
|
"Image":"base",
|
||||||
"Volumes":null,
|
"Volumes":null,
|
||||||
"VolumesFrom":""
|
"VolumesFrom":""
|
||||||
}
|
},
|
||||||
|
"Size": 6824592
|
||||||
}
|
}
|
||||||
|
|
||||||
:statuscode 200: no error
|
:statuscode 200: no error
|
||||||
|
|
|
@ -8,6 +8,33 @@
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
Usage: docker import [OPTIONS] URL|- [REPOSITORY [TAG]]
|
Usage: docker import URL|- [REPOSITORY [TAG]]
|
||||||
|
|
||||||
Create a new filesystem image from the contents of a tarball
|
Create a new filesystem image from the contents of a tarball
|
||||||
|
|
||||||
|
At this time, the URL must start with ``http`` and point to a single file archive (.tar, .tar.gz, .bzip)
|
||||||
|
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 standard in.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
Import from a remote location
|
||||||
|
.............................
|
||||||
|
|
||||||
|
``$ docker import http://example.com/exampleimage.tgz exampleimagerepo``
|
||||||
|
|
||||||
|
Import from a local file
|
||||||
|
........................
|
||||||
|
|
||||||
|
Import to docker via pipe and standard in
|
||||||
|
|
||||||
|
``$ cat exampleimage.tgz | docker import - exampleimagelocal``
|
||||||
|
|
||||||
|
Import from a local directory
|
||||||
|
.............................
|
||||||
|
|
||||||
|
``$ 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 sudo) when you tar, then the ownerships might not get preserved.
|
||||||
|
|
|
@ -33,7 +33,7 @@ Installation
|
||||||
sudo apt-get install python-software-properties
|
sudo apt-get install python-software-properties
|
||||||
sudo add-apt-repository ppa:gophers/go
|
sudo add-apt-repository ppa:gophers/go
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get -y install lxc wget bsdtar curl golang-stable git
|
sudo apt-get -y install lxc wget bsdtar curl golang-stable git aufs-tools
|
||||||
|
|
||||||
export GOPATH=~/go/
|
export GOPATH=~/go/
|
||||||
export PATH=$GOPATH/bin:$PATH
|
export PATH=$GOPATH/bin:$PATH
|
||||||
|
|
9
graph.go
9
graph.go
|
@ -90,6 +90,15 @@ func (graph *Graph) Get(name string) (*Image, error) {
|
||||||
return nil, fmt.Errorf("Image stored at '%s' has wrong id '%s'", id, img.ID)
|
return nil, fmt.Errorf("Image stored at '%s' has wrong id '%s'", id, img.ID)
|
||||||
}
|
}
|
||||||
img.graph = graph
|
img.graph = graph
|
||||||
|
if img.Size == 0 {
|
||||||
|
root, err := img.root()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := StoreSize(img, root); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
graph.lockSumMap.Lock()
|
graph.lockSumMap.Lock()
|
||||||
defer graph.lockSumMap.Unlock()
|
defer graph.lockSumMap.Unlock()
|
||||||
if _, exists := graph.checksumLock[img.ID]; !exists {
|
if _, exists := graph.checksumLock[img.ID]; !exists {
|
||||||
|
|
2
hack/Vagrantfile
vendored
2
hack/Vagrantfile
vendored
|
@ -22,7 +22,7 @@ Vagrant::Config.run do |config|
|
||||||
pkg_cmd = "touch #{DOCKER_PATH}; "
|
pkg_cmd = "touch #{DOCKER_PATH}; "
|
||||||
# Install docker dependencies
|
# Install docker dependencies
|
||||||
pkg_cmd << "export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; " \
|
pkg_cmd << "export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; " \
|
||||||
"apt-get install -q -y lxc bsdtar git golang make linux-image-extra-3.8.0-19-generic; " \
|
"apt-get install -q -y lxc bsdtar git aufs-tools golang make linux-image-extra-3.8.0-19-generic; " \
|
||||||
"chown -R #{USER}.#{USER} #{GOPATH}; " \
|
"chown -R #{USER}.#{USER} #{GOPATH}; " \
|
||||||
"install -m 0664 #{CFG_PATH}/bash_profile /home/#{USER}/.bash_profile"
|
"install -m 0664 #{CFG_PATH}/bash_profile /home/#{USER}/.bash_profile"
|
||||||
config.vm.provision :shell, :inline => pkg_cmd
|
config.vm.provision :shell, :inline => pkg_cmd
|
||||||
|
|
23
image.go
23
image.go
|
@ -13,6 +13,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -29,6 +30,7 @@ type Image struct {
|
||||||
Config *Config `json:"config,omitempty"`
|
Config *Config `json:"config,omitempty"`
|
||||||
Architecture string `json:"architecture,omitempty"`
|
Architecture string `json:"architecture,omitempty"`
|
||||||
graph *Graph
|
graph *Graph
|
||||||
|
Size int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadImage(root string) (*Image, error) {
|
func LoadImage(root string) (*Image, error) {
|
||||||
|
@ -94,6 +96,18 @@ func StoreImage(img *Image, layerData Archive, root string, store bool) error {
|
||||||
if err := Untar(layerData, layer); err != nil {
|
if err := Untar(layerData, layer); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return StoreSize(img, root)
|
||||||
|
}
|
||||||
|
|
||||||
|
func StoreSize(img *Image, root string) error {
|
||||||
|
layer := layerPath(root)
|
||||||
|
|
||||||
|
filepath.Walk(layer, func(path string, fileInfo os.FileInfo, err error) error {
|
||||||
|
img.Size += fileInfo.Size()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
// Store the json ball
|
// Store the json ball
|
||||||
jsonData, err := json.Marshal(img)
|
jsonData, err := json.Marshal(img)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -363,6 +377,15 @@ func (img *Image) Checksum() (string, error) {
|
||||||
return hash, nil
|
return hash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (img *Image) getParentsSize(size int64) int64 {
|
||||||
|
parentImage, err := img.GetParent()
|
||||||
|
if err != nil || parentImage == nil {
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
size += parentImage.Size
|
||||||
|
return parentImage.getParentsSize(size)
|
||||||
|
}
|
||||||
|
|
||||||
// Build an Image object from raw json data
|
// Build an Image object from raw json data
|
||||||
func NewImgJSON(src []byte) (*Image, error) {
|
func NewImgJSON(src []byte) (*Image, error) {
|
||||||
ret := &Image{}
|
ret := &Image{}
|
||||||
|
|
5
mount.go
5
mount.go
|
@ -2,13 +2,18 @@ package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/dotcloud/docker/utils"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Unmount(target string) error {
|
func Unmount(target string) error {
|
||||||
|
if err := exec.Command("auplink", target, "flush").Run(); err != nil {
|
||||||
|
utils.Debugf("[warning]: couldn't run auplink before unmount: %s", err)
|
||||||
|
}
|
||||||
if err := syscall.Unmount(target, 0); err != nil {
|
if err := syscall.Unmount(target, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ Homepage: http://github.com/dotcloud/docker
|
||||||
|
|
||||||
Package: lxc-docker
|
Package: lxc-docker
|
||||||
Architecture: linux-any
|
Architecture: linux-any
|
||||||
Depends: ${shlibs:Depends}, ${misc:Depends}, lxc, bsdtar
|
Depends: ${shlibs:Depends}, ${misc:Depends}, lxc, bsdtar, aufs-tools
|
||||||
Description: Linux container runtime
|
Description: Linux container runtime
|
||||||
Docker complements LXC with a high-level API which operates at the process
|
Docker complements LXC with a high-level API which operates at the process
|
||||||
level. It runs unix processes with strong guarantees of isolation and
|
level. It runs unix processes with strong guarantees of isolation and
|
||||||
|
|
|
@ -8,7 +8,7 @@ Homepage: http://github.com/dotcloud/docker
|
||||||
|
|
||||||
Package: lxc-docker
|
Package: lxc-docker
|
||||||
Architecture: linux-any
|
Architecture: linux-any
|
||||||
Depends: ${misc:Depends},${shlibs:Depends},lxc,bsdtar
|
Depends: ${misc:Depends},${shlibs:Depends},lxc,bsdtar,aufs-tools
|
||||||
Conflicts: docker
|
Conflicts: docker
|
||||||
Description: lxc-docker is a Linux container runtime
|
Description: lxc-docker is a Linux container runtime
|
||||||
Docker complements LXC with a high-level API which operates at the process
|
Docker complements LXC with a high-level API which operates at the process
|
||||||
|
|
|
@ -328,7 +328,7 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validate bool) (*RepositoryData, error) {
|
func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) {
|
||||||
imgListJSON, err := json.Marshal(imgList)
|
imgListJSON, err := json.Marshal(imgList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -347,6 +347,9 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat
|
||||||
req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
|
req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
|
||||||
req.ContentLength = int64(len(imgListJSON))
|
req.ContentLength = int64(len(imgListJSON))
|
||||||
req.Header.Set("X-Docker-Token", "true")
|
req.Header.Set("X-Docker-Token", "true")
|
||||||
|
if validate {
|
||||||
|
req.Header["X-Docker-Endpoints"] = regs
|
||||||
|
}
|
||||||
|
|
||||||
res, err := r.client.Do(req)
|
res, err := r.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -364,7 +367,9 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat
|
||||||
req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
|
req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
|
||||||
req.ContentLength = int64(len(imgListJSON))
|
req.ContentLength = int64(len(imgListJSON))
|
||||||
req.Header.Set("X-Docker-Token", "true")
|
req.Header.Set("X-Docker-Token", "true")
|
||||||
|
if validate {
|
||||||
|
req.Header["X-Docker-Endpoints"] = regs
|
||||||
|
}
|
||||||
res, err = r.client.Do(req)
|
res, err = r.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -32,6 +32,7 @@ type Runtime struct {
|
||||||
autoRestart bool
|
autoRestart bool
|
||||||
volumes *Graph
|
volumes *Graph
|
||||||
srv *Server
|
srv *Server
|
||||||
|
Dns []string
|
||||||
}
|
}
|
||||||
|
|
||||||
var sysInitPath string
|
var sysInitPath string
|
||||||
|
@ -245,11 +246,12 @@ func (runtime *Runtime) UpdateCapabilities(quiet bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: harmonize with NewGraph()
|
// FIXME: harmonize with NewGraph()
|
||||||
func NewRuntime(autoRestart bool) (*Runtime, error) {
|
func NewRuntime(autoRestart bool, dns []string) (*Runtime, error) {
|
||||||
runtime, err := NewRuntimeFromDirectory("/var/lib/docker", autoRestart)
|
runtime, err := NewRuntimeFromDirectory("/var/lib/docker", autoRestart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
runtime.Dns = dns
|
||||||
|
|
||||||
if k, err := utils.GetKernelVersion(); err != nil {
|
if k, err := utils.GetKernelVersion(); err != nil {
|
||||||
log.Printf("WARNING: %s\n", err)
|
log.Printf("WARNING: %s\n", err)
|
||||||
|
|
18
server.go
18
server.go
|
@ -174,6 +174,8 @@ func (srv *Server) Images(all bool, filter string) ([]APIImages, error) {
|
||||||
out.Tag = tag
|
out.Tag = tag
|
||||||
out.ID = image.ID
|
out.ID = image.ID
|
||||||
out.Created = image.Created.Unix()
|
out.Created = image.Created.Unix()
|
||||||
|
out.Size = image.Size
|
||||||
|
out.VirtualSize = image.getParentsSize(0) + image.Size
|
||||||
outs = append(outs, out)
|
outs = append(outs, out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,6 +185,8 @@ func (srv *Server) Images(all bool, filter string) ([]APIImages, error) {
|
||||||
var out APIImages
|
var out APIImages
|
||||||
out.ID = image.ID
|
out.ID = image.ID
|
||||||
out.Created = image.Created.Unix()
|
out.Created = image.Created.Unix()
|
||||||
|
out.Size = image.Size
|
||||||
|
out.VirtualSize = image.getParentsSize(0) + image.Size
|
||||||
outs = append(outs, out)
|
outs = append(outs, out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -268,6 +272,8 @@ func (srv *Server) Containers(all bool, n int, since, before string) []APIContai
|
||||||
c.Created = container.Created.Unix()
|
c.Created = container.Created.Unix()
|
||||||
c.Status = container.State.String()
|
c.Status = container.State.String()
|
||||||
c.Ports = container.NetworkSettings.PortMappingHuman()
|
c.Ports = container.NetworkSettings.PortMappingHuman()
|
||||||
|
c.SizeRw, c.SizeRootFs = container.GetSize()
|
||||||
|
|
||||||
retContainers = append(retContainers, c)
|
retContainers = append(retContainers, c)
|
||||||
}
|
}
|
||||||
return retContainers
|
return retContainers
|
||||||
|
@ -497,7 +503,7 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name stri
|
||||||
srvName = fmt.Sprintf("src/%s", url.QueryEscape(strings.Join(parts, "/")))
|
srvName = fmt.Sprintf("src/%s", url.QueryEscape(strings.Join(parts, "/")))
|
||||||
}
|
}
|
||||||
|
|
||||||
repoData, err := r.PushImageJSONIndex(srvName, imgList, false)
|
repoData, err := r.PushImageJSONIndex(srvName, imgList, false, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -521,7 +527,7 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name stri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := r.PushImageJSONIndex(srvName, imgList, true); err != nil {
|
if _, err := r.PushImageJSONIndex(srvName, imgList, true, repoData.Endpoints); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -652,6 +658,10 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
|
||||||
|
|
||||||
func (srv *Server) ContainerCreate(config *Config) (string, error) {
|
func (srv *Server) ContainerCreate(config *Config) (string, error) {
|
||||||
|
|
||||||
|
if config.Memory != 0 && config.Memory < 524288 {
|
||||||
|
return "", fmt.Errorf("Memory limit must be given in bytes (minimum 524288 bytes)")
|
||||||
|
}
|
||||||
|
|
||||||
if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
|
if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
|
||||||
config.Memory = 0
|
config.Memory = 0
|
||||||
}
|
}
|
||||||
|
@ -972,11 +982,11 @@ func (srv *Server) ImageInspect(name string) (*Image, error) {
|
||||||
return nil, fmt.Errorf("No such image: %s", name)
|
return nil, fmt.Errorf("No such image: %s", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(autoRestart, enableCors bool) (*Server, error) {
|
func NewServer(autoRestart, enableCors bool, dns ListOpts) (*Server, error) {
|
||||||
if runtime.GOARCH != "amd64" {
|
if runtime.GOARCH != "amd64" {
|
||||||
log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH)
|
log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH)
|
||||||
}
|
}
|
||||||
runtime, err := NewRuntime(autoRestart)
|
runtime, err := NewRuntime(autoRestart, dns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,3 +147,25 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunWithTooLowMemoryLimit(t *testing.T) {
|
||||||
|
runtime, err := newTestRuntime()
|
||||||
|
srv := &Server{runtime: runtime}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer nuke(runtime)
|
||||||
|
// Try to create a container with a memory limit of 1 byte less than the minimum allowed limit.
|
||||||
|
_, err = srv.ContainerCreate(
|
||||||
|
&Config{
|
||||||
|
Image: GetTestImage(runtime).ID,
|
||||||
|
Memory: 524287,
|
||||||
|
CpuShares: 1000,
|
||||||
|
Cmd: []string{"/bin/cat"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
2
testing/Vagrantfile
vendored
2
testing/Vagrantfile
vendored
|
@ -30,7 +30,7 @@ Vagrant::Config.run do |config|
|
||||||
# Install docker dependencies
|
# Install docker dependencies
|
||||||
pkg_cmd << "apt-get install -q -y python-software-properties; " \
|
pkg_cmd << "apt-get install -q -y python-software-properties; " \
|
||||||
"add-apt-repository -y ppa:gophers/go/ubuntu; apt-get update -qq; " \
|
"add-apt-repository -y ppa:gophers/go/ubuntu; apt-get update -qq; " \
|
||||||
"DEBIAN_FRONTEND=noninteractive apt-get install -q -y lxc bsdtar git golang-stable make; "
|
"DEBIAN_FRONTEND=noninteractive apt-get install -q -y lxc bsdtar git golang-stable aufs-tools make; "
|
||||||
# Activate new kernel
|
# Activate new kernel
|
||||||
pkg_cmd << "shutdown -r +1; "
|
pkg_cmd << "shutdown -r +1; "
|
||||||
config.vm.provision :shell, :inline => pkg_cmd
|
config.vm.provision :shell, :inline => pkg_cmd
|
||||||
|
|
|
@ -135,6 +135,20 @@ func HumanDuration(d time.Duration) string {
|
||||||
return fmt.Sprintf("%d years", d.Hours()/24/365)
|
return fmt.Sprintf("%d 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])
|
||||||
|
}
|
||||||
|
|
||||||
func Trunc(s string, maxlen int) string {
|
func Trunc(s string, maxlen int) string {
|
||||||
if len(s) <= maxlen {
|
if len(s) <= maxlen {
|
||||||
return s
|
return s
|
||||||
|
@ -628,3 +642,20 @@ func IsURL(str string) bool {
|
||||||
func IsGIT(str string) bool {
|
func IsGIT(str string) bool {
|
||||||
return strings.HasPrefix(str, "git://") || strings.HasPrefix(str, "github.com/")
|
return strings.HasPrefix(str, "git://") || strings.HasPrefix(str, "github.com/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CheckLocalDns() bool {
|
||||||
|
resolv, err := ioutil.ReadFile("/etc/resolv.conf")
|
||||||
|
if err != nil {
|
||||||
|
Debugf("Error openning resolv.conf: %s", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, ip := range []string{
|
||||||
|
"127.0.0.1",
|
||||||
|
"127.0.1.1",
|
||||||
|
} {
|
||||||
|
if strings.Contains(string(resolv), ip) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -261,3 +261,16 @@ func TestCompareKernelVersion(t *testing.T) {
|
||||||
&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "0"},
|
&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "0"},
|
||||||
-1)
|
-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHumanSize(t *testing.T) {
|
||||||
|
|
||||||
|
size1000 := HumanSize(1000)
|
||||||
|
if size1000 != "1 kB" {
|
||||||
|
t.Errorf("1000 -> expected 1 kB, got %s", size1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
size1024 := HumanSize(1024)
|
||||||
|
if size1024 != "1.024 kB" {
|
||||||
|
t.Errorf("1024 -> expected 1.024 kB, got %s", size1024)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue