Compare commits

...

54 Commits

Author SHA1 Message Date
Guillaume J. Charmes
4f202cd07f Bumped version to 0.3.0 2013-05-05 07:46:25 -07:00
Guillaume J. Charmes
da01dd3d56 Merge remote-tracking branch 'origin/registry-update'
+ Registry: Add the new registry support
2013-05-05 07:13:25 -07:00
shin-
09f1cbabb9 Fixed imports 2013-05-06 11:06:44 -07:00
Sam Alba
c9994ed0fb Moved the Debugf message in a registry to a more useful place 2013-05-06 10:57:48 -07:00
Guillaume J. Charmes
b0e076f374 Add output to checksums, code cleaning 2013-05-06 10:57:46 -07:00
Sam Alba
00266df8ac Fixed public pull + Added some verbosity about what is happening 2013-05-06 10:57:45 -07:00
Sam Alba
3febeb93f5 Added help message to invite to login when getting a 401 2013-05-06 10:57:43 -07:00
Sam Alba
bcdf03037b Fixed pulling repositories from library 2013-05-06 10:57:41 -07:00
Sam Alba
a372f982c1 Switching to prod index server 2013-05-06 10:57:38 -07:00
shin-
d985050aeb gofmt pass 2013-05-06 10:57:36 -07:00
shin-
0c5e76958b Use progress reader when uploading/downloading layers 2013-05-06 10:57:06 -07:00
Sam Alba
0f68042053 Handled wrong user credentials by re-init the auth file (it was impossible to login after having wrong crendentials) 2013-05-06 10:57:03 -07:00
shin-
18796d55a6 Fixed some login quirks 2013-05-06 10:57:00 -07:00
shin-
594827d416 Fixed typo in 'username or email already exists' 2013-05-06 10:56:59 -07:00
shin-
5690562fc8 Fix error in PushImage 2013-05-06 10:56:57 -07:00
shin-
be791a223b simplify image.Checksum 2013-05-06 10:56:55 -07:00
shin-
19045b530e simplify graph.Checksums 2013-05-06 10:56:54 -07:00
shin-
b5873806d0 Only send checksums for images not uploaded yet 2013-05-06 10:56:52 -07:00
shin-
f10b0f75e0 Fix checksum computing 2013-05-06 10:56:50 -07:00
shin-
6e2ddf6f60 Checksum system overhaul 2013-05-06 10:56:49 -07:00
shin-
e81a53eea9 Added support for REPO:TAG format in docker pull (overrides -t option) 2013-05-06 10:56:47 -07:00
shin-
1c76f91fc4 Fixed minor bugs in docker pull 2013-05-06 10:56:45 -07:00
shin-
be75608906 Fixed checksum computing. Ensure checksum is computed when image metadata is loaded from disk. Fixed docker push workflow. Moved hash computing to utils 2013-05-06 10:56:10 -07:00
shin-
3c85e9390e Added X-Docker-Token header to initial index requests 2013-05-06 10:54:57 -07:00
shin-
ea3374bcb0 Prepend hash method to the image checksum 2013-05-06 10:54:55 -07:00
shin-
6e936c8fd3 Follow redirections when sending PUT request in PushRepository 2013-05-06 10:54:53 -07:00
shin-
4cd9e4722c Fixed graph.Checksums() 2013-05-06 10:54:52 -07:00
shin-
630d358384 Fixed checksum representation 2013-05-06 10:54:50 -07:00
shin-
84be35dce1 Fixed docker login 2013-05-06 10:54:48 -07:00
shin-
23953e7d67 Style changes in auth.Login 2013-05-06 10:54:46 -07:00
shin-
6644a3c78a Reactivated CmdPush in commands.go 2013-05-06 10:54:45 -07:00
shin-
e179c66400 Reimplemented docker pull for new registry API (command is still deactivated) 2013-05-06 10:54:41 -07:00
shin-
048fd671ef Implemented checksum computation on image creation (necessary for new push primitive) 2013-05-06 10:52:42 -07:00
shin-
7c1a27e2ad gofmt pass 2013-05-06 10:52:10 -07:00
shin-
1cf8a2c26c Changed some of the routes to reflect changes made to the API ; added HTTPClient singleton to the graph object 2013-05-06 10:52:08 -07:00
shin-
e639309a7a Reimplemented feature: downloading all tags on a repository using docker pull. Temporarily commented out CmdPush 2013-05-06 10:51:10 -07:00
shin-
2f082510a7 Implemented new version of PullRepository. Missing support for whole repository pull (= no tag specified) 2013-05-06 10:51:07 -07:00
shin-
2421838b0a Support for the new registry/index API (wip) 2013-05-06 10:51:04 -07:00
Ken Cochrane
58ca46af39 Merge pull request #500 from kencochrane/registry-api-doc
added Docker Index search API doc
2013-05-06 10:44:50 -07:00
Ken Cochrane
09d4b9452d added new sphinx contrib extention for better REST API docs, and changed the index search API so that it uses the new docs, as a test to make sure it works correctly 2013-05-06 13:38:51 -04:00
Ken Cochrane
d78b2d4ade Merge github.com:dotcloud/docker into registry-api-doc 2013-05-06 13:13:17 -04:00
Solomon Hykes
e392b7ee9b Merge pull request #515 from drnic/vagrant_overrides
Allow reuse of existing vagrant boxes by env variables
2013-05-04 16:47:17 -07:00
Guillaume J. Charmes
c3a5dd76cf Merge pull request #520 from dotcloud/519-no_command_specified-fix
- runtime: Fix the command existance check
2013-05-04 12:03:08 -07:00
Dr Nic Williams
04f41ebdbc Allow reuse of existing vagrant boxes by env variables
Usage:

    BOX_NAME=precise64 vagrant up
2013-05-03 15:35:51 -07:00
Guillaume J. Charmes
c34989f1c4 Merge pull request #501 from losinggeneration/fix_cgroup_memory_check
- runtime: strings.Split may return an empty string on no match
2013-05-03 14:29:43 -07:00
Harley Laue
589d7c68db Check that the line is valid with 6 parts after split 2013-05-03 13:49:10 -05:00
Guillaume J. Charmes
b9ec03c21b Fix the command existance check 2013-05-02 20:50:28 -07:00
Guillaume J. Charmes
64d7bc442d Fix server crash when running an image without command without autorun 2013-05-02 13:56:45 -07:00
Harley Laue
4bc8ef42d4 strings.Split may return an empty string on no match
* This fixes an index out of range crash if cgroup memory is not
  enabled.
2013-05-02 14:44:41 -05:00
Guillaume J. Charmes
8378498951 Fix issue within mergeConfig preventing hostname and user to be set 2013-05-02 12:32:10 -07:00
Guillaume J. Charmes
1617a18258 Fix typo for command run docs 2013-05-02 12:11:57 -07:00
Solomon Hykes
6c1bb39c09 Fix date typos in changelog 2013-05-03 15:40:32 -07:00
Solomon Hykes
f6b5cd77eb Merge branch 'master' of ssh://github.com/dotcloud/docker 2013-05-03 15:37:37 -07:00
Ken Cochrane
b682a8ea9e added Docker Index search API doc 2013-05-02 14:32:38 -04:00
19 changed files with 678 additions and 276 deletions

View File

@@ -1,14 +1,23 @@
# Changelog
## 0.2.2 (2012-05-03)
## 0.3.0 (2013-05-06)
+ Registry: Implement the new registry
+ Documentation: new example: sharing data between 2 couchdb databases
- Runtime: Fix the command existance check
- Runtime: strings.Split may return an empty string on no match
- Runtime: Fix an index out of range crash if cgroup memory is not
* Documentation: Various improvments
* Vagrant: Use only one deb line in /etc/apt
## 0.2.2 (2013-05-03)
+ Support for data volumes ('docker run -v=PATH')
+ Share data volumes between containers ('docker run -volumes-from')
+ Improved documentation
* Upgrade to Go 1.0.3
* Various upgrades to the dev environment for contributors
## 0.2.1 (2012-05-01)
+ 'docker commit -run' bundles a layer with default runtime options: command, ports etc.
## 0.2.1 (2013-05-01)
+ 'docker commit -run' bundles a layer with default runtime options: command, ports etc.
* Improve install process on Vagrant
+ New Dockerfile operation: "maintainer"
+ New Dockerfile operation: "expose"
@@ -17,7 +26,7 @@
+ 'docker -d -r': restart crashed containers at daemon startup
* Runtime: improve test coverage
## 0.2.0 (2012-04-23)
## 0.2.0 (2013-04-23)
- Runtime: ghost containers can be killed and waited for
* Documentation: update install intructions
- Packaging: fix Vagrantfile
@@ -25,13 +34,12 @@
+ Add a changelog
- Various bugfixes
## 0.1.8 (2013-04-22)
- Dynamically detect cgroup capabilities
- Issue stability warning on kernels <3.8
- 'docker push' buffers on disk instead of memory
- Fix 'docker diff' for removed files
- Fix 'docker stop' for ghost containers
- Fix 'docker stop' for ghost containers
- Fix handling of pidfile
- Various bugfixes and stability improvements
@@ -52,7 +60,7 @@
- Improve diagnosis of missing system capabilities
- Allow disabling memory limits at compile time
- Add debian packaging
- Documentation: installing on Arch Linux
- Documentation: installing on Arch Linux
- Documentation: running Redis on docker
- Fixed lxc 0.9 compatibility
- Automatically load aufs module

4
Vagrantfile vendored
View File

@@ -1,8 +1,8 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
BOX_NAME = "ubuntu"
BOX_URI = "http://files.vagrantup.com/precise64.box"
BOX_NAME = ENV['BOX_NAME'] || "ubuntu"
BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64.box"
PPA_KEY = "E61D797F63561DC6"
Vagrant::Config.run do |config|

View File

@@ -3,7 +3,6 @@ package auth
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
@@ -16,7 +15,7 @@ import (
const CONFIGFILE = ".dockercfg"
// the registry server we want to login against
const REGISTRY_SERVER = "https://registry.docker.io"
const INDEX_SERVER = "https://index.docker.io"
type AuthConfig struct {
Username string `json:"username"`
@@ -76,6 +75,9 @@ func LoadConfig(rootPath string) (*AuthConfig, error) {
return nil, err
}
arr := strings.Split(string(b), "\n")
if len(arr) < 2 {
return nil, fmt.Errorf("The Auth config file is empty")
}
origAuth := strings.Split(arr[0], " = ")
origEmail := strings.Split(arr[1], " = ")
authConfig, err := DecodeAuth(origAuth[1])
@@ -89,9 +91,14 @@ func LoadConfig(rootPath string) (*AuthConfig, error) {
// save the auth config
func saveConfig(rootPath, authStr string, email string) error {
confFile := path.Join(rootPath, CONFIGFILE)
if len(email) == 0 {
os.Remove(confFile)
return nil
}
lines := "auth = " + authStr + "\n" + "email = " + email + "\n"
b := []byte(lines)
err := ioutil.WriteFile(path.Join(rootPath, CONFIGFILE), b, 0600)
err := ioutil.WriteFile(confFile, b, 0600)
if err != nil {
return err
}
@@ -101,40 +108,38 @@ func saveConfig(rootPath, authStr string, email string) error {
// try to register/login to the registry server
func Login(authConfig *AuthConfig) (string, error) {
storeConfig := false
client := &http.Client{}
reqStatusCode := 0
var status string
var errMsg string
var reqBody []byte
jsonBody, err := json.Marshal(authConfig)
if err != nil {
errMsg = fmt.Sprintf("Config Error: %s", err)
return "", errors.New(errMsg)
return "", fmt.Errorf("Config Error: %s", err)
}
// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
b := strings.NewReader(string(jsonBody))
req1, err := http.Post(REGISTRY_SERVER+"/v1/users", "application/json; charset=utf-8", b)
req1, err := http.Post(INDEX_SERVER+"/v1/users/", "application/json; charset=utf-8", b)
if err != nil {
errMsg = fmt.Sprintf("Server Error: %s", err)
return "", errors.New(errMsg)
return "", fmt.Errorf("Server Error: %s", err)
}
reqStatusCode = req1.StatusCode
defer req1.Body.Close()
reqBody, err = ioutil.ReadAll(req1.Body)
if err != nil {
errMsg = fmt.Sprintf("Server Error: [%#v] %s", reqStatusCode, err)
return "", errors.New(errMsg)
return "", fmt.Errorf("Server Error: [%#v] %s", reqStatusCode, err)
}
if reqStatusCode == 201 {
status = "Account Created\n"
status = "Account created. Please use the confirmation link we sent" +
" to your e-mail to activate it.\n"
storeConfig = true
} else if reqStatusCode == 403 {
return "", fmt.Errorf("Login: Your account hasn't been activated. " +
"Please check your e-mail for a confirmation link.")
} else if reqStatusCode == 400 {
// FIXME: This should be 'exists', not 'exist'. Need to change on the server first.
if string(reqBody) == "Username or email already exist" {
client := &http.Client{}
req, err := http.NewRequest("GET", REGISTRY_SERVER+"/v1/users", nil)
if string(reqBody) == "\"Username or email already exists\"" {
req, err := http.NewRequest("GET", INDEX_SERVER+"/v1/users/", nil)
req.SetBasicAuth(authConfig.Username, authConfig.Password)
resp, err := client.Do(req)
if err != nil {
@@ -148,17 +153,18 @@ func Login(authConfig *AuthConfig) (string, error) {
if resp.StatusCode == 200 {
status = "Login Succeeded\n"
storeConfig = true
} else if resp.StatusCode == 401 {
saveConfig(authConfig.rootPath, "", "")
return "", fmt.Errorf("Wrong login/password, please try again")
} else {
status = fmt.Sprintf("Login: %s", body)
return "", errors.New(status)
return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
resp.StatusCode, resp.Header)
}
} else {
status = fmt.Sprintf("Registration: %s", reqBody)
return "", errors.New(status)
return "", fmt.Errorf("Registration: %s", reqBody)
}
} else {
status = fmt.Sprintf("[%s] : %s", reqStatusCode, reqBody)
return "", errors.New(status)
return "", fmt.Errorf("Unexpected status code [%d] : %s", reqStatusCode, reqBody)
}
if storeConfig {
authStr := EncodeAuth(authConfig)

View File

@@ -19,7 +19,7 @@ import (
"unicode"
)
const VERSION = "0.2.2"
const VERSION = "0.3.0"
var (
GIT_COMMIT string
@@ -521,6 +521,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args .
func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
cmd := rcli.Subcmd(stdout, "push", "NAME", "Push an image or a repository to the registry")
registry := cmd.String("registry", "", "Registry host to push the image to")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -531,8 +532,8 @@ func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...
return nil
}
// If the login failed, abort
if srv.runtime.authConfig == nil || srv.runtime.authConfig.Username == "" {
// If the login failed AND we're using the index, abort
if *registry == "" && (srv.runtime.authConfig == nil || srv.runtime.authConfig.Username == "") {
if err := srv.CmdLogin(stdin, stdout, args...); err != nil {
return err
}
@@ -555,9 +556,6 @@ func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...
Debugf("Pushing [%s] to [%s]\n", local, remote)
// Try to get the image
// FIXME: Handle lookup
// FIXME: Also push the tags in case of ./docker push myrepo:mytag
// img, err := srv.runtime.LookupImage(cmd.Arg(0))
img, err := srv.runtime.graph.Get(local)
if err != nil {
Debugf("The push refers to a repository [%s] (len: %d)\n", local, len(srv.runtime.repositories.Repositories[local]))
@@ -571,7 +569,7 @@ func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...
return err
}
err = srv.runtime.graph.PushImage(stdout, img, srv.runtime.authConfig)
err = srv.runtime.graph.PushImage(stdout, img, *registry, nil)
if err != nil {
return err
}
@@ -580,6 +578,8 @@ func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...
func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
cmd := rcli.Subcmd(stdout, "pull", "NAME", "Pull an image or a repository from the registry")
tag := cmd.String("t", "", "Download tagged image in repository")
registry := cmd.String("registry", "", "Registry to download from. Necessary if image is pulled by ID")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -589,15 +589,20 @@ func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string
return nil
}
if strings.Contains(remote, ":") {
remoteParts := strings.Split(remote, ":")
tag = &remoteParts[1]
remote = remoteParts[0]
}
// FIXME: CmdPull should be a wrapper around Runtime.Pull()
if srv.runtime.graph.LookupRemoteImage(remote, srv.runtime.authConfig) {
if err := srv.runtime.graph.PullImage(stdout, remote, srv.runtime.authConfig); err != nil {
if *registry != "" {
if err := srv.runtime.graph.PullImage(stdout, remote, *registry, nil); err != nil {
return err
}
return nil
}
// FIXME: Allow pull repo:tag
if err := srv.runtime.graph.PullRepository(stdout, remote, "", srv.runtime.repositories, srv.runtime.authConfig); err != nil {
if err := srv.runtime.graph.PullRepository(stdout, remote, *tag, srv.runtime.repositories, srv.runtime.authConfig); err != nil {
return err
}
return nil

View File

@@ -736,6 +736,14 @@ func (container *Container) ExportRw() (Archive, error) {
return Tar(container.rwPath(), Uncompressed)
}
func (container *Container) RwChecksum() (string, error) {
rwData, err := Tar(container.rwPath(), Xz)
if err != nil {
return "", err
}
return HashData(rwData)
}
func (container *Container) Export() (Archive, error) {
if err := container.EnsureMounted(); err != nil {
return nil, err

2
docs/requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
Sphinx==1.1.3
sphinxcontrib-httpdomain==1.1.8

View File

@@ -18,4 +18,5 @@
-t=false: Allocate a pseudo-tty
-u="": Username or UID
-d=[]: Set custom dns servers for the container
-v=[]: Creates a new volumes and mount it at the specified path. A container ID can be passed instead of a path in order to mount all volumes from the given container.
-v=[]: Creates a new volumes and mount it at the specified path.
-volumes-from="": Mount all volumes from the given container.

View File

@@ -9,7 +9,7 @@ Commands
Contents:
.. toctree::
:maxdepth: 2
:maxdepth: 3
basics
workingwithrepository

View File

@@ -25,7 +25,7 @@ import sys, os
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = []
extensions = ['sphinxcontrib.httpdomain']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

View File

@@ -16,6 +16,7 @@ This documentation has the following resources:
contributing/index
commandline/index
registry/index
index/index
faq

View File

@@ -0,0 +1,15 @@
:title: Docker Index documentation
:description: Documentation for docker Index
:keywords: docker, index, api
Index
=====
Contents:
.. toctree::
:maxdepth: 2
search

View File

@@ -0,0 +1,38 @@
=======================
Docker Index Search API
=======================
Search
------
.. http:get:: /v1/search
Search the Index given a search term. It accepts :http:method:`get` only.
**Example request**:
.. sourcecode:: http
GET /v1/search?q=search_term HTTP/1.1
Host: example.com
Accept: application/json
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{"query":"search_term",
"num_results": 2,
"results" : [
{"name": "dotcloud/base", "description": "A base ubuntu64 image..."},
{"name": "base2", "description": "A base ubuntu64 image..."},
]
}
:query q: what you want to search for
:statuscode 200: no error
:statuscode 500: server error

View File

@@ -84,7 +84,9 @@ Its possible to run docker pull https://<registry>/repositories/samalba/busyb
Currently registry redirects to s3 urls for downloads, going forward all downloads need to be streamed through the registry. The Registry will then abstract the calls to S3 by a top-level class which implements sub-classes for S3 and local storage.
Token is only returned when it is a private repo, public repos do not require tokens to be returned. The Registry will still contact the Index to make sure the pull is authorized (“is it ok to download this repos without a Token?”).
Token is only returned when the 'X-Docker-Token' header is sent with request.
Basic Auth is required to pull private repos. Basic auth isn't required for pulling public repos, but if one is provided, it needs to be valid and for an active account.
API (pulling repository foo/bar):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -426,6 +428,8 @@ You have 3 options:
- X-Docker-Token: true
In this case, along with the 200 response, youll get a new token (if user auth is ok):
If authorization isn't correct you get a 401 response.
If account isn't active you will get a 403 response.
**Response**:
- 200 OK

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"path/filepath"
@@ -13,8 +14,9 @@ import (
// A Graph is a store for versioned filesystem images and the relationship between them.
type Graph struct {
Root string
idIndex *TruncIndex
Root string
idIndex *TruncIndex
httpClient *http.Client
}
// NewGraph instantiates a new graph at the given root path in the filesystem.
@@ -97,15 +99,11 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut
img.Parent = container.Image
img.Container = container.Id
img.ContainerConfig = *container.Config
if config == nil {
if parentImage, err := graph.Get(container.Image); err == nil && parentImage != nil {
img.Config = parentImage.Config
}
}
}
if err := graph.Register(layerData, img); err != nil {
return nil, err
}
img.Checksum()
return img, nil
}

View File

@@ -2,6 +2,7 @@ package docker
import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
@@ -51,6 +52,7 @@ func LoadImage(root string) (*Image, error) {
} else if !stat.IsDir() {
return nil, fmt.Errorf("Couldn't load image %s: %s is not a directory", img.Id, layerPath(root))
}
return &img, nil
}
@@ -257,3 +259,62 @@ func (img *Image) layer() (string, error) {
}
return layerPath(root), nil
}
func (img *Image) Checksum() (string, error) {
root, err := img.root()
if err != nil {
return "", err
}
checksumDictPth := path.Join(root, "..", "..", "checksums")
checksums := new(map[string]string)
if checksumDict, err := ioutil.ReadFile(checksumDictPth); err == nil {
if err := json.Unmarshal(checksumDict, checksums); err != nil {
return "", err
}
if checksum, ok := (*checksums)[img.Id]; ok {
return checksum, nil
}
}
layer, err := img.layer()
if err != nil {
return "", err
}
jsonData, err := ioutil.ReadFile(jsonPath(root))
if err != nil {
return "", err
}
layerData, err := Tar(layer, Xz)
if err != nil {
return "", err
}
h := sha256.New()
if _, err := h.Write(jsonData); err != nil {
return "", err
}
if _, err := h.Write([]byte("\n")); err != nil {
return "", err
}
if _, err := io.Copy(h, layerData); err != nil {
return "", err
}
hash := "sha256:" + hex.EncodeToString(h.Sum(nil))
if *checksums == nil {
*checksums = map[string]string{}
}
(*checksums)[img.Id] = hash
checksumJson, err := json.Marshal(checksums)
if err != nil {
return hash, err
}
if err := ioutil.WriteFile(checksumDictPth, checksumJson, 0600); err != nil {
return hash, err
}
return hash, nil
}

View File

@@ -1,3 +1,15 @@
lxc-docker (0.3.0-1) precise; urgency=low
- Registry: Implement the new registry
- Documentation: new example: sharing data between 2 couchdb databases
- Runtime: Fix the command existance check
- Runtime: strings.Split may return an empty string on no match
- Runtime: Fix an index out of range crash if cgroup memory is not
- Documentation: Various improvments
- Vagrant: Use only one deb line in /etc/apt
-- dotCloud <ops@dotcloud.com> Fri, 5 May 2013 00:00:00 -0700
lxc-docker (0.2.2-1) precise; urgency=low
- Support for data volumes ('docker run -v=PATH')
- Share data volumes between containers ('docker run -volumes-from')
@@ -10,7 +22,7 @@ lxc-docker (0.2.2-1) precise; urgency=low
lxc-docker (0.2.1-1) precise; urgency=low
- 'docker commit -run' bundles a layer with default runtime options: command, ports etc.
- 'docker commit -run' bundles a layer with default runtime options: command, ports etc.
- Improve install process on Vagrant
- New Dockerfile operation: "maintainer"
- New Dockerfile operation: "expose"

View File

@@ -1,20 +1,20 @@
package docker
import (
"bytes"
"encoding/json"
"fmt"
"github.com/dotcloud/docker/auth"
"github.com/shin-/cookiejar"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"strings"
)
//FIXME: Set the endpoint in a conf file or via commandline
//const REGISTRY_ENDPOINT = "http://registry-creack.dotcloud.com/v1"
const REGISTRY_ENDPOINT = auth.REGISTRY_SERVER + "/v1"
const INDEX_ENDPOINT = auth.INDEX_SERVER + "/v1"
// Build an Image object from raw json data
func NewImgJson(src []byte) (*Image, error) {
@@ -28,34 +28,23 @@ func NewImgJson(src []byte) (*Image, error) {
return ret, nil
}
// Build an Image object list from a raw json data
// FIXME: Do this in "stream" mode
func NewMultipleImgJson(src []byte) ([]*Image, error) {
ret := []*Image{}
dec := json.NewDecoder(strings.NewReader(string(src)))
for {
m := &Image{}
if err := dec.Decode(m); err == io.EOF {
break
} else if err != nil {
return nil, err
}
ret = append(ret, m)
func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) {
for _, cookie := range c.Jar.Cookies(req.URL) {
req.AddCookie(cookie)
}
return ret, nil
return c.Do(req)
}
// Retrieve the history of a given image from the Registry.
// Return a list of the parent's json (requested image included)
func (graph *Graph) getRemoteHistory(imgId string, authConfig *auth.AuthConfig) ([]*Image, error) {
client := &http.Client{}
func (graph *Graph) getRemoteHistory(imgId, registry string, token []string) ([]string, error) {
client := graph.getHttpClient()
req, err := http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/history", nil)
req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/ancestry", nil)
if err != nil {
return nil, err
}
req.SetBasicAuth(authConfig.Username, authConfig.Password)
req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
res, err := client.Do(req)
if err != nil || res.StatusCode != 200 {
if res != nil {
@@ -70,41 +59,83 @@ func (graph *Graph) getRemoteHistory(imgId string, authConfig *auth.AuthConfig)
return nil, fmt.Errorf("Error while reading the http response: %s\n", err)
}
history, err := NewMultipleImgJson(jsonString)
if err != nil {
return nil, fmt.Errorf("Error while parsing the json: %s\n", err)
Debugf("Ancestry: %s", jsonString)
history := new([]string)
if err := json.Unmarshal(jsonString, history); err != nil {
return nil, err
}
return history, nil
return *history, nil
}
func (graph *Graph) getHttpClient() *http.Client {
if graph.httpClient == nil {
graph.httpClient = new(http.Client)
graph.httpClient.Jar = cookiejar.NewCookieJar()
}
return graph.httpClient
}
// Check if an image exists in the Registry
func (graph *Graph) LookupRemoteImage(imgId string, authConfig *auth.AuthConfig) bool {
func (graph *Graph) LookupRemoteImage(imgId, registry string, authConfig *auth.AuthConfig) bool {
rt := &http.Transport{Proxy: http.ProxyFromEnvironment}
req, err := http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/json", nil)
req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil)
if err != nil {
return false
}
req.SetBasicAuth(authConfig.Username, authConfig.Password)
res, err := rt.RoundTrip(req)
if err != nil || res.StatusCode != 307 {
return false
return err == nil && res.StatusCode == 307
}
func (graph *Graph) getImagesInRepository(repository string, authConfig *auth.AuthConfig) ([]map[string]string, error) {
u := INDEX_ENDPOINT + "/repositories/" + repository + "/images"
req, err := http.NewRequest("GET", u, nil)
if err != nil {
return nil, err
}
return res.StatusCode == 307
if authConfig != nil && len(authConfig.Username) > 0 {
req.SetBasicAuth(authConfig.Username, authConfig.Password)
}
res, err := graph.getHttpClient().Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
// Repository doesn't exist yet
if res.StatusCode == 404 {
return nil, nil
}
jsonData, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
imageList := []map[string]string{}
err = json.Unmarshal(jsonData, &imageList)
if err != nil {
Debugf("Body: %s (%s)\n", res.Body, u)
return nil, err
}
return imageList, nil
}
// Retrieve an image from the Registry.
// Returns the Image object as well as the layer as an Archive (io.Reader)
func (graph *Graph) getRemoteImage(stdout io.Writer, imgId string, authConfig *auth.AuthConfig) (*Image, Archive, error) {
client := &http.Client{}
func (graph *Graph) getRemoteImage(stdout io.Writer, imgId, registry string, token []string) (*Image, Archive, error) {
client := graph.getHttpClient()
fmt.Fprintf(stdout, "Pulling %s metadata\r\n", imgId)
// Get the Json
req, err := http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/json", nil)
req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil)
if err != nil {
return nil, nil, fmt.Errorf("Failed to download json: %s", err)
}
req.SetBasicAuth(authConfig.Username, authConfig.Password)
req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
res, err := client.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("Failed to download json: %s", err)
@@ -127,11 +158,11 @@ func (graph *Graph) getRemoteImage(stdout io.Writer, imgId string, authConfig *a
// Get the layer
fmt.Fprintf(stdout, "Pulling %s fs layer\r\n", imgId)
req, err = http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/layer", nil)
req, err = http.NewRequest("GET", registry+"/images/"+imgId+"/layer", nil)
if err != nil {
return nil, nil, fmt.Errorf("Error while getting from the server: %s\n", err)
}
req.SetBasicAuth(authConfig.Username, authConfig.Password)
req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
res, err = client.Do(req)
if err != nil {
return nil, nil, err
@@ -139,16 +170,87 @@ func (graph *Graph) getRemoteImage(stdout io.Writer, imgId string, authConfig *a
return img, ProgressReader(res.Body, int(res.ContentLength), stdout, "Downloading %v/%v (%v)"), nil
}
func (graph *Graph) PullImage(stdout io.Writer, imgId string, authConfig *auth.AuthConfig) error {
history, err := graph.getRemoteHistory(imgId, authConfig)
func (graph *Graph) getRemoteTags(stdout io.Writer, registries []string, repository string, token []string) (map[string]string, error) {
client := graph.getHttpClient()
if strings.Count(repository, "/") == 0 {
// This will be removed once the Registry supports auto-resolution on
// the "library" namespace
repository = "library/" + repository
}
for _, host := range registries {
endpoint := fmt.Sprintf("https://%s/v1/repositories/%s/tags", host, repository)
req, err := http.NewRequest("GET", endpoint, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
res, err := client.Do(req)
defer res.Body.Close()
Debugf("Got status code %d from %s", res.StatusCode, endpoint)
if err != nil || (res.StatusCode != 200 && res.StatusCode != 404) {
continue
} else if res.StatusCode == 404 {
return nil, fmt.Errorf("Repository not found")
}
result := new(map[string]string)
rawJson, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
if err = json.Unmarshal(rawJson, result); err != nil {
return nil, err
}
return *result, nil
}
return nil, fmt.Errorf("Could not reach any registry endpoint")
}
func (graph *Graph) getImageForTag(stdout io.Writer, tag, remote, registry string, token []string) (string, error) {
client := graph.getHttpClient()
registryEndpoint := "https://" + registry + "/v1"
repositoryTarget := registryEndpoint + "/repositories/" + remote + "/tags/" + tag
req, err := http.NewRequest("GET", repositoryTarget, nil)
if err != nil {
return "", err
}
req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
res, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("Error while retrieving repository info: %v", err)
}
defer res.Body.Close()
if res.StatusCode == 403 {
return "", fmt.Errorf("You aren't authorized to access this resource")
} else if res.StatusCode != 200 {
return "", fmt.Errorf("HTTP code: %d", res.StatusCode)
}
var imgId string
rawJson, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", err
}
if err = json.Unmarshal(rawJson, &imgId); err != nil {
return "", err
}
return imgId, nil
}
func (graph *Graph) PullImage(stdout io.Writer, imgId, registry string, token []string) error {
history, err := graph.getRemoteHistory(imgId, registry, token)
if err != nil {
return err
}
// FIXME: Try to stream the images?
// FIXME: Lunch the getRemoteImage() in goroutines
for _, j := range history {
if !graph.Exists(j.Id) {
img, layer, err := graph.getRemoteImage(stdout, j.Id, authConfig)
// FIXME: Launch the getRemoteImage() in goroutines
for _, id := range history {
if !graph.Exists(id) {
img, layer, err := graph.getRemoteImage(stdout, id, registry, token)
if err != nil {
// FIXME: Keep goging in case of error?
return err
@@ -161,165 +263,195 @@ func (graph *Graph) PullImage(stdout io.Writer, imgId string, authConfig *auth.A
return nil
}
// FIXME: Handle the askedTag parameter
func (graph *Graph) PullRepository(stdout io.Writer, remote, askedTag string, repositories *TagStore, authConfig *auth.AuthConfig) error {
client := &http.Client{}
client := graph.getHttpClient()
fmt.Fprintf(stdout, "Pulling repository %s\r\n", remote)
var repositoryTarget string
// If we are asking for 'root' repository, lookup on the Library's registry
if strings.Index(remote, "/") == -1 {
repositoryTarget = REGISTRY_ENDPOINT + "/library/" + remote
} else {
repositoryTarget = REGISTRY_ENDPOINT + "/users/" + remote
}
fmt.Fprintf(stdout, "Pulling repository %s from %s\r\n", remote, INDEX_ENDPOINT)
repositoryTarget := INDEX_ENDPOINT + "/repositories/" + remote + "/images"
req, err := http.NewRequest("GET", repositoryTarget, nil)
if err != nil {
return err
}
req.SetBasicAuth(authConfig.Username, authConfig.Password)
if authConfig != nil && len(authConfig.Username) > 0 {
req.SetBasicAuth(authConfig.Username, authConfig.Password)
}
req.Header.Set("X-Docker-Token", "true")
res, err := client.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode == 401 {
return fmt.Errorf("Please login first (HTTP code %d)", res.StatusCode)
}
// TODO: Right now we're ignoring checksums in the response body.
// In the future, we need to use them to check image validity.
if res.StatusCode != 200 {
return fmt.Errorf("HTTP code: %d", res.StatusCode)
}
rawJson, err := ioutil.ReadAll(res.Body)
var token, endpoints []string
if res.Header.Get("X-Docker-Token") != "" {
token = res.Header["X-Docker-Token"]
}
if res.Header.Get("X-Docker-Endpoints") != "" {
endpoints = res.Header["X-Docker-Endpoints"]
} else {
return fmt.Errorf("Index response didn't contain any endpoints")
}
var tagsList map[string]string
if askedTag == "" {
tagsList, err = graph.getRemoteTags(stdout, endpoints, remote, token)
if err != nil {
return err
}
} else {
tagsList = map[string]string{askedTag: ""}
}
for askedTag, imgId := range tagsList {
fmt.Fprintf(stdout, "Resolving tag \"%s:%s\" from %s\n", remote, askedTag, endpoints)
success := false
for _, registry := range endpoints {
if imgId == "" {
imgId, err = graph.getImageForTag(stdout, askedTag, remote, registry, token)
if err != nil {
fmt.Fprintf(stdout, "Error while retrieving image for tag: %v (%v) ; "+
"checking next endpoint", askedTag, err)
continue
}
}
if err := graph.PullImage(stdout, imgId, "https://"+registry+"/v1", token); err != nil {
return err
}
if err = repositories.Set(remote, askedTag, imgId, true); err != nil {
return err
}
success = true
}
if !success {
return fmt.Errorf("Could not find repository on any of the indexed registries.")
}
}
if err = repositories.Save(); err != nil {
return err
}
return nil
}
func pushImageRec(graph *Graph, stdout io.Writer, img *Image, registry string, token []string) error {
if parent, err := img.GetParent(); err != nil {
return err
} else if parent != nil {
if err := pushImageRec(graph, stdout, parent, registry, token); err != nil {
return err
}
}
client := graph.getHttpClient()
jsonRaw, err := ioutil.ReadFile(path.Join(graph.Root, img.Id, "json"))
if err != nil {
return fmt.Errorf("Error while retreiving the path for {%s}: %s", img.Id, err)
}
fmt.Fprintf(stdout, "Pushing %s metadata\r\n", img.Id)
// FIXME: try json with UTF8
jsonData := strings.NewReader(string(jsonRaw))
req, err := http.NewRequest("PUT", registry+"/images/"+img.Id+"/json", jsonData)
if err != nil {
return err
}
t := map[string]string{}
if err = json.Unmarshal(rawJson, &t); err != nil {
req.Header.Add("Content-type", "application/json")
req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
checksum, err := img.Checksum()
if err != nil {
return fmt.Errorf("Error while retrieving checksum for %s: %v", img.Id, err)
}
req.Header.Set("X-Docker-Checksum", checksum)
res, err := doWithCookies(client, req)
if err != nil {
return fmt.Errorf("Failed to upload metadata: %s", err)
}
defer res.Body.Close()
if len(res.Cookies()) > 0 {
client.Jar.SetCookies(req.URL, res.Cookies())
}
if res.StatusCode != 200 {
errBody, err := ioutil.ReadAll(res.Body)
if err != nil {
return fmt.Errorf("HTTP code %d while uploading metadata and error when"+
" trying to parse response body: %v", res.StatusCode, err)
}
var jsonBody map[string]string
if err := json.Unmarshal(errBody, &jsonBody); err != nil {
errBody = []byte(err.Error())
} else if jsonBody["error"] == "Image already exists" {
fmt.Fprintf(stdout, "Image %v already uploaded ; skipping\n", img.Id)
return nil
}
return fmt.Errorf("HTTP code %d while uploading metadata: %s", res.StatusCode, errBody)
}
fmt.Fprintf(stdout, "Pushing %s fs layer\r\n", img.Id)
layerData, err := graph.TempLayerArchive(img.Id, Xz, stdout)
if err != nil {
return fmt.Errorf("Failed to generate layer archive: %s", err)
}
req3, err := http.NewRequest("PUT", registry+"/images/"+img.Id+"/layer",
ProgressReader(layerData, -1, stdout, ""))
if err != nil {
return err
}
for tag, rev := range t {
fmt.Fprintf(stdout, "Pulling tag %s:%s\r\n", remote, tag)
if err = graph.PullImage(stdout, rev, authConfig); err != nil {
return err
}
if err = repositories.Set(remote, tag, rev, true); err != nil {
return err
}
req3.ContentLength = -1
req3.TransferEncoding = []string{"chunked"}
req3.Header.Set("Authorization", "Token "+strings.Join(token, ","))
res3, err := doWithCookies(client, req3)
if err != nil {
return fmt.Errorf("Failed to upload layer: %s", err)
}
if err = repositories.Save(); err != nil {
return err
res3.Body.Close()
if res3.StatusCode != 200 {
return fmt.Errorf("Received HTTP code %d while uploading layer", res3.StatusCode)
}
return nil
}
// Push a local image to the registry with its history if needed
func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth.AuthConfig) error {
client := &http.Client{}
// FIXME: Factorize the code
// FIXME: Do the puts in goroutines
if err := imgOrig.WalkHistory(func(img *Image) error {
jsonRaw, err := ioutil.ReadFile(path.Join(graph.Root, img.Id, "json"))
if err != nil {
return fmt.Errorf("Error while retreiving the path for {%s}: %s", img.Id, err)
}
fmt.Fprintf(stdout, "Pushing %s metadata\r\n", img.Id)
// FIXME: try json with UTF8
jsonData := strings.NewReader(string(jsonRaw))
req, err := http.NewRequest("PUT", REGISTRY_ENDPOINT+"/images/"+img.Id+"/json", jsonData)
if err != nil {
return err
}
req.Header.Add("Content-type", "application/json")
req.SetBasicAuth(authConfig.Username, authConfig.Password)
res, err := client.Do(req)
if err != nil {
return fmt.Errorf("Failed to upload metadata: %s", err)
}
defer res.Body.Close()
if res.StatusCode != 200 {
switch res.StatusCode {
case 204:
// Case where the image is already on the Registry
// FIXME: Do not be silent?
return nil
default:
errBody, err := ioutil.ReadAll(res.Body)
if err != nil {
errBody = []byte(err.Error())
}
return fmt.Errorf("HTTP code %d while uploading metadata: %s", res.StatusCode, errBody)
}
}
fmt.Fprintf(stdout, "Pushing %s fs layer\r\n", img.Id)
req2, err := http.NewRequest("PUT", REGISTRY_ENDPOINT+"/images/"+img.Id+"/layer", nil)
req2.SetBasicAuth(authConfig.Username, authConfig.Password)
res2, err := client.Do(req2)
if err != nil {
return fmt.Errorf("Registry returned error: %s", err)
}
res2.Body.Close()
if res2.StatusCode != 307 {
return fmt.Errorf("Registry returned unexpected HTTP status code %d, expected 307", res2.StatusCode)
}
url, err := res2.Location()
if err != nil || url == nil {
return fmt.Errorf("Failed to retrieve layer upload location: %s", err)
}
// FIXME: stream the archive directly to the registry instead of buffering it on disk. This requires either:
// a) Implementing S3's proprietary streaming logic, or
// b) Stream directly to the registry instead of S3.
// I prefer option b. because it doesn't lock us into a proprietary cloud service.
tmpLayer, err := graph.TempLayerArchive(img.Id, Xz, stdout)
if err != nil {
return err
}
defer os.Remove(tmpLayer.Name())
req3, err := http.NewRequest("PUT", url.String(), ProgressReader(tmpLayer, int(tmpLayer.Size), stdout, "Uploading %v/%v (%v)"))
if err != nil {
return err
}
req3.ContentLength = int64(tmpLayer.Size)
req3.TransferEncoding = []string{"none"}
res3, err := client.Do(req3)
if err != nil {
return fmt.Errorf("Failed to upload layer: %s", err)
}
res3.Body.Close()
if res3.StatusCode != 200 {
return fmt.Errorf("Received HTTP code %d while uploading layer", res3.StatusCode)
}
return nil
}); err != nil {
return err
}
return nil
func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, registry string, token []string) error {
registry = "https://" + registry + "/v1"
return pushImageRec(graph, stdout, imgOrig, registry, token)
}
// push a tag on the registry.
// Remote has the format '<user>/<repo>
func (graph *Graph) pushTag(remote, revision, tag string, authConfig *auth.AuthConfig) error {
// Keep this for backward compatibility
if tag == "" {
tag = "lastest"
}
func (graph *Graph) pushTag(remote, revision, tag, registry string, token []string) error {
// "jsonify" the string
revision = "\"" + revision + "\""
registry = "https://" + registry + "/v1"
Debugf("Pushing tags for rev [%s] on {%s}\n", revision, REGISTRY_ENDPOINT+"/users/"+remote+"/"+tag)
Debugf("Pushing tags for rev [%s] on {%s}\n", revision, registry+"/users/"+remote+"/"+tag)
client := &http.Client{}
req, err := http.NewRequest("PUT", REGISTRY_ENDPOINT+"/users/"+remote+"/"+tag, strings.NewReader(revision))
client := graph.getHttpClient()
req, err := http.NewRequest("PUT", registry+"/repositories/"+remote+"/tags/"+tag, strings.NewReader(revision))
if err != nil {
return err
}
req.Header.Add("Content-type", "application/json")
req.SetBasicAuth(authConfig.Username, authConfig.Password)
res, err := client.Do(req)
req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
req.ContentLength = int64(len(revision))
res, err := doWithCookies(client, req)
if err != nil {
return err
}
@@ -327,62 +459,25 @@ func (graph *Graph) pushTag(remote, revision, tag string, authConfig *auth.AuthC
if res.StatusCode != 200 && res.StatusCode != 201 {
return fmt.Errorf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote)
}
Debugf("Result of push tag: %d\n", res.StatusCode)
switch res.StatusCode {
default:
return fmt.Errorf("Error %d\n", res.StatusCode)
case 200:
case 201:
}
return nil
}
func (graph *Graph) LookupRemoteRepository(remote string, authConfig *auth.AuthConfig) bool {
rt := &http.Transport{Proxy: http.ProxyFromEnvironment}
var repositoryTarget string
// If we are asking for 'root' repository, lookup on the Library's registry
if strings.Index(remote, "/") == -1 {
repositoryTarget = REGISTRY_ENDPOINT + "/library/" + remote + "/lookup"
} else {
repositoryTarget = REGISTRY_ENDPOINT + "/users/" + remote + "/lookup"
}
Debugf("Checking for permissions on: %s", repositoryTarget)
req, err := http.NewRequest("PUT", repositoryTarget, strings.NewReader("\"\""))
if err != nil {
Debugf("%s\n", err)
return false
}
req.SetBasicAuth(authConfig.Username, authConfig.Password)
req.Header.Add("Content-type", "application/json")
res, err := rt.RoundTrip(req)
if err != nil || res.StatusCode != 404 {
errBody, err := ioutil.ReadAll(res.Body)
if err != nil {
errBody = []byte(err.Error())
}
Debugf("Lookup status code: %d (body: %s)", res.StatusCode, errBody)
return false
}
return true
}
// FIXME: this should really be PushTag
func (graph *Graph) pushPrimitive(stdout io.Writer, remote, tag, imgId string, authConfig *auth.AuthConfig) error {
func (graph *Graph) pushPrimitive(stdout io.Writer, remote, tag, imgId, registry string, token []string) error {
// Check if the local impage exists
img, err := graph.Get(imgId)
if err != nil {
fmt.Fprintf(stdout, "Skipping tag %s:%s: %s does not exist\r\n", remote, tag, imgId)
return nil
}
fmt.Fprintf(stdout, "Pushing tag %s:%s\r\n", remote, tag)
fmt.Fprintf(stdout, "Pushing image %s:%s\r\n", remote, tag)
// Push the image
if err = graph.PushImage(stdout, img, authConfig); err != nil {
if err = graph.PushImage(stdout, img, registry, token); err != nil {
return err
}
fmt.Fprintf(stdout, "Registering tag %s:%s\r\n", remote, tag)
// And then the tag
if err = graph.pushTag(remote, imgId, tag, authConfig); err != nil {
if err = graph.pushTag(remote, imgId, tag, registry, token); err != nil {
return err
}
return nil
@@ -391,18 +486,155 @@ func (graph *Graph) pushPrimitive(stdout io.Writer, remote, tag, imgId string, a
// Push a repository to the registry.
// Remote has the format '<user>/<repo>
func (graph *Graph) PushRepository(stdout io.Writer, remote string, localRepo Repository, authConfig *auth.AuthConfig) error {
// Check if the remote repository exists/if we have the permission
if !graph.LookupRemoteRepository(remote, authConfig) {
return fmt.Errorf("Permission denied on repository %s\n", remote)
client := graph.getHttpClient()
checksums, err := graph.Checksums(stdout, localRepo)
if err != nil {
return err
}
fmt.Fprintf(stdout, "Pushing repository %s (%d tags)\r\n", remote, len(localRepo))
// For each image within the repo, push them
for tag, imgId := range localRepo {
if err := graph.pushPrimitive(stdout, remote, tag, imgId, authConfig); err != nil {
// FIXME: Continue on error?
return err
imgList := make([]map[string]string, len(checksums))
checksums2 := make([]map[string]string, len(checksums))
uploadedImages, err := graph.getImagesInRepository(remote, authConfig)
if err != nil {
return fmt.Errorf("Error occured while fetching the list: %s", err)
}
// Filter list to only send images/checksums not already uploaded
i := 0
for _, obj := range checksums {
found := false
for _, uploadedImg := range uploadedImages {
if obj["id"] == uploadedImg["id"] && uploadedImg["checksum"] != "" {
found = true
break
}
}
if !found {
imgList[i] = map[string]string{"id": obj["id"]}
checksums2[i] = obj
i += 1
}
}
checksums = checksums2[:i]
imgList = imgList[:i]
imgListJson, err := json.Marshal(imgList)
if err != nil {
return err
}
req, err := http.NewRequest("PUT", INDEX_ENDPOINT+"/repositories/"+remote+"/", bytes.NewReader(imgListJson))
if err != nil {
return err
}
req.SetBasicAuth(authConfig.Username, authConfig.Password)
req.ContentLength = int64(len(imgListJson))
req.Header.Set("X-Docker-Token", "true")
res, err := client.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
for res.StatusCode >= 300 && res.StatusCode < 400 {
Debugf("Redirected to %s\n", res.Header.Get("Location"))
req, err = http.NewRequest("PUT", res.Header.Get("Location"), bytes.NewReader(imgListJson))
if err != nil {
return err
}
req.SetBasicAuth(authConfig.Username, authConfig.Password)
req.ContentLength = int64(len(imgListJson))
req.Header.Set("X-Docker-Token", "true")
res, err = client.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
}
if res.StatusCode != 200 && res.StatusCode != 201 {
return fmt.Errorf("Error: Status %d trying to push repository %s", res.StatusCode, remote)
}
var token, endpoints []string
if res.Header.Get("X-Docker-Token") != "" {
token = res.Header["X-Docker-Token"]
Debugf("Auth token: %v", token)
} else {
return fmt.Errorf("Index response didn't contain an access token")
}
if res.Header.Get("X-Docker-Endpoints") != "" {
endpoints = res.Header["X-Docker-Endpoints"]
} else {
return fmt.Errorf("Index response didn't contain any endpoints")
}
for _, registry := range endpoints {
fmt.Fprintf(stdout, "Pushing repository %s to %s (%d tags)\r\n", remote, registry,
len(localRepo))
// For each image within the repo, push them
for tag, imgId := range localRepo {
if err := graph.pushPrimitive(stdout, remote, tag, imgId, registry, token); err != nil {
// FIXME: Continue on error?
return err
}
}
}
checksumsJson, err := json.Marshal(checksums)
if err != nil {
return err
}
req2, err := http.NewRequest("PUT", INDEX_ENDPOINT+"/repositories/"+remote+"/images", bytes.NewReader(checksumsJson))
if err != nil {
return err
}
req2.SetBasicAuth(authConfig.Username, authConfig.Password)
req2.Header["X-Docker-Endpoints"] = endpoints
req2.ContentLength = int64(len(checksumsJson))
res2, err := client.Do(req2)
if err != nil {
return err
}
res2.Body.Close()
if res2.StatusCode != 204 {
return fmt.Errorf("Error: Status %d trying to push checksums %s", res.StatusCode, remote)
}
return nil
}
func (graph *Graph) Checksums(output io.Writer, repo Repository) ([]map[string]string, error) {
var result []map[string]string
checksums := map[string]string{}
for _, id := range repo {
img, err := graph.Get(id)
if err != nil {
return nil, err
}
err = img.WalkHistory(func(image *Image) error {
fmt.Fprintf(output, "Computing checksum for image %s\n", image.Id)
if _, exists := checksums[image.Id]; !exists {
checksums[image.Id], err = image.Checksum()
if err != nil {
return err
}
}
return nil
})
if err != nil {
return nil, err
}
}
i := 0
result = make([]map[string]string, len(checksums))
for id, sum := range checksums {
result[i] = map[string]string{
"id": id,
"checksum": sum,
}
i++
}
return result, nil
}

View File

@@ -80,10 +80,10 @@ func (runtime *Runtime) containerRoot(id string) string {
}
func (runtime *Runtime) mergeConfig(userConf, imageConf *Config) {
if userConf.Hostname != "" {
if userConf.Hostname == "" {
userConf.Hostname = imageConf.Hostname
}
if userConf.User != "" {
if userConf.User == "" {
userConf.User = imageConf.User
}
if userConf.Memory == 0 {
@@ -127,7 +127,7 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) {
runtime.mergeConfig(config, img.Config)
}
if config.Cmd == nil {
if config.Cmd == nil || len(config.Cmd) == 0 {
return nil, fmt.Errorf("No command specified")
}

View File

@@ -2,6 +2,8 @@ package docker
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"github.com/dotcloud/docker/rcli"
@@ -395,6 +397,15 @@ func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error)
return written, err
}
func HashData(src io.Reader) (string, error) {
h := sha256.New()
if _, err := io.Copy(h, src); err != nil {
return "", err
}
return "sha256:" + hex.EncodeToString(h.Sum(nil)), nil
}
type KernelVersionInfo struct {
Kernel int
Major int
@@ -445,7 +456,7 @@ func FindCgroupMountpoint(cgroupType string) (string, error) {
// cgroup /sys/fs/cgroup/devices cgroup rw,relatime,devices 0 0
for _, line := range strings.Split(string(output), "\n") {
parts := strings.Split(line, " ")
if parts[2] == "cgroup" {
if len(parts) == 6 && parts[2] == "cgroup" {
for _, opt := range strings.Split(parts[3], ",") {
if opt == cgroupType {
return parts[1], nil